From 228dd868e3b6881acb781e7b01aed9ef3085bafb Mon Sep 17 00:00:00 2001
From: John Fawcett
Date: Fri, 3 Apr 2026 05:16:46 -0500
Subject: [PATCH 01/14] docs(gastown): add local debug testing guide and drain
test script
- docs/local-debug-testing.md: comprehensive guide covering debug
endpoints, container monitoring, drain testing, wrangler management,
drain architecture, and common issues
- scripts/test-drain.sh: automated end-to-end drain test that sends
work, waits for an agent, triggers graceful stop, monitors the drain,
and prints PASS/FAIL with diagnostics
---
.../docs/local-debug-testing.md | 305 ++++++++++++++++++
cloudflare-gastown/scripts/test-drain.sh | 146 +++++++++
2 files changed, 451 insertions(+)
create mode 100644 cloudflare-gastown/docs/local-debug-testing.md
create mode 100755 cloudflare-gastown/scripts/test-drain.sh
diff --git a/cloudflare-gastown/docs/local-debug-testing.md b/cloudflare-gastown/docs/local-debug-testing.md
new file mode 100644
index 000000000..7ea1af6cc
--- /dev/null
+++ b/cloudflare-gastown/docs/local-debug-testing.md
@@ -0,0 +1,305 @@
+# Local Debug Testing Guide
+
+Guide for an AI agent to test gastown features locally — dispatching work, monitoring agents, triggering container eviction, and verifying the drain flow.
+
+## Prerequisites
+
+- Wrangler dev server running for gastown (`pnpm dev` in `cloudflare-gastown/`)
+- Read the dev server port from the wrangler config (default: 8803)
+- A town ID (ask the user or check the UI)
+- Docker running (containers are managed by wrangler's container runtime)
+
+## Quick Reference
+
+```bash
+BASE=http://localhost:8803
+TOWN_ID=""
+```
+
+## 1. Debug Endpoints
+
+All `/debug/` endpoints are unauthenticated in local dev.
+
+### Town Status
+
+The primary status endpoint — shows agents, beads, alarm, patrol, and drain state:
+
+```bash
+curl -s $BASE/debug/towns/$TOWN_ID/status | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+a = d.get('alarmStatus', {})
+print('Agents:', json.dumps(a.get('agents', {})))
+print('Draining:', a.get('draining', False))
+print()
+for am in d.get('agentMeta', []):
+ print(f\" {am.get('role', '?'):12s} bead={am.get('bead_id', '?')[:8]} status={am.get('status', '?'):10s} hook={str(am.get('current_hook_bead_id', 'NULL'))[:8]}\")
+print()
+for b in d.get('beadSummary', []):
+ if b.get('type') == 'issue' and b.get('status') in ('open', 'in_progress'):
+ print(f\" bead={b.get('bead_id', '?')[:8]} status={b.get('status', '?'):15s} {b.get('title', '')[:60]}\")
+"
+```
+
+### Drain Status
+
+```bash
+curl -s $BASE/debug/towns/$TOWN_ID/drain-status | python3 -m json.tool
+```
+
+### Pending Nudges
+
+```bash
+curl -s $BASE/debug/towns/$TOWN_ID/nudges | python3 -m json.tool
+```
+
+### Send Message to Mayor (dev only)
+
+Creates beads by telling the mayor what to do:
+
+```bash
+curl -s -m 120 -X POST $BASE/debug/towns/$TOWN_ID/send-message \
+ -H "Content-Type: application/json" \
+ -d '{"message": "Create a bead for this task: Write src/example.ts with 10 utility functions. Commit and push when done."}'
+```
+
+### Trigger Graceful Stop (dev only)
+
+Sends SIGTERM to the container, initiating the drain flow:
+
+```bash
+curl -s -X POST $BASE/debug/towns/$TOWN_ID/graceful-stop
+```
+
+## 2. Container Monitoring
+
+### Find the Active Container
+
+```bash
+CONTAINER_ID=$(docker ps --format "{{.ID}}\t{{.Image}}" | grep towncontainerdo | head -1 | awk '{print $1}')
+echo "Container: $CONTAINER_ID"
+```
+
+### Container Health
+
+```bash
+docker exec $CONTAINER_ID curl -s http://localhost:8080/health | python3 -m json.tool
+```
+
+### Live Container Logs
+
+Stream to a file for analysis:
+
+```bash
+docker logs -f $CONTAINER_ID > /tmp/container-logs.log 2>&1 &
+```
+
+### Filter Drain Logs
+
+```bash
+grep -E "\[drain\]|handleIdleEvent|idle timeout|agent\.(exit|start)|Drain complete" /tmp/container-logs.log
+```
+
+### Check Agent Events
+
+```bash
+docker logs $CONTAINER_ID 2>&1 | grep "Event #" | tail -10
+```
+
+## 3. Testing Graceful Container Eviction
+
+### Full Test Loop
+
+The recommended sequence for verifying the drain flow end-to-end:
+
+#### Step 1: Verify clean state
+
+```bash
+# Not draining, agents are available
+curl -s $BASE/debug/towns/$TOWN_ID/drain-status # draining: false
+curl -s $BASE/debug/towns/$TOWN_ID/status # check agent statuses
+```
+
+#### Step 2: Send work to the mayor
+
+```bash
+curl -s -m 120 -X POST $BASE/debug/towns/$TOWN_ID/send-message \
+ -H "Content-Type: application/json" \
+ -d '{"message": "Create a bead for this task: ."}'
+```
+
+#### Step 3: Wait for a polecat to be dispatched and working
+
+Poll until a polecat shows `status=working`:
+
+```bash
+for i in $(seq 1 40); do
+ WORKING=$(curl -s $BASE/debug/towns/$TOWN_ID/status | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for am in d.get('agentMeta', []):
+ if am.get('role') == 'polecat' and am.get('status') == 'working':
+ print(f\"polecat:{am.get('bead_id', '?')[:8]}\")
+" 2>/dev/null)
+ echo "$(date +%H:%M:%S) $WORKING"
+ if [ -n "$WORKING" ]; then echo "READY"; break; fi
+ sleep 15
+done
+```
+
+#### Step 4: Start monitoring container logs
+
+```bash
+CONTAINER_ID=$(docker ps --format "{{.ID}}\t{{.Image}}" | grep towncontainerdo | head -1 | awk '{print $1}')
+docker logs -f $CONTAINER_ID > /tmp/drain-test.log 2>&1 &
+```
+
+#### Step 5: Trigger graceful stop
+
+```bash
+curl -s -X POST $BASE/debug/towns/$TOWN_ID/graceful-stop
+```
+
+#### Step 6: Monitor the drain
+
+```bash
+# Watch for container exit
+for i in $(seq 1 60); do
+ sleep 10
+ RUNNING=$(docker ps -q --filter "id=$CONTAINER_ID")
+ if [ -z "$RUNNING" ]; then
+ echo "$(date +%H:%M:%S) CONTAINER EXITED"
+ break
+ fi
+ DRAIN_LINE=$(grep "\[drain\]" /tmp/drain-test.log | tail -1)
+ echo "$(date +%H:%M:%S) ${DRAIN_LINE:0:100}"
+done
+```
+
+#### Step 7: Verify the drain log
+
+```bash
+grep -E "\[drain\]|handleIdleEvent.*(idle timeout fired)|agent\.(exit|start)|Drain complete" /tmp/drain-test.log
+```
+
+**Expected drain flow:**
+
+1. `Phase 1: TownDO responded 200` — TownDO notified, dispatch blocked
+2. `Phase 2: waiting up to 300s` — waiting for non-mayor agents
+3. `Waiting for N non-mayor agents` — polling until agents finish or idle
+4. `All N non-mayor agents are idle` — agents finished, timers pending
+5. `idle timeout fired` / `agent.exit` — agents exit via normal path
+6. `Phase 3: freezing N straggler(s)` — only the mayor (or truly stuck agents)
+7. `Phase 3: force-saving agent ...` — WIP git commit for stragglers
+8. `Drain complete` — container exits
+
+#### Step 8: Verify post-drain state
+
+```bash
+# Drain flag should clear within ~30s (heartbeat instance ID detection)
+curl -s $BASE/debug/towns/$TOWN_ID/drain-status
+
+# Check bead states — evicted beads should be 'open' with eviction context
+curl -s $BASE/debug/towns/$TOWN_ID/status | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for am in d.get('agentMeta', []):
+ print(f\" {am.get('role', '?'):12s} status={am.get('status', '?'):10s} hook={str(am.get('current_hook_bead_id', 'NULL'))[:8]}\")
+"
+```
+
+## 4. Wrangler Management
+
+### Restart Wrangler
+
+When you need to pick up code changes:
+
+```bash
+# Kill existing
+ps aux | grep -i wrangler | grep gastown | grep -v grep | awk '{print $2}' | xargs kill -9
+ps aux | grep workerd | grep -v grep | awk '{print $2}' | xargs kill -9
+sleep 3
+
+# Start fresh
+cd cloudflare-gastown && nohup pnpm dev > /tmp/gastown-wrangler.log 2>&1 &
+sleep 25
+curl -s http://localhost:8803/health
+```
+
+### Check Wrangler Logs
+
+```bash
+# Dispatch errors
+grep -i "error\|failed\|dispatch" /tmp/gastown-wrangler.log | tail -20
+
+# Container lifecycle
+grep "container\|startAgent\|eviction" /tmp/gastown-wrangler.log | tail -20
+```
+
+## 5. Drain Architecture
+
+### 3-Phase Drain (SIGTERM handler)
+
+1. **Phase 1: Notify TownDO** — POST `/api/towns/:id/container-eviction`. Sets `_draining = true` on the TownDO, blocking new agent dispatch. Records the drain nonce and start time.
+
+2. **Phase 2: Wait for agents** — Poll the container's local `agents` Map every 5s for up to 5 minutes. Excludes mayors (they never exit on their own). When all non-mayor agents have idle timers pending (meaning they called `gt_done` and went idle), polls at 1s for fast exit. Agents exit through the normal `exitAgent` → `reportAgentCompleted` path.
+
+3. **Phase 3: Force-save stragglers** — Any agents still running after 5 minutes are frozen (SDK session aborted), WIP git committed and pushed, eviction context written on the bead body, and `reportAgentCompleted(agent, 'completed', 'container eviction')` called. The TownDO resets the bead to `open` and clears the assignee so the reconciler re-dispatches on the next tick.
+
+### Drain Flag Clearing
+
+The TownDO's `_draining` flag is cleared by whichever happens first:
+- **Heartbeat instance ID change** (~30s): each container has a UUID. When a new container's heartbeat arrives with a different ID, the drain clears.
+- **Nonce handshake**: the new container calls `/container-ready` with the drain nonce.
+- **Hard timeout** (7 min): safety net if no heartbeat or handshake arrives.
+
+### Key Behaviors During Drain
+
+- `isDraining()` returns true → `/agents/start` returns 503
+- `handleIdleEvent` skips `fetchPendingNudges` (avoids hanging outbound HTTP)
+- Idle timeout is 10s (vs normal 120s/600s) so agents exit promptly
+- `reconcileAgents` skips stale-heartbeat checks (heartbeat reporter is stopped)
+- `reconcileGUPP` skips entirely (no false "idle for 15 minutes" nudges)
+- Evicted `in_progress` beads are reset to `open` with assignee cleared
+
+## 6. Common Issues
+
+### Container Keeps Cycling
+
+Containers start and immediately get killed (exit code 137). This is usually the wrangler container runtime recreating them. Kill all containers and restart wrangler cleanly:
+
+```bash
+docker kill $(docker ps -q) 2>/dev/null
+# Then restart wrangler (see section 4)
+```
+
+### Drain Stuck "Waiting for N agents"
+
+Agents show as `running` but aren't doing work. Common causes:
+- **`fetchPendingNudges` hanging**: should be skipped during drain (check `_draining` flag)
+- **`server.heartbeat` clearing idle timer**: these events should be in `IDLE_TIMER_IGNORE_EVENTS`
+- **Agent in `starting` status**: `session.prompt()` blocking. Status is set to `running` before the prompt now.
+
+### Drain Flag Persists After Restart
+
+The drain banner stays visible after the container restarted. Causes:
+- **No heartbeats arriving**: container failed to start, no agents registered
+- **`_containerInstanceId` not persisted**: should be in `ctx.storage`
+- Fallback: wait for the 7-minute hard timeout
+
+### Git Credential Errors
+
+Container starts but agents fail at `git clone`:
+```
+Error checking if container is ready: Invalid username
+```
+This means the git token is stale/expired. Refresh credentials in the town settings.
+
+### Accumulating Escalation Beads
+
+Triage/escalation beads pile up with `rig_id=NULL`. These are by design:
+- `type=escalation` beads surface for human attention (merge conflicts, rework)
+- `type=issue` triage beads are handled by `maybeDispatchTriageAgent`
+- GUPP force-stop beads are created by the patrol system for stuck agents
+
+During testing, container restarts generate many of these. Bulk-close via admin panel if needed.
diff --git a/cloudflare-gastown/scripts/test-drain.sh b/cloudflare-gastown/scripts/test-drain.sh
new file mode 100755
index 000000000..b013cc54e
--- /dev/null
+++ b/cloudflare-gastown/scripts/test-drain.sh
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+#
+# test-drain.sh — End-to-end graceful container eviction test.
+#
+# Usage:
+# ./scripts/test-drain.sh [port]
+#
+# Sends a task to the mayor, waits for a polecat to start working,
+# triggers a graceful stop, and monitors the drain to completion.
+# Prints a PASS/FAIL summary at the end.
+#
+# Requires: docker, curl, python3
+
+set -euo pipefail
+
+TOWN_ID="${1:?Usage: $0 [port]}"
+PORT="${2:-8803}"
+BASE="http://localhost:$PORT"
+LOG_FILE="/tmp/drain-test-$(date +%s).log"
+
+info() { echo "[test-drain] $*"; }
+fail() { echo "[test-drain] FAIL: $*" >&2; exit 1; }
+
+# ── Preflight ────────────────────────────────────────────────────────────
+info "Town: $TOWN_ID Port: $PORT Log: $LOG_FILE"
+
+curl -sf "$BASE/health" > /dev/null || fail "Wrangler not running on port $PORT"
+
+DRAIN=$(curl -sf "$BASE/debug/towns/$TOWN_ID/drain-status" | python3 -c "import sys,json; print(json.load(sys.stdin).get('draining', True))")
+[ "$DRAIN" = "False" ] || fail "Town is still draining from a previous run. Wait for it to clear."
+
+info "Preflight OK"
+
+# ── Step 1: Send task ────────────────────────────────────────────────────
+info "Sending task to mayor..."
+curl -sf -m 120 -X POST "$BASE/debug/towns/$TOWN_ID/send-message" \
+ -H "Content-Type: application/json" \
+ -d '{"message": "Create a bead for this task: Write src/drainTestUtils.ts with 8 utility functions including capitalize, slugify, truncate, camelCase, kebabCase, reverse, countWords, and isPalindrome. Each function needs JSDoc comments. Commit and push when done."}' > /dev/null
+
+info "Task sent"
+
+# ── Step 2: Wait for working polecat ─────────────────────────────────────
+info "Waiting for a polecat to start working..."
+POLECAT_READY=false
+for i in $(seq 1 40); do
+ WORKING=$(curl -sf "$BASE/debug/towns/$TOWN_ID/status" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for am in d.get('agentMeta', []):
+ if am.get('role') == 'polecat' and am.get('status') == 'working':
+ print(am.get('bead_id', '?')[:8])
+" 2>/dev/null || true)
+ if [ -n "$WORKING" ]; then
+ info "Polecat working: $WORKING"
+ POLECAT_READY=true
+ break
+ fi
+ sleep 15
+done
+$POLECAT_READY || fail "No polecat started working within 10 minutes"
+
+# ── Step 3: Find container and start log capture ─────────────────────────
+CONTAINER_ID=$(docker ps --format "{{.ID}}\t{{.Image}}" | grep towncontainerdo | head -1 | awk '{print $1}')
+[ -n "$CONTAINER_ID" ] || fail "No town container found"
+info "Container: $CONTAINER_ID"
+
+docker logs -f "$CONTAINER_ID" > "$LOG_FILE" 2>&1 &
+LOG_PID=$!
+trap "kill $LOG_PID 2>/dev/null" EXIT
+
+# Wait 30s for the polecat to make progress
+info "Waiting 30s for polecat progress..."
+sleep 30
+
+# ── Step 4: Trigger graceful stop ────────────────────────────────────────
+info "=== TRIGGERING GRACEFUL STOP ==="
+curl -sf -X POST "$BASE/debug/towns/$TOWN_ID/graceful-stop" > /dev/null
+DRAIN_START=$(date +%s)
+
+# ── Step 5: Monitor drain ────────────────────────────────────────────────
+info "Monitoring drain..."
+CONTAINER_EXITED=false
+for i in $(seq 1 60); do
+ sleep 10
+ RUNNING=$(docker ps -q --filter "id=$CONTAINER_ID" 2>/dev/null)
+ if [ -z "$RUNNING" ]; then
+ DRAIN_END=$(date +%s)
+ DRAIN_SECS=$((DRAIN_END - DRAIN_START))
+ info "Container exited after ${DRAIN_SECS}s"
+ CONTAINER_EXITED=true
+ break
+ fi
+ DRAIN_LINE=$(grep "\[drain\]" "$LOG_FILE" 2>/dev/null | tail -1)
+ echo " $(date +%H:%M:%S) ${DRAIN_LINE:0:100}"
+done
+
+# ── Step 6: Verify ───────────────────────────────────────────────────────
+echo ""
+info "=== DRAIN LOG ==="
+grep -E "\[drain\]|handleIdleEvent.*(idle timeout fired)|agent\.(exit|start)|Drain complete" "$LOG_FILE" 2>/dev/null || true
+
+echo ""
+
+if ! $CONTAINER_EXITED; then
+ fail "Container did not exit within 10 minutes"
+fi
+
+# Check for drain completion
+if grep -q "Drain complete" "$LOG_FILE" 2>/dev/null; then
+ info "Drain completed successfully"
+else
+ fail "Drain did not complete (no 'Drain complete' in logs)"
+fi
+
+# Check Phase 1 succeeded
+if grep -q "Phase 1: TownDO responded 200" "$LOG_FILE" 2>/dev/null; then
+ info "Phase 1: TownDO notified OK"
+else
+ info "WARN: Phase 1 TownDO notification may have failed"
+fi
+
+# Check for force-save (Phase 3 stragglers)
+STRAGGLERS=$(grep -c "Phase 3: force-saving" "$LOG_FILE" 2>/dev/null || echo "0")
+MAYOR_ONLY=$(grep -c "Phase 3: froze agent.*mayor\|Phase 3: force-saving.*mayor" "$LOG_FILE" 2>/dev/null || echo "0")
+NON_MAYOR_STRAGGLERS=$((STRAGGLERS - MAYOR_ONLY))
+if [ "$NON_MAYOR_STRAGGLERS" -gt 0 ]; then
+ info "WARN: $NON_MAYOR_STRAGGLERS non-mayor agent(s) were force-saved (did not exit cleanly)"
+else
+ info "All non-mayor agents exited cleanly (no force-save needed)"
+fi
+
+# Wait for drain flag to clear
+info "Waiting for drain flag to clear..."
+sleep 15
+STILL_DRAINING=$(curl -sf "$BASE/debug/towns/$TOWN_ID/drain-status" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('draining', True))" 2>/dev/null || echo "unknown")
+if [ "$STILL_DRAINING" = "False" ]; then
+ info "Drain flag cleared"
+elif [ "$STILL_DRAINING" = "unknown" ]; then
+ info "WARN: Could not check drain status (wrangler may have restarted)"
+else
+ info "WARN: Drain flag still set (will clear on next heartbeat from new container)"
+fi
+
+echo ""
+info "=== RESULT: PASS ==="
+info "Drain completed in ${DRAIN_SECS:-?}s, container exited, log at $LOG_FILE"
From bf7a035070a00ddc0ebf1b51c41342250503a193 Mon Sep 17 00:00:00 2001
From: John Fawcett
Date: Fri, 3 Apr 2026 14:16:19 -0500
Subject: [PATCH 02/14] fix(gastown): refresh container token when mayor is
alive but waiting (#1979)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fix(gastown): also refresh container token when mayor is alive (waiting)
Refine the refreshContainerToken guard to check for an alive mayor
(working/stalled/waiting) in addition to hasActiveWork(). A waiting
mayor is alive in the container but hasActiveWork() returns false —
without this fix the 8h container token can expire, stranding GT tool
calls when the user sends the next message via sendMayorMessage
(which reuses the container without calling ensureContainerToken).
Co-authored-by: John Fawcett
---
cloudflare-gastown/src/dos/Town.do.ts | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/cloudflare-gastown/src/dos/Town.do.ts b/cloudflare-gastown/src/dos/Town.do.ts
index d1172b5e2..87b9496ae 100644
--- a/cloudflare-gastown/src/dos/Town.do.ts
+++ b/cloudflare-gastown/src/dos/Town.do.ts
@@ -3317,12 +3317,11 @@ export class TownDO extends DurableObject {
});
}
- // Refresh the container-scoped JWT before any work that might
- // trigger API calls. Throttled to once per hour (tokens have 8h
- // expiry, so hourly refresh provides ample safety margin).
- // Gated on hasRigs (not hasActiveWork) because the container may
- // still be running with an idle mayor accepting user messages,
- // even when there are no active beads or agents.
+ // Refresh the container-scoped JWT. Throttled to once per hour (tokens
+ // have 8h expiry). Skips when no active work AND no alive mayor — the
+ // container is sleeping and the token will be refreshed at dispatch time.
+ // Keeps refreshing for waiting mayors since sendMayorMessage reuses the
+ // container without calling ensureContainerToken.
try {
await this.refreshContainerToken();
} catch (err) {
@@ -3647,6 +3646,19 @@ export class TownDO extends DurableObject {
* requests that reset the container's sleepAfter timer (#1409).
*/
private async refreshContainerToken(): Promise {
+ // Skip if no active work AND no alive mayor — the container is sleeping
+ // and doesn't need a fresh token. The token will be refreshed when work
+ // is next dispatched (ensureContainerToken is called in
+ // startAgentInContainer at container-dispatch.ts:329).
+ // However, a waiting mayor IS alive in the container and needs a valid
+ // token for GT tool calls when the user sends the next message
+ // (sendMayorMessage → sendMessageToAgent does NOT call ensureContainerToken).
+ const mayor = agents.listAgents(this.sql, { role: 'mayor' })[0] ?? null;
+ const mayorAlive =
+ mayor &&
+ (mayor.status === 'working' || mayor.status === 'stalled' || mayor.status === 'waiting');
+ if (!this.hasActiveWork() && !mayorAlive) return;
+
const TOKEN_REFRESH_INTERVAL_MS = 60 * 60_000; // 1 hour
const now = Date.now();
const lastRefresh = (await this.ctx.storage.get('container:lastTokenRefreshAt')) ?? 0;
From 7fd0e5eaad9f1de0f2c140b7eec11f9e56af66b5 Mon Sep 17 00:00:00 2001
From: John Fawcett
Date: Fri, 3 Apr 2026 15:41:10 -0500
Subject: [PATCH 03/14] feat(gastown): skip review queue for gt:pr-fixup beads
(#1985)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(gastown): skip review queue for gt:pr-fixup beads (#1983)
* feat(gastown): skip review queue for gt:pr-fixup beads in agentDone()
* style: fix formatting in local-debug-testing.md
---------
Co-authored-by: John Fawcett
* feat(gastown): thread labels parameter through gt_sling tool chain
Add optional labels[] parameter to gt_sling so the mayor can tag beads
at creation time. The parameter flows through:
mayor-tools.ts → client.ts → mayor-tools.handler.ts → Town.do.ts slingBead() → createBead()
createBead() already accepts labels, so no further changes needed.
* feat(gastown): add pr_fixup_context to PrimeContext and buildPrimeContext
Add pr_fixup_context field to PrimeContext type and populate it in
buildPrimeContext when the hooked bead has the gt:pr-fixup label,
mirroring the existing rework_context pattern. Extracts pr_url, branch,
and target_branch from bead metadata.
---------
Co-authored-by: John Fawcett
---
cloudflare-gastown/container/plugin/client.ts | 1 +
cloudflare-gastown/container/plugin/mayor-tools.ts | 5 +++++
cloudflare-gastown/docs/local-debug-testing.md | 6 ++++++
cloudflare-gastown/src/dos/Town.do.ts | 2 ++
cloudflare-gastown/src/dos/town/agents.ts | 12 ++++++++++++
cloudflare-gastown/src/dos/town/review-queue.ts | 11 +++++++++++
.../src/handlers/mayor-tools.handler.ts | 1 +
cloudflare-gastown/src/types.ts | 6 ++++++
8 files changed, 44 insertions(+)
diff --git a/cloudflare-gastown/container/plugin/client.ts b/cloudflare-gastown/container/plugin/client.ts
index 93b972854..6a222b7b4 100644
--- a/cloudflare-gastown/container/plugin/client.ts
+++ b/cloudflare-gastown/container/plugin/client.ts
@@ -310,6 +310,7 @@ export class MayorGastownClient {
title: string;
body?: string;
metadata?: Record;
+ labels?: string[];
}): Promise {
return this.request(this.mayorPath('/sling'), {
method: 'POST',
diff --git a/cloudflare-gastown/container/plugin/mayor-tools.ts b/cloudflare-gastown/container/plugin/mayor-tools.ts
index e8c9929de..48ad58644 100644
--- a/cloudflare-gastown/container/plugin/mayor-tools.ts
+++ b/cloudflare-gastown/container/plugin/mayor-tools.ts
@@ -67,6 +67,10 @@ export function createMayorTools(client: MayorGastownClient) {
.string()
.describe('JSON-encoded metadata object for additional context')
.optional(),
+ labels: tool.schema
+ .array(tool.schema.string())
+ .describe('Labels to attach to the bead (e.g. ["gt:pr-fixup"])')
+ .optional(),
},
async execute(args) {
const metadata = args.metadata ? parseJsonObject(args.metadata, 'metadata') : undefined;
@@ -75,6 +79,7 @@ export function createMayorTools(client: MayorGastownClient) {
title: args.title,
body: args.body,
metadata,
+ labels: args.labels,
});
return [
`Task slung successfully.`,
diff --git a/cloudflare-gastown/docs/local-debug-testing.md b/cloudflare-gastown/docs/local-debug-testing.md
index 7ea1af6cc..7d795592f 100644
--- a/cloudflare-gastown/docs/local-debug-testing.md
+++ b/cloudflare-gastown/docs/local-debug-testing.md
@@ -249,6 +249,7 @@ grep "container\|startAgent\|eviction" /tmp/gastown-wrangler.log | tail -20
### Drain Flag Clearing
The TownDO's `_draining` flag is cleared by whichever happens first:
+
- **Heartbeat instance ID change** (~30s): each container has a UUID. When a new container's heartbeat arrives with a different ID, the drain clears.
- **Nonce handshake**: the new container calls `/container-ready` with the drain nonce.
- **Hard timeout** (7 min): safety net if no heartbeat or handshake arrives.
@@ -276,6 +277,7 @@ docker kill $(docker ps -q) 2>/dev/null
### Drain Stuck "Waiting for N agents"
Agents show as `running` but aren't doing work. Common causes:
+
- **`fetchPendingNudges` hanging**: should be skipped during drain (check `_draining` flag)
- **`server.heartbeat` clearing idle timer**: these events should be in `IDLE_TIMER_IGNORE_EVENTS`
- **Agent in `starting` status**: `session.prompt()` blocking. Status is set to `running` before the prompt now.
@@ -283,6 +285,7 @@ Agents show as `running` but aren't doing work. Common causes:
### Drain Flag Persists After Restart
The drain banner stays visible after the container restarted. Causes:
+
- **No heartbeats arriving**: container failed to start, no agents registered
- **`_containerInstanceId` not persisted**: should be in `ctx.storage`
- Fallback: wait for the 7-minute hard timeout
@@ -290,14 +293,17 @@ The drain banner stays visible after the container restarted. Causes:
### Git Credential Errors
Container starts but agents fail at `git clone`:
+
```
Error checking if container is ready: Invalid username
```
+
This means the git token is stale/expired. Refresh credentials in the town settings.
### Accumulating Escalation Beads
Triage/escalation beads pile up with `rig_id=NULL`. These are by design:
+
- `type=escalation` beads surface for human attention (merge conflicts, rework)
- `type=issue` triage beads are handled by `maybeDispatchTriageAgent`
- GUPP force-stop beads are created by the patrol system for stuck agents
diff --git a/cloudflare-gastown/src/dos/Town.do.ts b/cloudflare-gastown/src/dos/Town.do.ts
index 87b9496ae..1cf66dc2f 100644
--- a/cloudflare-gastown/src/dos/Town.do.ts
+++ b/cloudflare-gastown/src/dos/Town.do.ts
@@ -2074,6 +2074,7 @@ export class TownDO extends DurableObject {
body?: string;
priority?: string;
metadata?: Record;
+ labels?: string[];
}): Promise<{ bead: Bead; agent: Agent }> {
const createdBead = beadOps.createBead(this.sql, {
type: 'issue',
@@ -2082,6 +2083,7 @@ export class TownDO extends DurableObject {
priority: BeadPriority.catch('medium').parse(input.priority ?? 'medium'),
rig_id: input.rigId,
metadata: input.metadata,
+ labels: input.labels,
});
events.insertEvent(this.sql, 'bead_created', {
diff --git a/cloudflare-gastown/src/dos/town/agents.ts b/cloudflare-gastown/src/dos/town/agents.ts
index a723ce6d9..21c12673e 100644
--- a/cloudflare-gastown/src/dos/town/agents.ts
+++ b/cloudflare-gastown/src/dos/town/agents.ts
@@ -493,12 +493,24 @@ export function prime(sql: SqlStorage, agentId: string): PrimeContext {
};
}
+ // Build PR fixup context if the hooked bead is a PR fixup request
+ let pr_fixup_context: PrimeContext['pr_fixup_context'] = null;
+ if (hookedBead?.labels.includes('gt:pr-fixup') && hookedBead.metadata) {
+ const meta = hookedBead.metadata as Record;
+ pr_fixup_context = {
+ pr_url: typeof meta.pr_url === 'string' ? meta.pr_url : null,
+ branch: typeof meta.branch === 'string' ? meta.branch : null,
+ target_branch: typeof meta.target_branch === 'string' ? meta.target_branch : null,
+ };
+ }
+
return {
agent,
hooked_bead: hookedBead,
undelivered_mail: undeliveredMail,
open_beads: openBeads,
rework_context,
+ pr_fixup_context,
};
}
diff --git a/cloudflare-gastown/src/dos/town/review-queue.ts b/cloudflare-gastown/src/dos/town/review-queue.ts
index 44c7e26bf..73cf004a2 100644
--- a/cloudflare-gastown/src/dos/town/review-queue.ts
+++ b/cloudflare-gastown/src/dos/town/review-queue.ts
@@ -577,6 +577,17 @@ export function agentDone(sql: SqlStorage, agentId: string, input: AgentDoneInpu
return;
}
+ // PR-fixup beads skip the review queue. The polecat pushed fixup commits
+ // to an existing PR branch — no separate review is needed.
+ if (hookedBead?.labels.includes('gt:pr-fixup')) {
+ console.log(
+ `[review-queue] agentDone: pr-fixup bead ${agent.current_hook_bead_id} — closing directly (skip review)`
+ );
+ closeBead(sql, agent.current_hook_bead_id, agentId);
+ unhookBead(sql, agentId);
+ return;
+ }
+
if (agent.role === 'refinery') {
// The refinery handles merging (direct strategy) or PR creation (pr strategy)
// itself. When it calls gt_done:
diff --git a/cloudflare-gastown/src/handlers/mayor-tools.handler.ts b/cloudflare-gastown/src/handlers/mayor-tools.handler.ts
index 6f778d45d..fcfe207ce 100644
--- a/cloudflare-gastown/src/handlers/mayor-tools.handler.ts
+++ b/cloudflare-gastown/src/handlers/mayor-tools.handler.ts
@@ -24,6 +24,7 @@ const MayorSlingBody = z.object({
title: z.string().min(1),
body: z.string().optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
+ labels: z.array(z.string()).optional(),
});
const MayorSlingBatchBody = z
diff --git a/cloudflare-gastown/src/types.ts b/cloudflare-gastown/src/types.ts
index 245097035..2c0844ecf 100644
--- a/cloudflare-gastown/src/types.ts
+++ b/cloudflare-gastown/src/types.ts
@@ -171,6 +171,12 @@ export type PrimeContext = {
original_bead_title: string | null;
mr_bead_id: string | null;
} | null;
+ /** Present when the hooked bead is a PR fixup (gt:pr-fixup label). */
+ pr_fixup_context: {
+ pr_url: string | null;
+ branch: string | null;
+ target_branch: string | null;
+ } | null;
};
// -- Agent done --
From 0349fccfaa82e7b5072b860a4d18b2efbcb3203e Mon Sep 17 00:00:00 2001
From: John Fawcett
Date: Fri, 3 Apr 2026 15:50:39 -0500
Subject: [PATCH 04/14] feat(container): expand apt-get block with build tools,
ripgrep, and dev libraries (#1978)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(container): expand apt-get block with build tools, ripgrep, and dev libraries
Add comprehensive package list to both Dockerfile and Dockerfile.dev:
- Build toolchain: build-essential, autoconf
- Search tools: ripgrep, jq
- Network: wget, gnupg, unzip
- Compression: bzip2, zstd
- SSL/crypto: libssl-dev, libffi-dev
- Database client libs: libdb-dev, libgdbm-dev, libgdbm6
- Python build deps: libbz2-dev, liblzma-dev, libncurses5-dev, libreadline-dev, zlib1g-dev
- Ruby build deps: libyaml-dev
- Image processing: libvips-dev
- Browser/rendering: libgbm1
- Native addon support: libc++1, libgmp-dev
- Timezone data: tzdata
Closes #1976
* fix(container): remove shell comments from inside backslash-continued apt-get install
Shell comments inside backslash-continued lines break the apt-get
install command — the # starts a comment that swallows the rest of
the logical line, dropping all subsequent packages. Move category
info to a block comment above the RUN statement instead.
---------
Co-authored-by: John Fawcett
---
cloudflare-gastown/container/Dockerfile | 68 +++++++++++++++++----
cloudflare-gastown/container/Dockerfile.dev | 68 +++++++++++++++++----
2 files changed, 112 insertions(+), 24 deletions(-)
diff --git a/cloudflare-gastown/container/Dockerfile b/cloudflare-gastown/container/Dockerfile
index 39b2235c5..b835a3332 100644
--- a/cloudflare-gastown/container/Dockerfile
+++ b/cloudflare-gastown/container/Dockerfile
@@ -1,18 +1,62 @@
FROM oven/bun:1-slim
-# Install git, gh CLI, and Node.js (required by @kilocode/cli which uses #!/usr/bin/env node)
+# Install dev toolchain, search tools, build deps, gh CLI, and Node.js
+# Package categories:
+# version control: git, git-lfs
+# network/download: curl, wget, ca-certificates, gnupg, unzip
+# build toolchain: build-essential, autoconf
+# search tools: ripgrep, jq
+# compression: bzip2, zstd
+# SSL/crypto: libssl-dev, libffi-dev
+# database client libs: libdb-dev, libgdbm-dev, libgdbm6
+# Python build deps: libbz2-dev, liblzma-dev, libncurses5-dev, libreadline-dev, zlib1g-dev
+# Ruby build deps: libyaml-dev
+# image processing: libvips-dev
+# browser/rendering: libgbm1
+# C++ stdlib: libc++1
+# math: libgmp-dev
+# timezone data: tzdata
RUN apt-get update && \
- apt-get install -y --no-install-recommends git git-lfs curl ca-certificates && \
- curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \
- apt-get install -y --no-install-recommends nodejs && \
- curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
- -o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
- > /etc/apt/sources.list.d/github-cli.list && \
- apt-get update && \
- apt-get install -y --no-install-recommends gh && \
- apt-get clean && \
- rm -rf /var/lib/apt/lists/*
+ apt-get install -y --no-install-recommends \
+ git \
+ git-lfs \
+ curl \
+ wget \
+ ca-certificates \
+ gnupg \
+ unzip \
+ build-essential \
+ autoconf \
+ ripgrep \
+ jq \
+ bzip2 \
+ zstd \
+ libssl-dev \
+ libffi-dev \
+ libdb-dev \
+ libgdbm-dev \
+ libgdbm6 \
+ libbz2-dev \
+ liblzma-dev \
+ libncurses5-dev \
+ libreadline-dev \
+ zlib1g-dev \
+ libyaml-dev \
+ libvips-dev \
+ libgbm1 \
+ libc++1 \
+ libgmp-dev \
+ tzdata \
+ && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
+ && apt-get install -y --no-install-recommends nodejs \
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
+ -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
+ > /etc/apt/sources.list.d/github-cli.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends gh \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
RUN git lfs install --system
diff --git a/cloudflare-gastown/container/Dockerfile.dev b/cloudflare-gastown/container/Dockerfile.dev
index 474ef2315..a4bebc5db 100644
--- a/cloudflare-gastown/container/Dockerfile.dev
+++ b/cloudflare-gastown/container/Dockerfile.dev
@@ -1,18 +1,62 @@
FROM --platform=linux/arm64 oven/bun:1-slim
-# Install git, gh CLI, and Node.js (required by @kilocode/cli which uses #!/usr/bin/env node)
+# Install dev toolchain, search tools, build deps, gh CLI, and Node.js
+# Package categories:
+# version control: git, git-lfs
+# network/download: curl, wget, ca-certificates, gnupg, unzip
+# build toolchain: build-essential, autoconf
+# search tools: ripgrep, jq
+# compression: bzip2, zstd
+# SSL/crypto: libssl-dev, libffi-dev
+# database client libs: libdb-dev, libgdbm-dev, libgdbm6
+# Python build deps: libbz2-dev, liblzma-dev, libncurses5-dev, libreadline-dev, zlib1g-dev
+# Ruby build deps: libyaml-dev
+# image processing: libvips-dev
+# browser/rendering: libgbm1
+# C++ stdlib: libc++1
+# math: libgmp-dev
+# timezone data: tzdata
RUN apt-get update && \
- apt-get install -y --no-install-recommends git git-lfs curl ca-certificates && \
- curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \
- apt-get install -y --no-install-recommends nodejs && \
- curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
- -o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
- > /etc/apt/sources.list.d/github-cli.list && \
- apt-get update && \
- apt-get install -y --no-install-recommends gh && \
- apt-get clean && \
- rm -rf /var/lib/apt/lists/*
+ apt-get install -y --no-install-recommends \
+ git \
+ git-lfs \
+ curl \
+ wget \
+ ca-certificates \
+ gnupg \
+ unzip \
+ build-essential \
+ autoconf \
+ ripgrep \
+ jq \
+ bzip2 \
+ zstd \
+ libssl-dev \
+ libffi-dev \
+ libdb-dev \
+ libgdbm-dev \
+ libgdbm6 \
+ libbz2-dev \
+ liblzma-dev \
+ libncurses5-dev \
+ libreadline-dev \
+ zlib1g-dev \
+ libyaml-dev \
+ libvips-dev \
+ libgbm1 \
+ libc++1 \
+ libgmp-dev \
+ tzdata \
+ && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
+ && apt-get install -y --no-install-recommends nodejs \
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
+ -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
+ > /etc/apt/sources.list.d/github-cli.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends gh \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
RUN git lfs install --system
From ca6e7fb9018e88d4c7d97c129954a2d8289e695a Mon Sep 17 00:00:00 2001
From: John Fawcett
Date: Fri, 3 Apr 2026 19:18:07 -0500
Subject: [PATCH 05/14] feat(gastown): polecat creates PRs, refinery reviews
via GitHub, code_review toggle (#1586)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(gastown): auto-resolve PR feedback and auto-merge with grace period
Implements the core infrastructure for automatically addressing unresolved
review comments and failing CI checks on PRs, plus configurable auto-merge
with a grace period timer.
Closes #1002
* fix(gastown): address PR review feedback — pagination safety, merge failure recovery, repo validation
- GraphQL reviewThreads query now includes pageInfo.hasNextPage; if there
are more than 100 threads, conservatively treats PR as having unresolved
comments to prevent auto-merging with unchecked feedback
- merge_pr action clears auto_merge_pending flag on failure (405/409/error)
and resets the auto_merge_ready_since timer so normal polling resumes
- reconcileReviewQueue now emits poll_pr alongside merge_pr so PR status
changes (merged/closed by human) are still observed during merge attempts
- merge_pr validates the PR URL's owner/repo against the rig's git_url
before calling the merge API, preventing merges targeting unrelated repos
* fix(gastown): check-runs pagination and fresh feedback check before merge
- check-runs API now uses per_page=100 and compares total_count to
detect unpaginated runs; if more runs exist than returned,
allChecksPass is conservatively set to false
- merge_pr re-checks feedback (comments + CI) immediately before
calling the merge API; if state regressed since the last poll,
clears auto_merge_pending and resets the timer
* fix(gastown): conservative hasFailingChecks for unpaginated check-runs, fix CI failures
- hasFailingChecks is now conservatively true when total_count > returned
runs, so PRs with >100 check-runs correctly trigger feedback handling
- Replace `as` casts with Zod safeParse for GitHub API responses
(checkPRFeedback) to satisfy oxlint no-unnecessary-type-assertion
- Fix test constant expressions flagged by TS2871 and oxlint
- Run oxfmt on changed files
* fix(gastown): separate hasUncheckedRuns from hasFailingChecks in PR feedback
hasFailingChecks now only reflects actual failures from inspected runs.
A new hasUncheckedRuns field tracks when the check-runs response was
paginated. allChecksPass is still conservatively false when runs are
unchecked (blocking auto-merge), but the feedback bead is no longer
dispatched for phantom CI failures on repos with >100 passing checks.
* style: auto-format local-debug-testing.md
* feat(gastown): polecat creates PRs, refinery reviews via GitHub, code_review toggle
- Polecat creates PRs directly when merge_strategy is 'pr', passing
pr_url to gt_done. Removes a full agent dispatch cycle from the flow.
- Refinery reviews existing PRs via gh pr review, adding inline comments
that go through the same auto-resolve feedback loop as human reviews.
- New refinery.code_review toggle (default: true) lets users disable
refinery code review when they have an external review bot.
- Fix: allChecksPass treats 0 check-runs as passing for repos without CI.
- Fix: reconciler Rule 7 stops refineries hooked to terminal MR beads,
preventing post-merge review comments.
- Convoy-aware: review-then-land intermediate beads skip PR creation.
* fix(gastown): include hasUncheckedRuns in pr_feedback_detected condition
When check-runs are paginated (>100), the first page may all pass
but later pages may have failures. Previously only hasFailingChecks
was checked, which only reflects the first page. Now hasUncheckedRuns
also triggers feedback detection so the auto-resolve path can fix CI.
* fix(gastown): include auto_merge columns in REVIEW_JOIN query
REVIEW_JOIN was missing auto_merge_ready_since and
last_feedback_check_at from the SELECT, causing MergeRequestBeadRecord
parse failures on pr_status_changed events.
* fix(gastown): refinery uses gh pr comment instead of --approve
The bot account that created the PR cannot approve its own PRs.
The refinery now posts a comment when the review passes instead
of using gh pr review --approve.
* fix(gastown): set parent_bead_id on rework and PR feedback beads
Rework beads (from refinery review) and PR feedback beads (from
poll_pr comment detection) now set parent_bead_id to the MR bead
that triggered them. This links them into the bead chain so the
relationship is visible in the UI and queryable via parent traversal.
* fix(gastown): skip orphaned-review timeout when code_review disabled, fix inline comment syntax
- Rule 4 orphaned-review timeout now skips when refineryCodeReview is
false — poll_pr keeps MR beads alive, no refinery expected.
- Removed invalid 'gh pr review --comment path:line' syntax from
refinery prompt; consolidated on the GitHub API example which
correctly creates inline review threads.
---
.gitignore | 3 +
.../docs/e2e-pr-feedback-testing.md | 382 ++
.../src/db/tables/review-metadata.table.ts | 15 +
.../src/db/tables/town-events.table.ts | 2 +
cloudflare-gastown/src/dos/Town.do.ts | 263 +-
cloudflare-gastown/src/dos/town/actions.ts | 331 ++
cloudflare-gastown/src/dos/town/beads.ts | 13 +-
cloudflare-gastown/src/dos/town/config.ts | 9 +
.../src/dos/town/pr-feedback.test.ts | 219 +
cloudflare-gastown/src/dos/town/reconciler.ts | 266 +-
.../src/dos/town/review-queue.ts | 4 +-
.../src/prompts/polecat-system.prompt.test.ts | 36 +
.../src/prompts/polecat-system.prompt.ts | 33 +-
.../src/prompts/refinery-system.prompt.ts | 104 +-
cloudflare-gastown/src/types.ts | 13 +
.../settings/TownSettingsPageClient.tsx | 85 +
src/lib/gastown/types/router.d.ts | 4069 ++++++++---------
src/lib/gastown/types/schemas.d.ts | 2176 ++++-----
src/routers/admin/gastown-router.ts | 3 +
19 files changed, 4547 insertions(+), 3479 deletions(-)
create mode 100644 cloudflare-gastown/docs/e2e-pr-feedback-testing.md
create mode 100644 cloudflare-gastown/src/dos/town/pr-feedback.test.ts
diff --git a/.gitignore b/.gitignore
index 71f605ef7..e08af3592 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,6 +102,9 @@ supabase/.temp
.kilo/plans
+# ESLint cache
+.eslintcache
+
# @kilocode/trpc build output (rebuilt by: pnpm --filter @kilocode/trpc run build)
packages/trpc/dist/
.plan/
diff --git a/cloudflare-gastown/docs/e2e-pr-feedback-testing.md b/cloudflare-gastown/docs/e2e-pr-feedback-testing.md
new file mode 100644
index 000000000..4b684d32d
--- /dev/null
+++ b/cloudflare-gastown/docs/e2e-pr-feedback-testing.md
@@ -0,0 +1,382 @@
+# E2E Local Testing: PR Feedback Auto-Resolve & Auto-Merge
+
+Guide for an AI agent to test the PR feedback auto-resolve and auto-merge feature locally. Covers the full lifecycle for both single beads and convoys.
+
+## Architecture Overview
+
+When `merge_strategy: 'pr'` is configured:
+
+1. **Polecat creates the PR** — pushes branch, runs `gh pr create`, passes `pr_url` to `gt_done`
+2. **Refinery reviews the existing PR** — runs quality gates, reviews diff, adds GitHub review comments (approve or request changes)
+3. **Auto-resolve detects comments** — `poll_pr` checks for unresolved review threads, dispatches polecat to fix
+4. **Auto-merge** — once all comments resolved and CI passes, grace period timer starts, then PR is merged via API
+
+## Prerequisites
+
+- Wrangler dev server running for gastown (`pnpm dev` in `cloudflare-gastown/`, port 8803)
+- Docker running (containers are managed by wrangler's container runtime)
+- A town with an active container and at least one rig configured with a GitHub repo
+- `gh` CLI authenticated (for adding PR comments and verifying merges)
+
+## Quick Reference
+
+```bash
+BASE=http://localhost:8803
+TOWN_ID="${TOWN_ID:-a093a551-ff4d-4c36-9274-252df66128fd}"
+RIG_ID="${RIG_ID:-mega-todo-app5}"
+REPO="${REPO:-jrf0110/mega-todo-app5}"
+```
+
+## 1. Verify Town Settings
+
+Check these settings in the town settings UI:
+
+| Setting | Required Value |
+|---|---|
+| Merge strategy | `pr` (Pull Request) |
+| Auto-merge | enabled |
+| Auto-resolve PR feedback | enabled |
+| Auto-merge delay | 2 minutes (or preferred delay) |
+
+### Verify Clean State
+
+```bash
+curl -s $BASE/debug/towns/$TOWN_ID/status | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+alarm = d.get('alarmStatus', {})
+print('Agents:', json.dumps(alarm.get('agents', {})))
+print('Beads:', json.dumps(alarm.get('beads', {})))
+summary = d.get('beadSummary', [])
+if summary:
+ print(f'WARNING: {len(summary)} non-terminal bead(s)')
+ for b in summary:
+ print(f' {b.get(\"type\",\"?\"):16s} {b.get(\"status\",\"?\"):12s} {str(b.get(\"title\",\"\"))[:60]}')
+else:
+ print('Clean state.')
+"
+```
+
+---
+
+## Test A: Single Bead Flow
+
+### A.1. Send Work to the Mayor
+
+```bash
+curl -s -m 120 -X POST $BASE/debug/towns/$TOWN_ID/send-message \
+ -H "Content-Type: application/json" \
+ -d "{\"message\": \"Create a bead for this task on the $RIG_ID rig: Add a new utility file src/utils/string-helpers.ts with 5 string utility functions (capitalize, truncate, slugify, camelToKebab, kebabToCamel). Each function should have JSDoc comments. Commit and push when done.\"}"
+```
+
+### A.2. Wait for Polecat to Create PR
+
+The polecat now creates the PR itself. Poll until the MR bead appears with a PR URL:
+
+```bash
+for i in $(seq 1 60); do
+ STATUS=$(curl -s $BASE/debug/towns/$TOWN_ID/status)
+ echo "$(date +%H:%M:%S)"
+ echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for b in d.get('beadSummary', []):
+ btype = b.get('type', '?')
+ if btype in ('issue', 'merge_request'):
+ print(f' {btype:16s} {b.get(\"status\",\"?\"):12s} {str(b.get(\"title\",\"\"))[:55]}')
+for am in d.get('agentMeta', []):
+ if am.get('status') != 'idle':
+ hook = str(am.get('current_hook_bead_id', 'NULL') or 'NULL')[:12]
+ print(f' {am.get(\"role\",\"?\"):12s} status={am.get(\"status\",\"?\"):10s} hook={hook}')
+for e in d.get('alarmStatus', {}).get('recentEvents', [])[:3]:
+ t = e.get('type', '')
+ if 'pr_' in t or 'created' in t or 'review' in t:
+ print(f' EVT: {t:20s} {e.get(\"message\",\"\")[:60]}')
+" 2>/dev/null
+ MR_READY=$(echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for b in d.get('beadSummary', []):
+ if b.get('type') == 'merge_request':
+ print('MR_EXISTS')
+ break
+" 2>/dev/null)
+ if [ "$MR_READY" = "MR_EXISTS" ]; then echo "=== MR BEAD CREATED (polecat created PR) ==="; break; fi
+ sleep 15
+done
+```
+
+**Expected:** The polecat creates the PR and calls `gt_done(branch, pr_url)`. The MR bead appears as `open`.
+
+### A.3. Verify PR Exists on GitHub
+
+```bash
+gh pr list --repo $REPO --state open --limit 5 --json number,title,headRefName,createdAt
+```
+
+Record the PR number:
+```bash
+PR_NUMBER=
+```
+
+### A.4. Wait for Refinery Review
+
+The refinery is dispatched to review the existing PR. It runs quality gates, reviews the diff, and adds review comments. Watch for the refinery to complete:
+
+```bash
+for i in $(seq 1 60); do
+ STATUS=$(curl -s $BASE/debug/towns/$TOWN_ID/status)
+ echo "$(date +%H:%M:%S)"
+ echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for b in d.get('beadSummary', []):
+ if b.get('type') == 'merge_request':
+ print(f' MR: status={b.get(\"status\",\"?\"):12s} {str(b.get(\"title\",\"\"))[:50]}')
+for am in d.get('agentMeta', []):
+ if am.get('role') == 'refinery' and am.get('status') != 'idle':
+ print(f' refinery: status={am.get(\"status\",\"?\"):10s}')
+for e in d.get('alarmStatus', {}).get('recentEvents', [])[:3]:
+ t = e.get('type', '')
+ if 'pr_' in t or 'review' in t:
+ print(f' EVT: {t:20s} {e.get(\"message\",\"\")[:60]}')
+" 2>/dev/null
+ # MR in_progress means refinery called gt_done with pr_url
+ IN_PROGRESS=$(echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for b in d.get('beadSummary', []):
+ if b.get('type') == 'merge_request' and b.get('status') == 'in_progress':
+ print('yes')
+" 2>/dev/null)
+ if [ "$IN_PROGRESS" = "yes" ]; then echo "=== REFINERY DONE — MR in_progress, poll_pr active ==="; break; fi
+ sleep 15
+done
+```
+
+### A.5. Check for Refinery Comments (Optional)
+
+If the refinery requested changes, an auto-resolve cycle will begin automatically. Check:
+
+```bash
+gh api graphql -f query='query {
+ repository(owner: "'$(echo $REPO | cut -d/ -f1)'", name: "'$(echo $REPO | cut -d/ -f2)'") {
+ pullRequest(number: '$PR_NUMBER') {
+ reviewThreads(first: 100) {
+ nodes { isResolved, comments(first: 1) { nodes { body, author { login } } } }
+ }
+ }
+ }
+}'
+```
+
+### A.6. Add a Human Review Comment
+
+To test the human feedback loop, add a review with inline comments:
+
+```bash
+gh api repos/$REPO/pulls/$PR_NUMBER/reviews \
+ --method POST \
+ --input - <<'EOF'
+{
+ "event": "REQUEST_CHANGES",
+ "body": "The capitalize function needs input validation.",
+ "comments": [
+ {
+ "path": "src/utils/string-helpers.ts",
+ "position": 5,
+ "body": "Please add input validation - handle empty strings and non-string inputs gracefully."
+ }
+ ]
+}
+EOF
+```
+
+**Note:** You must use inline comments (with `path` and `position`) to create review threads. The `checkPRFeedback` function detects **unresolved review threads** via GitHub GraphQL, not review state.
+
+### A.7. Observe Feedback Detection and Resolution
+
+```bash
+for i in $(seq 1 60); do
+ STATUS=$(curl -s $BASE/debug/towns/$TOWN_ID/status)
+ echo "$(date +%H:%M:%S)"
+ echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+for b in d.get('beadSummary', []):
+ title = str(b.get('title', ''))
+ if b.get('type') in ('issue', 'merge_request'):
+ marker = ' <-- FEEDBACK' if ('Address' in title or 'PR #' in title) else ''
+ print(f' {b.get(\"type\",\"?\"):16s} {b.get(\"status\",\"?\"):12s} {title[:50]}{marker}')
+for am in d.get('agentMeta', []):
+ if am.get('status') != 'idle':
+ print(f' {am.get(\"role\",\"?\"):12s} status={am.get(\"status\",\"?\"):10s}')
+" 2>/dev/null
+ sleep 10
+done
+```
+
+### A.8. Wait for Auto-Merge
+
+After all review threads are resolved and CI passes, the auto-merge timer starts (configured delay, e.g. 2 minutes). Monitor until all beads close:
+
+```bash
+echo "Waiting for auto-merge..."
+MERGE_START=$(date +%s)
+for i in $(seq 1 60); do
+ STATUS=$(curl -s $BASE/debug/towns/$TOWN_ID/status)
+ ELAPSED=$(( $(date +%s) - MERGE_START ))
+ echo "$(date +%H:%M:%S) [${ELAPSED}s]"
+ echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+beads = d.get('beadSummary', [])
+relevant = [b for b in beads if b.get('type') in ('merge_request',) or 'string' in str(b.get('title','')).lower() or 'Address' in str(b.get('title',''))]
+if not relevant:
+ print(' ALL DONE')
+else:
+ for b in relevant:
+ print(f' {b.get(\"type\",\"?\"):16s} {b.get(\"status\",\"?\"):12s} {str(b.get(\"title\",\"\"))[:50]}')
+" 2>/dev/null
+ ALL_DONE=$(echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+beads = d.get('beadSummary', [])
+relevant = [b for b in beads if b.get('type') in ('merge_request',) or 'string' in str(b.get('title','')).lower() or 'Address' in str(b.get('title',''))]
+if not relevant: print('DONE')
+" 2>/dev/null)
+ if [ "$ALL_DONE" = "DONE" ]; then echo "=== AUTO-MERGE COMPLETE ==="; break; fi
+ sleep 10
+done
+```
+
+### A.9. Verify Merge
+
+```bash
+gh pr view $PR_NUMBER --repo $REPO --json state,mergedAt
+```
+
+---
+
+## Test B: 3-Bead Convoy Flow
+
+This tests the review-and-merge convoy mode where each bead gets its own PR, review, and auto-merge.
+
+### B.1. Send Convoy Work to the Mayor
+
+```bash
+curl -s -m 120 -X POST $BASE/debug/towns/$TOWN_ID/send-message \
+ -H "Content-Type: application/json" \
+ -d "{\"message\": \"Create a convoy of 3 beads on the $RIG_ID rig with merge mode review-and-merge. The beads should be: (1) Add src/utils/array-helpers.ts with functions: unique, flatten, chunk, zip, groupBy. (2) Add src/utils/object-helpers.ts with functions: pick, omit, deepClone, merge, hasKey. (3) Add src/utils/math-helpers.ts with functions: clamp, lerp, roundTo, sum, average. Each file should have JSDoc comments and a simple test file alongside it. Use review-and-merge mode so each bead gets its own PR.\"}"
+```
+
+### B.2. Monitor All 3 Beads
+
+Poll the status showing all beads and their progress:
+
+```bash
+for i in $(seq 1 120); do
+ STATUS=$(curl -s $BASE/debug/towns/$TOWN_ID/status)
+ echo "$(date +%H:%M:%S)"
+ echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+beads = d.get('beadSummary', [])
+for b in beads:
+ btype = b.get('type', '?')
+ if btype in ('issue', 'merge_request', 'convoy'):
+ print(f' {btype:16s} {b.get(\"status\",\"?\"):12s} {str(b.get(\"title\",\"\"))[:55]}')
+agents = d.get('agentMeta', [])
+active = [a for a in agents if a.get('status') != 'idle']
+for am in active:
+ hook = str(am.get('current_hook_bead_id', 'NULL') or 'NULL')[:8]
+ print(f' {am.get(\"role\",\"?\"):12s} status={am.get(\"status\",\"?\"):10s} hook={hook}')
+alarm = d.get('alarmStatus', {})
+recon = alarm.get('reconciler', {})
+actions = recon.get('actionsByType', {})
+if actions:
+ print(f' reconciler: {json.dumps(actions)}')
+" 2>/dev/null
+ # Check if all relevant beads are closed
+ ALL_DONE=$(echo "$STATUS" | python3 -c "
+import sys, json
+d = json.load(sys.stdin)
+beads = d.get('beadSummary', [])
+relevant = [b for b in beads if b.get('type') in ('issue', 'merge_request', 'convoy') and ('helper' in str(b.get('title','')).lower() or 'Review' in str(b.get('title','')) or 'convoy' in b.get('type',''))]
+if not relevant: print('DONE')
+" 2>/dev/null)
+ if [ "$ALL_DONE" = "DONE" ]; then echo "=== ALL CONVOY BEADS COMPLETE ==="; break; fi
+ sleep 15
+done
+```
+
+### B.3. Verify All PRs Merged
+
+```bash
+gh pr list --repo $REPO --state merged --limit 10 --json number,title,mergedAt | python3 -c "
+import sys, json
+prs = json.load(sys.stdin)
+today = '$(date -u +%Y-%m-%d)'
+for pr in prs:
+ if today in pr.get('mergedAt', ''):
+ print(f' PR #{pr[\"number\"]}: {pr[\"title\"]} (merged: {pr[\"mergedAt\"][:19]})')
+"
+```
+
+---
+
+## Expected Timeline
+
+### Single Bead
+
+| Step | Duration |
+|---|---|
+| Mayor slings bead | ~30s |
+| Polecat works + creates PR | 2-5 min |
+| Refinery reviews PR, adds comments | 2-5 min |
+| Feedback detected + polecat resolves | 2-5 min (if comments) |
+| Auto-merge grace period | 2 min (configured) |
+| **Total** | **8-17 min** |
+
+### 3-Bead Convoy (review-and-merge)
+
+| Step | Duration |
+|---|---|
+| Mayor creates convoy + 3 beads | ~1 min |
+| 3 polecats work in parallel + create PRs | 2-5 min |
+| 3 refinery reviews (sequential per rig) | 5-15 min |
+| Feedback resolution cycles | 2-5 min each (if needed) |
+| Auto-merge per PR | 2 min grace each |
+| **Total** | **15-30 min** |
+
+---
+
+## Troubleshooting
+
+### Polecat Doesn't Create PR
+
+If the polecat pushes but doesn't create a PR:
+- Check the polecat's system prompt includes "Pull Request Creation" section
+- Verify `merge_strategy` is `pr` in town settings
+- Check wrangler logs for the polecat's agent output
+
+### Refinery Tries to Create a New PR
+
+If the refinery creates a duplicate PR instead of reviewing the existing one:
+- Check that `review_metadata.pr_url` is set on the MR bead (polecat should have passed it)
+- The refinery prompt switches to "PR review mode" only when `existingPrUrl` is set
+
+### Feedback Not Detected
+
+`checkPRFeedback` checks for **unresolved review threads** via GitHub GraphQL, not review state. A `REQUEST_CHANGES` review without inline/line comments does NOT create review threads. Use reviews with `comments[].path` and `comments[].position` to create detectable threads.
+
+### Auto-Merge Stuck
+
+- `allChecksPass` requires either (a) 0 check-runs (no CI = pass) or (b) all check-runs completed successfully. If the repo has CI, all checks must pass.
+- The GitHub token in town config must be valid. Check wrangler logs for `401` errors from `checkPRStatus` or `checkPRFeedback`.
+- Check `auto_merge_delay_minutes` is set (not null) in town config.
+
+### Convoy Beads Not Dispatching
+
+- In `review-and-merge` mode, each bead is independent — no sequencing dependencies.
+- In `review-then-land` mode, beads with `blocks` dependencies wait for their predecessors. Intermediate beads do NOT create PRs (the refinery merges directly to the feature branch).
diff --git a/cloudflare-gastown/src/db/tables/review-metadata.table.ts b/cloudflare-gastown/src/db/tables/review-metadata.table.ts
index 8dab287a2..80846c41f 100644
--- a/cloudflare-gastown/src/db/tables/review-metadata.table.ts
+++ b/cloudflare-gastown/src/db/tables/review-metadata.table.ts
@@ -8,6 +8,11 @@ export const ReviewMetadataRecord = z.object({
merge_commit: z.string().nullable(),
pr_url: z.string().nullable(),
retry_count: z.number(),
+ /** Timestamp when all CI checks passed and all review threads were resolved.
+ * Used by the auto-merge timer to track grace period start. */
+ auto_merge_ready_since: z.string().nullable(),
+ /** Timestamp of the last feedback detection check to prevent duplicate dispatches. */
+ last_feedback_check_at: z.string().nullable(),
});
export type ReviewMetadataRecord = z.output;
@@ -22,5 +27,15 @@ export function createTableReviewMetadata(): string {
merge_commit: `text`,
pr_url: `text`,
retry_count: `integer default 0`,
+ auto_merge_ready_since: `text`,
+ last_feedback_check_at: `text`,
});
}
+
+/** Idempotent ALTER statements for existing databases. */
+export function migrateReviewMetadata(): string[] {
+ return [
+ `ALTER TABLE review_metadata ADD COLUMN auto_merge_ready_since text`,
+ `ALTER TABLE review_metadata ADD COLUMN last_feedback_check_at text`,
+ ];
+}
diff --git a/cloudflare-gastown/src/db/tables/town-events.table.ts b/cloudflare-gastown/src/db/tables/town-events.table.ts
index 30be09c65..436aaceca 100644
--- a/cloudflare-gastown/src/db/tables/town-events.table.ts
+++ b/cloudflare-gastown/src/db/tables/town-events.table.ts
@@ -11,6 +11,8 @@ export const TownEventType = z.enum([
'bead_cancelled',
'convoy_started',
'nudge_timeout',
+ 'pr_feedback_detected',
+ 'pr_auto_merge',
]);
export type TownEventType = z.output;
diff --git a/cloudflare-gastown/src/dos/Town.do.ts b/cloudflare-gastown/src/dos/Town.do.ts
index 1cf66dc2f..3ee49b53b 100644
--- a/cloudflare-gastown/src/dos/Town.do.ts
+++ b/cloudflare-gastown/src/dos/Town.do.ts
@@ -31,7 +31,8 @@ import * as scheduling from './town/scheduling';
import * as events from './town/events';
import * as reconciler from './town/reconciler';
import { applyAction } from './town/actions';
-import type { Action, ApplyActionContext } from './town/actions';
+import type { Action, ApplyActionContext, PRFeedbackCheckResult } from './town/actions';
+import { buildPolecatSystemPrompt } from '../prompts/polecat-system.prompt';
import { buildRefinerySystemPrompt } from '../prompts/refinery-system.prompt';
import { GitHubPRStatusSchema, GitLabMRStatusSchema } from '../util/platform-pr.util';
@@ -278,11 +279,17 @@ export class TownDO extends DurableObject {
const bead = beadOps.getBead(this.sql, beadId);
if (!agent || !bead) return false;
- // Build refinery-specific system prompt with branch/target info
let systemPromptOverride: string | undefined;
+ const townConfig = await this.getTownConfig();
+
+ // Build refinery-specific system prompt with branch/target info.
+ // When the MR bead already has a pr_url (polecat created the PR),
+ // the refinery reviews the existing PR and adds GitHub comments
+ // instead of creating a new PR.
if (agent.role === 'refinery' && bead.type === 'merge_request') {
const reviewMeta = reviewQueue.getReviewMetadata(this.sql, beadId);
- const townConfig = await this.getTownConfig();
+ const existingPrUrl =
+ typeof reviewMeta?.pr_url === 'string' ? reviewMeta.pr_url : undefined;
systemPromptOverride = buildRefinerySystemPrompt({
identity: agent.identity,
rigId,
@@ -295,9 +302,35 @@ export class TownDO extends DurableObject {
? bead.metadata.source_agent_id
: 'unknown',
mergeStrategy: townConfig.merge_strategy ?? 'direct',
+ existingPrUrl,
});
}
+ // When merge_strategy is 'pr', polecats create the PR themselves.
+ // Exception: review-then-land convoy intermediate beads merge directly
+ // into the convoy feature branch (the refinery handles that).
+ if (agent.role === 'polecat' && townConfig.merge_strategy === 'pr') {
+ const convoyId = beadOps.getConvoyForBead(this.sql, beadId);
+ const convoyMergeMode = convoyId
+ ? beadOps.getConvoyMergeMode(this.sql, convoyId)
+ : null;
+ const isReviewThenLandIntermediate =
+ convoyMergeMode === 'review-then-land' && convoyId !== beadId;
+
+ if (!isReviewThenLandIntermediate) {
+ const rig = rigs.getRig(this.sql, rigId);
+ systemPromptOverride = buildPolecatSystemPrompt({
+ agentName: agent.name,
+ rigId,
+ townId: this.townId,
+ identity: agent.identity,
+ gates: townConfig.refinery?.gates ?? [],
+ mergeStrategy: 'pr',
+ targetBranch: rig?.default_branch ?? 'main',
+ });
+ }
+ }
+
return scheduling.dispatchAgent(schedulingCtx, agent, bead, {
systemPromptOverride,
});
@@ -309,6 +342,17 @@ export class TownDO extends DurableObject {
const townConfig = await this.getTownConfig();
return this.checkPRStatus(prUrl, townConfig);
},
+ checkPRFeedback: async prUrl => {
+ const townConfig = await this.getTownConfig();
+ return this.checkPRFeedback(prUrl, townConfig);
+ },
+ mergePR: async prUrl => {
+ const townConfig = await this.getTownConfig();
+ return this.mergePR(prUrl, townConfig);
+ },
+ getTownConfig: async () => {
+ return this.getTownConfig();
+ },
queueNudge: async (agentId, message, _tier) => {
await this.queueNudge(agentId, message, {
mode: 'immediate',
@@ -1709,6 +1753,7 @@ export class TownDO extends DurableObject {
body: input.feedback,
priority: sourceBead?.priority ?? 'medium',
rig_id: mrBead.rig_id ?? undefined,
+ parent_bead_id: mrBead.bead_id,
labels: ['gt:rework'],
metadata: {
rework_for: sourceBeadId,
@@ -3460,7 +3505,11 @@ export class TownDO extends DurableObject {
// Phase 1: Reconcile — compute desired state vs actual state
const sideEffects: Array<() => Promise> = [];
try {
- const actions = reconciler.reconcile(this.sql, { draining: this._draining });
+ const townConfig = await this.getTownConfig();
+ const actions = reconciler.reconcile(this.sql, {
+ draining: this._draining,
+ refineryCodeReview: townConfig.refinery?.code_review ?? true,
+ });
metrics.actionsEmitted = actions.length;
for (const a of actions) {
metrics.actionsByType[a.type] = (metrics.actionsByType[a.type] ?? 0) + 1;
@@ -4044,6 +4093,202 @@ export class TownDO extends DurableObject {
return null;
}
+ /**
+ * Check a PR for unresolved review comments and failing CI checks.
+ * Used by the auto-resolve PR feedback feature.
+ */
+ private async checkPRFeedback(
+ prUrl: string,
+ townConfig: TownConfig
+ ): Promise {
+ const ghMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
+ if (!ghMatch) {
+ // GitLab feedback detection not yet supported
+ return null;
+ }
+
+ const [, owner, repo, numberStr] = ghMatch;
+ const token = townConfig.git_auth.github_token;
+ if (!token) return null;
+
+ const headers = {
+ Authorization: `token ${token}`,
+ Accept: 'application/vnd.github.v3+json',
+ 'User-Agent': 'Gastown-Refinery/1.0',
+ };
+
+ // Check for unresolved review threads via GraphQL.
+ // Fetches the first 100 threads; if there are more (hasNextPage),
+ // conservatively treat the PR as having unresolved comments to avoid
+ // auto-merging with un-checked reviewer feedback.
+ let hasUnresolvedComments = false;
+ try {
+ const graphqlRes = await fetch('https://api.github.com/graphql', {
+ method: 'POST',
+ headers: { ...headers, 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ query: `query($owner: String!, $repo: String!, $number: Int!) {
+ repository(owner: $owner, name: $repo) {
+ pullRequest(number: $number) {
+ reviewThreads(first: 100) {
+ pageInfo { hasNextPage }
+ nodes { isResolved }
+ }
+ }
+ }
+ }`,
+ variables: { owner, repo, number: parseInt(numberStr, 10) },
+ }),
+ });
+ if (graphqlRes.ok) {
+ const gqlRaw: unknown = await graphqlRes.json();
+ const gql = z
+ .object({
+ data: z
+ .object({
+ repository: z
+ .object({
+ pullRequest: z
+ .object({
+ reviewThreads: z
+ .object({
+ pageInfo: z.object({ hasNextPage: z.boolean() }).optional(),
+ nodes: z.array(z.object({ isResolved: z.boolean() })),
+ })
+ .optional(),
+ })
+ .optional(),
+ })
+ .optional(),
+ })
+ .optional(),
+ })
+ .safeParse(gqlRaw);
+ const reviewThreads = gql.success
+ ? gql.data.data?.repository?.pullRequest?.reviewThreads
+ : undefined;
+ const threads = reviewThreads?.nodes ?? [];
+ const hasMorePages = reviewThreads?.pageInfo?.hasNextPage === true;
+ hasUnresolvedComments = threads.some(t => !t.isResolved) || hasMorePages;
+ }
+ } catch (err) {
+ console.warn(`${TOWN_LOG} checkPRFeedback: GraphQL failed for ${prUrl}`, err);
+ }
+
+ // Check CI status via check-runs API.
+ // Uses per_page=100 (GitHub max) and compares total_count to detect
+ // unpaginated runs — if there are more runs than returned, conservatively
+ // marks allChecksPass as false to prevent premature auto-merge.
+ let hasFailingChecks = false;
+ let allChecksPass = false;
+ let hasUncheckedRuns = false;
+ try {
+ // Get the PR's head SHA
+ const prRes = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/pulls/${numberStr}`,
+ { headers }
+ );
+ if (prRes.ok) {
+ const prRaw: unknown = await prRes.json();
+ const prData = z
+ .object({ head: z.object({ sha: z.string() }).optional() })
+ .safeParse(prRaw);
+ const sha = prData.success ? prData.data.head?.sha : undefined;
+ if (sha) {
+ const checksRes = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/commits/${sha}/check-runs?per_page=100`,
+ { headers }
+ );
+ if (checksRes.ok) {
+ const checksRaw: unknown = await checksRes.json();
+ const checksData = z
+ .object({
+ total_count: z.number().optional(),
+ check_runs: z
+ .array(
+ z.object({
+ status: z.string(),
+ conclusion: z.string().nullable(),
+ })
+ )
+ .optional(),
+ })
+ .safeParse(checksRaw);
+ const runs = checksData.success ? (checksData.data.check_runs ?? []) : [];
+ const totalCount = checksData.success
+ ? (checksData.data.total_count ?? runs.length)
+ : runs.length;
+ const hasMorePages = totalCount > runs.length;
+ hasUncheckedRuns = hasMorePages;
+
+ hasFailingChecks = runs.some(
+ r =>
+ r.status === 'completed' && r.conclusion !== 'success' && r.conclusion !== 'skipped'
+ );
+ // All checks pass when:
+ // - No check-runs exist (repo has no CI — nothing to fail), OR
+ // - All returned runs completed successfully and no unpaginated runs exist
+ allChecksPass =
+ runs.length === 0 ||
+ (!hasMorePages &&
+ runs.every(
+ r =>
+ r.status === 'completed' &&
+ (r.conclusion === 'success' || r.conclusion === 'skipped')
+ ));
+ }
+ }
+ }
+ } catch (err) {
+ console.warn(`${TOWN_LOG} checkPRFeedback: check-runs failed for ${prUrl}`, err);
+ }
+
+ return { hasUnresolvedComments, hasFailingChecks, allChecksPass, hasUncheckedRuns };
+ }
+
+ /**
+ * Merge a PR via GitHub API. Used by the auto-merge feature.
+ * Returns true if the merge succeeded.
+ */
+ private async mergePR(prUrl: string, townConfig: TownConfig): Promise {
+ const ghMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
+ if (!ghMatch) {
+ console.warn(`${TOWN_LOG} mergePR: unsupported PR URL format: ${prUrl}`);
+ return false;
+ }
+
+ const [, owner, repo, numberStr] = ghMatch;
+ const token = townConfig.git_auth.github_token;
+ if (!token) {
+ console.warn(`${TOWN_LOG} mergePR: no github_token configured`);
+ return false;
+ }
+
+ const response = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/pulls/${numberStr}/merge`,
+ {
+ method: 'PUT',
+ headers: {
+ Authorization: `token ${token}`,
+ Accept: 'application/vnd.github.v3+json',
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'Gastown-Refinery/1.0',
+ },
+ body: JSON.stringify({ merge_method: 'merge' }),
+ }
+ );
+
+ if (!response.ok) {
+ const text = await response.text().catch(() => '(unreadable)');
+ console.warn(
+ `${TOWN_LOG} mergePR: GitHub API returned ${response.status} for ${prUrl}: ${text.slice(0, 500)}`
+ );
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Bump severity of stale unacknowledged escalations.
*/
@@ -4449,7 +4694,10 @@ export class TownDO extends DurableObject {
}
// Run reconciler against the resulting state
- const actions = reconciler.reconcile(this.sql);
+ const tc = await this.getTownConfig();
+ const actions = reconciler.reconcile(this.sql, {
+ refineryCodeReview: tc.refinery?.code_review ?? true,
+ });
// Capture a state snapshot before rollback
const agentSnapshot = [
@@ -4528,7 +4776,10 @@ export class TownDO extends DurableObject {
}
// Phase 1: Reconcile against now-current state
- const actions = reconciler.reconcile(this.sql);
+ const tc2 = await this.getTownConfig();
+ const actions = reconciler.reconcile(this.sql, {
+ refineryCodeReview: tc2.refinery?.code_review ?? true,
+ });
const pendingEventCount = events.pendingEventCount(this.sql);
const actionsByType: Record = {};
for (const a of actions) {
diff --git a/cloudflare-gastown/src/dos/town/actions.ts b/cloudflare-gastown/src/dos/town/actions.ts
index d9bbac5c3..0419a1f4a 100644
--- a/cloudflare-gastown/src/dos/town/actions.ts
+++ b/cloudflare-gastown/src/dos/town/actions.ts
@@ -14,11 +14,14 @@ import { agent_metadata } from '../../db/tables/agent-metadata.table';
import { convoy_metadata } from '../../db/tables/convoy-metadata.table';
import { bead_dependencies } from '../../db/tables/bead-dependencies.table';
import { agent_nudges } from '../../db/tables/agent-nudges.table';
+import { review_metadata } from '../../db/tables/review-metadata.table';
import { query } from '../../util/query.util';
import * as beadOps from './beads';
import * as agentOps from './agents';
import * as reviewQueue from './review-queue';
import * as patrol from './patrol';
+import { getRig } from './rigs';
+import { parseGitUrl } from '../../util/platform-pr.util';
// ── Bead mutations ──────────────────────────────────────────────────
@@ -164,6 +167,12 @@ const NotifyMayor = z.object({
message: z.string(),
});
+const MergePr = z.object({
+ type: z.literal('merge_pr'),
+ bead_id: z.string(),
+ pr_url: z.string(),
+});
+
const EmitEvent = z.object({
type: z.literal('emit_event'),
event_name: z.string(),
@@ -195,6 +204,7 @@ export const Action = z.discriminatedUnion('type', [
DispatchAgent,
StopAgent,
PollPr,
+ MergePr,
SendNudge,
CreateTriageRequest,
NotifyMayor,
@@ -225,6 +235,7 @@ export type CloseConvoy = z.infer;
export type DispatchAgent = z.infer;
export type StopAgent = z.infer;
export type PollPr = z.infer;
+export type MergePr = z.infer;
export type SendNudge = z.infer;
export type CreateTriageRequest = z.infer;
export type NotifyMayor = z.infer;
@@ -235,6 +246,17 @@ export type EmitEvent = z.infer;
// The SQL handle is for synchronous mutations; the rest are for async
// side effects (dispatch, stop, poll, nudge).
+/** Result of checking PR feedback (unresolved comments + failing CI checks). */
+export type PRFeedbackCheckResult = {
+ hasUnresolvedComments: boolean;
+ hasFailingChecks: boolean;
+ allChecksPass: boolean;
+ /** True when the check-runs response was paginated and not all runs were
+ * inspected. allChecksPass is already false in this case, but
+ * hasFailingChecks only reflects the runs we actually saw. */
+ hasUncheckedRuns: boolean;
+};
+
export type ApplyActionContext = {
sql: SqlStorage;
townId: string;
@@ -244,6 +266,10 @@ export type ApplyActionContext = {
stopAgent: (agentId: string) => Promise;
/** Check a PR's status via GitHub/GitLab API. Returns 'open'|'merged'|'closed'|null. */
checkPRStatus: (prUrl: string) => Promise<'open' | 'merged' | 'closed' | null>;
+ /** Check PR for unresolved review comments and failing CI checks. */
+ checkPRFeedback: (prUrl: string) => Promise;
+ /** Merge a PR via GitHub/GitLab API. */
+ mergePR: (prUrl: string) => Promise;
/** Queue a nudge message for an agent. */
queueNudge: (agentId: string, message: string, tier: string) => Promise;
/** Insert a town_event for deferred processing (e.g. pr_status_changed). */
@@ -253,6 +279,10 @@ export type ApplyActionContext = {
) => void;
/** Emit an analytics/WebSocket event. */
emitEvent: (data: Record) => void;
+ /** Get the current town config (read lazily). */
+ getTownConfig: () => Promise<{
+ refinery?: { auto_resolve_pr_feedback?: boolean; auto_merge_delay_minutes?: number | null };
+ }>;
};
const LOG = '[actions]';
@@ -565,6 +595,139 @@ export function applyAction(ctx: ApplyActionContext, action: Action): (() => Pro
bead_id: action.bead_id,
payload: { pr_url: action.pr_url, pr_state: status },
});
+ return;
+ }
+
+ // PR is open — check for feedback and auto-merge if configured
+ const townConfig = await ctx.getTownConfig();
+ const refineryConfig = townConfig.refinery;
+ if (!refineryConfig) return;
+
+ // Auto-resolve PR feedback: detect unresolved comments and failing CI
+ if (refineryConfig.auto_resolve_pr_feedback) {
+ const feedback = await ctx.checkPRFeedback(action.pr_url);
+ if (
+ feedback &&
+ (feedback.hasUnresolvedComments ||
+ feedback.hasFailingChecks ||
+ feedback.hasUncheckedRuns)
+ ) {
+ // Check for existing non-terminal feedback bead to prevent duplicates
+ const existingFeedback = hasExistingFeedbackBead(sql, action.bead_id);
+ if (!existingFeedback) {
+ // Parse PR URL for repo/number metadata
+ const prMeta = parsePrUrl(action.pr_url);
+ const rmRows = z
+ .object({ branch: z.string() })
+ .array()
+ .parse([
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT ${review_metadata.columns.branch}
+ FROM ${review_metadata}
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ ),
+ ]);
+ const branch = rmRows[0]?.branch ?? '';
+
+ ctx.insertEvent('pr_feedback_detected', {
+ bead_id: action.bead_id,
+ payload: {
+ mr_bead_id: action.bead_id,
+ pr_url: action.pr_url,
+ pr_number: prMeta?.prNumber ?? 0,
+ repo: prMeta?.repo ?? '',
+ branch,
+ has_unresolved_comments: feedback.hasUnresolvedComments,
+ has_failing_checks: feedback.hasFailingChecks,
+ },
+ });
+ }
+
+ // Update last_feedback_check_at
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.last_feedback_check_at} = ?
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ }
+ }
+
+ // Auto-merge timer: track grace period when everything is green
+ if (
+ refineryConfig.auto_merge_delay_minutes !== null &&
+ refineryConfig.auto_merge_delay_minutes !== undefined
+ ) {
+ const feedback = await ctx.checkPRFeedback(action.pr_url);
+ if (!feedback) return;
+
+ const allGreen =
+ !feedback.hasUnresolvedComments &&
+ !feedback.hasFailingChecks &&
+ feedback.allChecksPass;
+
+ if (allGreen) {
+ // Check if timer is already running
+ const readySinceRows = z
+ .object({ auto_merge_ready_since: z.string().nullable() })
+ .array()
+ .parse([
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT ${review_metadata.columns.auto_merge_ready_since}
+ FROM ${review_metadata}
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ ),
+ ]);
+
+ const readySince = readySinceRows[0]?.auto_merge_ready_since;
+
+ if (!readySince) {
+ // First tick where everything is green — start the timer
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.auto_merge_ready_since} = ?
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ } else {
+ const elapsed = Date.now() - new Date(readySince).getTime();
+ if (elapsed >= refineryConfig.auto_merge_delay_minutes * 60_000) {
+ // Grace period elapsed — emit merge event
+ ctx.insertEvent('pr_auto_merge', {
+ bead_id: action.bead_id,
+ payload: {
+ mr_bead_id: action.bead_id,
+ pr_url: action.pr_url,
+ },
+ });
+ }
+ }
+ } else {
+ // Not all green — reset the timer
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.auto_merge_ready_since} = NULL
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ );
+ }
}
} catch (err) {
console.warn(`${LOG} poll_pr failed: bead=${action.bead_id} url=${action.pr_url}`, err);
@@ -572,6 +735,134 @@ export function applyAction(ctx: ApplyActionContext, action: Action): (() => Pro
};
}
+ case 'merge_pr': {
+ // Validate the PR URL matches the rig's repository before merging.
+ // Prevents merging an unrelated repo if a buggy refinery stores a wrong URL.
+ const mrBead = beadOps.getBead(sql, action.bead_id);
+ if (mrBead?.rig_id) {
+ const rig = getRig(sql, mrBead.rig_id);
+ if (rig?.git_url) {
+ const rigCoords = parseGitUrl(rig.git_url);
+ const prMeta = parsePrUrl(action.pr_url);
+ if (rigCoords && prMeta) {
+ const rigRepo = `${rigCoords.owner}/${rigCoords.repo}`;
+ if (rigRepo !== prMeta.repo) {
+ console.warn(
+ `${LOG} merge_pr: PR repo "${prMeta.repo}" does not match rig repo "${rigRepo}" — refusing to merge`
+ );
+ // Clear the pending flag to avoid retry loops
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${beads}
+ SET ${beads.columns.metadata} = json_remove(COALESCE(${beads.metadata}, '{}'), '$.auto_merge_pending'),
+ ${beads.columns.updated_at} = ?
+ WHERE ${beads.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ return null;
+ }
+ }
+ }
+ }
+
+ return async () => {
+ try {
+ // Re-check feedback immediately before merging to avoid acting on
+ // stale state. If a reviewer posted new comments or CI regressed
+ // since the last poll, abort and reset the timer.
+ const freshFeedback = await ctx.checkPRFeedback(action.pr_url);
+ if (
+ freshFeedback &&
+ (freshFeedback.hasUnresolvedComments ||
+ freshFeedback.hasFailingChecks ||
+ !freshFeedback.allChecksPass)
+ ) {
+ console.log(
+ `${LOG} merge_pr: fresh feedback check found issues, aborting merge for bead=${action.bead_id}`
+ );
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${beads}
+ SET ${beads.columns.metadata} = json_remove(COALESCE(${beads.metadata}, '{}'), '$.auto_merge_pending'),
+ ${beads.columns.updated_at} = ?
+ WHERE ${beads.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.auto_merge_ready_since} = NULL
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ );
+ return;
+ }
+
+ const merged = await ctx.mergePR(action.pr_url);
+ if (merged) {
+ ctx.insertEvent('pr_status_changed', {
+ bead_id: action.bead_id,
+ payload: { pr_url: action.pr_url, pr_state: 'merged' },
+ });
+ } else {
+ // Merge failed (405/409: branch protection, merge conflict, stale head, etc.)
+ // Clear auto_merge_pending so we resume normal polling on the next tick.
+ // Also reset the auto_merge_ready_since timer so it re-evaluates freshness.
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${beads}
+ SET ${beads.columns.metadata} = json_remove(COALESCE(${beads.metadata}, '{}'), '$.auto_merge_pending'),
+ ${beads.columns.updated_at} = ?
+ WHERE ${beads.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.auto_merge_ready_since} = NULL
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ );
+ console.warn(
+ `${LOG} merge_pr: merge failed, cleared auto_merge_pending for bead=${action.bead_id}`
+ );
+ }
+ } catch (err) {
+ console.warn(`${LOG} merge_pr failed: bead=${action.bead_id} url=${action.pr_url}`, err);
+ // Clear pending flag on unexpected errors too
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${beads}
+ SET ${beads.columns.metadata} = json_remove(COALESCE(${beads.metadata}, '{}'), '$.auto_merge_pending'),
+ ${beads.columns.updated_at} = ?
+ WHERE ${beads.bead_id} = ?
+ `,
+ [now(), action.bead_id]
+ );
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${review_metadata}
+ SET ${review_metadata.columns.auto_merge_ready_since} = NULL
+ WHERE ${review_metadata.bead_id} = ?
+ `,
+ [action.bead_id]
+ );
+ }
+ };
+ }
+
case 'send_nudge': {
// Insert nudge record synchronously.
// Explicitly set created_at to ISO 8601 so it matches the format used
@@ -646,3 +937,43 @@ export function applyAction(ctx: ApplyActionContext, action: Action): (() => Pro
}
}
}
+
+// ── Helpers ─────────────────────────────────────────────────────────
+
+/** Check if an MR bead already has a non-terminal feedback bead blocking it. */
+function hasExistingFeedbackBead(sql: SqlStorage, mrBeadId: string): boolean {
+ const rows = [
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT 1 FROM ${bead_dependencies} bd
+ INNER JOIN ${beads} fb ON fb.${beads.columns.bead_id} = bd.${bead_dependencies.columns.depends_on_bead_id}
+ WHERE bd.${bead_dependencies.columns.bead_id} = ?
+ AND bd.${bead_dependencies.columns.dependency_type} = 'blocks'
+ AND fb.${beads.columns.labels} LIKE '%gt:pr-feedback%'
+ AND fb.${beads.columns.status} NOT IN ('closed', 'failed')
+ LIMIT 1
+ `,
+ [mrBeadId]
+ ),
+ ];
+ return rows.length > 0;
+}
+
+/** Parse a GitHub/GitLab PR URL to extract repo and PR number. */
+function parsePrUrl(prUrl: string): { repo: string; prNumber: number } | null {
+ // GitHub: https://github.com/{owner}/{repo}/pull/{number}
+ const ghMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
+ if (ghMatch) {
+ return { repo: ghMatch[1], prNumber: parseInt(ghMatch[2], 10) };
+ }
+ // GitLab: https://{host}/{path}/-/merge_requests/{iid}
+ const glMatch = prUrl.match(/^https:\/\/[^/]+\/(.+)\/-\/merge_requests\/(\d+)/);
+ if (glMatch) {
+ return { repo: glMatch[1], prNumber: parseInt(glMatch[2], 10) };
+ }
+ return null;
+}
+
+// Exported for testing
+export { hasExistingFeedbackBead as _hasExistingFeedbackBead, parsePrUrl as _parsePrUrl };
diff --git a/cloudflare-gastown/src/dos/town/beads.ts b/cloudflare-gastown/src/dos/town/beads.ts
index c2e865d55..25d76d8a3 100644
--- a/cloudflare-gastown/src/dos/town/beads.ts
+++ b/cloudflare-gastown/src/dos/town/beads.ts
@@ -27,7 +27,11 @@ import {
createTableAgentMetadata,
migrateAgentMetadata,
} from '../../db/tables/agent-metadata.table';
-import { review_metadata, createTableReviewMetadata } from '../../db/tables/review-metadata.table';
+import {
+ review_metadata,
+ createTableReviewMetadata,
+ migrateReviewMetadata,
+} from '../../db/tables/review-metadata.table';
import {
escalation_metadata,
createTableEscalationMetadata,
@@ -72,7 +76,12 @@ export function initBeadTables(sql: SqlStorage): void {
dropCheckConstraints(sql);
// Migrations: add columns to existing tables (idempotent)
- for (const stmt of [...migrateBeads(), ...migrateConvoyMetadata(), ...migrateAgentMetadata()]) {
+ for (const stmt of [
+ ...migrateBeads(),
+ ...migrateConvoyMetadata(),
+ ...migrateAgentMetadata(),
+ ...migrateReviewMetadata(),
+ ]) {
try {
query(sql, stmt, []);
} catch {
diff --git a/cloudflare-gastown/src/dos/town/config.ts b/cloudflare-gastown/src/dos/town/config.ts
index 0d064ed6b..6c3c875f7 100644
--- a/cloudflare-gastown/src/dos/town/config.ts
+++ b/cloudflare-gastown/src/dos/town/config.ts
@@ -82,6 +82,15 @@ export async function updateTownConfig(
auto_merge: update.refinery.auto_merge ?? current.refinery?.auto_merge ?? true,
require_clean_merge:
update.refinery.require_clean_merge ?? current.refinery?.require_clean_merge ?? true,
+ code_review: update.refinery.code_review ?? current.refinery?.code_review ?? true,
+ auto_resolve_pr_feedback:
+ update.refinery.auto_resolve_pr_feedback ??
+ current.refinery?.auto_resolve_pr_feedback ??
+ false,
+ auto_merge_delay_minutes:
+ update.refinery.auto_merge_delay_minutes !== undefined
+ ? update.refinery.auto_merge_delay_minutes
+ : (current.refinery?.auto_merge_delay_minutes ?? null),
}
: current.refinery,
container:
diff --git a/cloudflare-gastown/src/dos/town/pr-feedback.test.ts b/cloudflare-gastown/src/dos/town/pr-feedback.test.ts
new file mode 100644
index 000000000..1af8cb60a
--- /dev/null
+++ b/cloudflare-gastown/src/dos/town/pr-feedback.test.ts
@@ -0,0 +1,219 @@
+import { describe, it, expect } from 'vitest';
+import { TownConfigSchema } from '../../types';
+import { _parsePrUrl as parsePrUrl } from './actions';
+import { TownEventType } from '../../db/tables/town-events.table';
+import { ReviewMetadataRecord } from '../../db/tables/review-metadata.table';
+import { buildRefinerySystemPrompt } from '../../prompts/refinery-system.prompt';
+
+describe('TownConfigSchema refinery extensions', () => {
+ it('defaults code_review to true', () => {
+ const config = TownConfigSchema.parse({ refinery: {} });
+ expect(config.refinery?.code_review).toBe(true);
+ });
+
+ it('accepts code_review = false', () => {
+ const config = TownConfigSchema.parse({ refinery: { code_review: false } });
+ expect(config.refinery?.code_review).toBe(false);
+ });
+
+ it('defaults auto_resolve_pr_feedback to false', () => {
+ const config = TownConfigSchema.parse({});
+ expect(config.refinery).toBeUndefined();
+
+ const configWithRefinery = TownConfigSchema.parse({ refinery: {} });
+ expect(configWithRefinery.refinery?.auto_resolve_pr_feedback).toBe(false);
+ });
+
+ it('defaults auto_merge_delay_minutes to null', () => {
+ const config = TownConfigSchema.parse({ refinery: {} });
+ expect(config.refinery?.auto_merge_delay_minutes).toBeNull();
+ });
+
+ it('accepts auto_resolve_pr_feedback = true', () => {
+ const config = TownConfigSchema.parse({
+ refinery: { auto_resolve_pr_feedback: true },
+ });
+ expect(config.refinery?.auto_resolve_pr_feedback).toBe(true);
+ });
+
+ it('accepts auto_merge_delay_minutes = 0 (immediate merge)', () => {
+ const config = TownConfigSchema.parse({
+ refinery: { auto_merge_delay_minutes: 0 },
+ });
+ expect(config.refinery?.auto_merge_delay_minutes).toBe(0);
+ });
+
+ it('accepts auto_merge_delay_minutes = 15', () => {
+ const config = TownConfigSchema.parse({
+ refinery: { auto_merge_delay_minutes: 15 },
+ });
+ expect(config.refinery?.auto_merge_delay_minutes).toBe(15);
+ });
+
+ it('rejects negative auto_merge_delay_minutes', () => {
+ expect(() => TownConfigSchema.parse({ refinery: { auto_merge_delay_minutes: -1 } })).toThrow();
+ });
+
+ it('preserves existing refinery fields alongside new ones', () => {
+ const config = TownConfigSchema.parse({
+ refinery: {
+ gates: ['npm test'],
+ auto_merge: false,
+ require_clean_merge: true,
+ auto_resolve_pr_feedback: true,
+ auto_merge_delay_minutes: 60,
+ },
+ });
+ expect(config.refinery?.gates).toEqual(['npm test']);
+ expect(config.refinery?.auto_merge).toBe(false);
+ expect(config.refinery?.require_clean_merge).toBe(true);
+ expect(config.refinery?.auto_resolve_pr_feedback).toBe(true);
+ expect(config.refinery?.auto_merge_delay_minutes).toBe(60);
+ });
+});
+
+describe('parsePrUrl', () => {
+ it('parses GitHub PR URLs', () => {
+ const result = parsePrUrl('https://github.com/Kilo-Org/cloud/pull/42');
+ expect(result).toEqual({ repo: 'Kilo-Org/cloud', prNumber: 42 });
+ });
+
+ it('parses GitHub PR URLs with long paths', () => {
+ const result = parsePrUrl('https://github.com/org/repo/pull/123');
+ expect(result).toEqual({ repo: 'org/repo', prNumber: 123 });
+ });
+
+ it('parses GitLab MR URLs', () => {
+ const result = parsePrUrl('https://gitlab.com/group/project/-/merge_requests/7');
+ expect(result).toEqual({ repo: 'group/project', prNumber: 7 });
+ });
+
+ it('parses GitLab MR URLs with subgroups', () => {
+ const result = parsePrUrl('https://gitlab.example.com/org/team/project/-/merge_requests/99');
+ expect(result).toEqual({ repo: 'org/team/project', prNumber: 99 });
+ });
+
+ it('returns null for unrecognized URLs', () => {
+ expect(parsePrUrl('https://example.com/pr/1')).toBeNull();
+ expect(parsePrUrl('not a url')).toBeNull();
+ });
+});
+
+describe('TownEventType enum', () => {
+ it('includes pr_feedback_detected and pr_auto_merge', () => {
+ expect(TownEventType.options).toContain('pr_feedback_detected');
+ expect(TownEventType.options).toContain('pr_auto_merge');
+ });
+});
+
+describe('ReviewMetadataRecord', () => {
+ it('includes auto_merge_ready_since and last_feedback_check_at fields', () => {
+ const result = ReviewMetadataRecord.parse({
+ bead_id: 'test-id',
+ branch: 'feature/test',
+ target_branch: 'main',
+ merge_commit: null,
+ pr_url: 'https://github.com/org/repo/pull/1',
+ retry_count: 0,
+ auto_merge_ready_since: '2025-01-01T00:00:00.000Z',
+ last_feedback_check_at: '2025-01-01T00:00:00.000Z',
+ });
+ expect(result.auto_merge_ready_since).toBe('2025-01-01T00:00:00.000Z');
+ expect(result.last_feedback_check_at).toBe('2025-01-01T00:00:00.000Z');
+ });
+
+ it('accepts null for new fields', () => {
+ const result = ReviewMetadataRecord.parse({
+ bead_id: 'test-id',
+ branch: 'feature/test',
+ target_branch: 'main',
+ merge_commit: null,
+ pr_url: null,
+ retry_count: 0,
+ auto_merge_ready_since: null,
+ last_feedback_check_at: null,
+ });
+ expect(result.auto_merge_ready_since).toBeNull();
+ expect(result.last_feedback_check_at).toBeNull();
+ });
+});
+
+describe('config deep merge for refinery extensions', () => {
+ it('preserves auto_resolve_pr_feedback when updating other refinery fields', () => {
+ // Simulate the merge logic from config.ts updateTownConfig:
+ // When a partial update provides only gates, the new refinery fields
+ // should fall through to the current value.
+ const current = TownConfigSchema.parse({
+ refinery: {
+ gates: ['npm test'],
+ auto_resolve_pr_feedback: true,
+ auto_merge_delay_minutes: 15,
+ },
+ });
+
+ // Partial update only touches gates — other fields come from current
+ const updateGates: string[] | undefined = ['npm run test:all'];
+ const updateAutoResolve: boolean | undefined = undefined;
+ const updateDelayMinutes: number | null | undefined = undefined;
+
+ const merged = {
+ gates: updateGates ?? current.refinery?.gates ?? [],
+ auto_merge: current.refinery?.auto_merge ?? true,
+ require_clean_merge: current.refinery?.require_clean_merge ?? true,
+ auto_resolve_pr_feedback:
+ updateAutoResolve ?? current.refinery?.auto_resolve_pr_feedback ?? false,
+ auto_merge_delay_minutes:
+ updateDelayMinutes !== undefined
+ ? updateDelayMinutes
+ : (current.refinery?.auto_merge_delay_minutes ?? null),
+ };
+
+ expect(merged.auto_resolve_pr_feedback).toBe(true);
+ expect(merged.auto_merge_delay_minutes).toBe(15);
+ expect(merged.gates).toEqual(['npm run test:all']);
+ });
+});
+
+describe('buildRefinerySystemPrompt with existingPrUrl', () => {
+ const baseParams = {
+ identity: 'refinery-alpha',
+ rigId: 'rig-1',
+ townId: 'town-1',
+ gates: ['pnpm test'],
+ branch: 'gt/toast/abc123',
+ targetBranch: 'main',
+ polecatAgentId: 'polecat-1',
+ mergeStrategy: 'pr' as const,
+ };
+
+ it('produces standard PR-creation prompt when no existingPrUrl', () => {
+ const prompt = buildRefinerySystemPrompt(baseParams);
+ expect(prompt).toContain('gh pr create');
+ expect(prompt).toContain('create a pull request');
+ expect(prompt).not.toContain('Pull Request:');
+ });
+
+ it('produces PR-review prompt when existingPrUrl is set', () => {
+ const prompt = buildRefinerySystemPrompt({
+ ...baseParams,
+ existingPrUrl: 'https://github.com/org/repo/pull/42',
+ });
+ expect(prompt).toContain('https://github.com/org/repo/pull/42');
+ expect(prompt).toContain('gh pr review');
+ expect(prompt).toContain('gh pr diff');
+ expect(prompt).toContain('gh pr comment');
+ expect(prompt).toContain('Do NOT merge the PR');
+ expect(prompt).not.toContain('gh pr create');
+ expect(prompt).toContain('Do NOT use `gh pr review --approve`');
+ });
+
+ it('includes gates in PR-review prompt', () => {
+ const prompt = buildRefinerySystemPrompt({
+ ...baseParams,
+ gates: ['pnpm test', 'pnpm lint'],
+ existingPrUrl: 'https://github.com/org/repo/pull/42',
+ });
+ expect(prompt).toContain('pnpm test');
+ expect(prompt).toContain('pnpm lint');
+ });
+});
diff --git a/cloudflare-gastown/src/dos/town/reconciler.ts b/cloudflare-gastown/src/dos/town/reconciler.ts
index e3129896e..d0384a1a2 100644
--- a/cloudflare-gastown/src/dos/town/reconciler.ts
+++ b/cloudflare-gastown/src/dos/town/reconciler.ts
@@ -163,6 +163,7 @@ const MrBeadRow = BeadRecord.pick({
rig_id: true,
updated_at: true,
assignee_agent_bead_id: true,
+ metadata: true,
}).extend({
// Joined from review_metadata
pr_url: ReviewMetadataRecord.shape.pr_url,
@@ -368,6 +369,71 @@ export function applyEvent(sql: SqlStorage, event: TownEventRecord): void {
return;
}
+ case 'pr_feedback_detected': {
+ const mrBeadId = typeof payload.mr_bead_id === 'string' ? payload.mr_bead_id : null;
+ if (!mrBeadId) {
+ console.warn(`${LOG} applyEvent: pr_feedback_detected missing mr_bead_id`);
+ return;
+ }
+
+ const mrBead = beadOps.getBead(sql, mrBeadId);
+ if (!mrBead || mrBead.status === 'closed' || mrBead.status === 'failed') return;
+
+ // Check for existing non-terminal feedback bead to prevent duplicates
+ if (hasExistingPrFeedbackBead(sql, mrBeadId)) return;
+
+ const prUrl = typeof payload.pr_url === 'string' ? payload.pr_url : '';
+ const prNumber = typeof payload.pr_number === 'number' ? payload.pr_number : 0;
+ const repo = typeof payload.repo === 'string' ? payload.repo : '';
+ const branch = typeof payload.branch === 'string' ? payload.branch : '';
+ const hasUnresolvedComments = payload.has_unresolved_comments === true;
+ const hasFailingChecks = payload.has_failing_checks === true;
+
+ const feedbackBead = beadOps.createBead(sql, {
+ type: 'issue',
+ title: buildFeedbackBeadTitle(prNumber, repo, hasUnresolvedComments, hasFailingChecks),
+ body: buildFeedbackPrompt(prNumber, repo, branch, hasUnresolvedComments, hasFailingChecks),
+ rig_id: mrBead.rig_id ?? undefined,
+ parent_bead_id: mrBeadId,
+ labels: ['gt:pr-feedback'],
+ metadata: {
+ pr_feedback_for: mrBeadId,
+ pr_url: prUrl,
+ branch,
+ },
+ });
+
+ // Feedback bead blocks the MR bead (same pattern as rework beads)
+ beadOps.insertDependency(sql, mrBeadId, feedbackBead.bead_id, 'blocks');
+ return;
+ }
+
+ case 'pr_auto_merge': {
+ const mrBeadId = typeof payload.mr_bead_id === 'string' ? payload.mr_bead_id : null;
+ if (!mrBeadId) {
+ console.warn(`${LOG} applyEvent: pr_auto_merge missing mr_bead_id`);
+ return;
+ }
+
+ const mrBead = beadOps.getBead(sql, mrBeadId);
+ if (!mrBead || mrBead.status === 'closed' || mrBead.status === 'failed') return;
+
+ // The actual merge is handled by the merge_pr side effect generated by
+ // the reconciler on the next tick when it sees this event has been processed.
+ // We just mark the intent here via metadata.
+ query(
+ sql,
+ /* sql */ `
+ UPDATE ${beads}
+ SET ${beads.columns.metadata} = json_set(COALESCE(${beads.metadata}, '{}'), '$.auto_merge_pending', 1),
+ ${beads.columns.updated_at} = ?
+ WHERE ${beads.bead_id} = ?
+ `,
+ [new Date().toISOString(), mrBeadId]
+ );
+ return;
+ }
+
default: {
console.warn(`${LOG} applyEvent: unknown event type: ${event.event_type}`);
}
@@ -378,12 +444,17 @@ export function applyEvent(sql: SqlStorage, event: TownEventRecord): void {
// Top-level reconcile
// ════════════════════════════════════════════════════════════════════
-export function reconcile(sql: SqlStorage, opts?: { draining?: boolean }): Action[] {
+export function reconcile(
+ sql: SqlStorage,
+ opts?: { draining?: boolean; refineryCodeReview?: boolean }
+): Action[] {
const draining = opts?.draining ?? false;
const actions: Action[] = [];
actions.push(...reconcileAgents(sql, { draining }));
actions.push(...reconcileBeads(sql, { draining }));
- actions.push(...reconcileReviewQueue(sql, { draining }));
+ actions.push(
+ ...reconcileReviewQueue(sql, { draining, refineryCodeReview: opts?.refineryCodeReview })
+ );
actions.push(...reconcileConvoys(sql));
actions.push(...reconcileGUPP(sql, { draining }));
actions.push(...reconcileGC(sql));
@@ -943,8 +1014,12 @@ export function reconcileBeads(sql: SqlStorage, opts?: { draining?: boolean }):
// refinery dispatch
// ════════════════════════════════════════════════════════════════════
-export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolean }): Action[] {
+export function reconcileReviewQueue(
+ sql: SqlStorage,
+ opts?: { draining?: boolean; refineryCodeReview?: boolean }
+): Action[] {
const draining = opts?.draining ?? false;
+ const refineryCodeReview = opts?.refineryCodeReview ?? true;
const actions: Action[] = [];
// Town-level circuit breaker
@@ -958,7 +1033,8 @@ export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolea
SELECT b.${beads.columns.bead_id}, b.${beads.columns.status},
b.${beads.columns.rig_id}, b.${beads.columns.updated_at},
rm.${review_metadata.columns.pr_url},
- b.${beads.columns.assignee_agent_bead_id}
+ b.${beads.columns.assignee_agent_bead_id},
+ b.${beads.columns.metadata}
FROM ${beads} b
INNER JOIN ${review_metadata} rm ON rm.${review_metadata.columns.bead_id} = b.${beads.columns.bead_id}
WHERE b.${beads.columns.type} = 'merge_request'
@@ -971,11 +1047,20 @@ export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolea
for (const mr of mrBeads) {
// Rule 1: PR-strategy MR beads in_progress need polling
if (mr.status === 'in_progress' && mr.pr_url) {
+ // Always poll for status changes (merged/closed by human, etc.)
actions.push({
type: 'poll_pr',
bead_id: mr.bead_id,
pr_url: mr.pr_url,
});
+ // If auto-merge is pending, also attempt the merge
+ if (mr.metadata?.auto_merge_pending) {
+ actions.push({
+ type: 'merge_pr',
+ bead_id: mr.bead_id,
+ pr_url: mr.pr_url,
+ });
+ }
}
// Rule 2: Stuck MR beads in_progress with no PR, no working agent, stale >30min
@@ -1032,7 +1117,10 @@ export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolea
// Rule 4: PR-strategy MR beads orphaned (refinery dispatched then died, stale >30min)
// Only in_progress — open beads are just waiting for the refinery to pop them.
+ // Skip when refinery code review is disabled: poll_pr keeps the bead alive via
+ // updated_at touches, and no refinery is expected to be working on it.
if (
+ refineryCodeReview &&
mr.status === 'in_progress' &&
mr.pr_url &&
staleMs(mr.updated_at, ORPHANED_PR_REVIEW_TIMEOUT_MS)
@@ -1051,6 +1139,47 @@ export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolea
}
}
+ // When refinery code review is disabled, skip refinery dispatch (Rules 5–6)
+ // for MR beads that already have a pr_url (polecat created the PR).
+ // Transition them straight to in_progress so poll_pr can handle auto-merge.
+ // MR beads without a pr_url (direct merge strategy) still need the refinery.
+ if (!refineryCodeReview) {
+ const openMrsWithPr = z
+ .object({ bead_id: z.string() })
+ .array()
+ .parse([
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT b.${beads.columns.bead_id}
+ FROM ${beads} b
+ INNER JOIN ${review_metadata} rm
+ ON rm.${review_metadata.columns.bead_id} = b.${beads.columns.bead_id}
+ WHERE b.${beads.columns.type} = 'merge_request'
+ AND b.${beads.columns.status} = 'open'
+ AND rm.${review_metadata.columns.pr_url} IS NOT NULL
+ `,
+ []
+ ),
+ ]);
+ for (const { bead_id } of openMrsWithPr) {
+ actions.push({
+ type: 'transition_bead',
+ bead_id,
+ from: 'open',
+ to: 'in_progress',
+ reason: 'refinery code review disabled — skip to poll_pr',
+ actor: 'system',
+ });
+ }
+ }
+
+ // Rules 5–6 only apply when refinery code review is enabled.
+ // When disabled, open MR beads with pr_url are fast-tracked above.
+ if (!refineryCodeReview) {
+ // Skip refinery dispatch — jump to Rule 7
+ } else {
+
// Rule 5: Pop open MR bead for idle refinery
// Get all rigs that have open MR beads
const rigsWithOpenMrs = z
@@ -1283,6 +1412,48 @@ export function reconcileReviewQueue(sql: SqlStorage, opts?: { draining?: boolea
});
}
+ } // end refineryCodeReview gate (Rules 5–6)
+
+ // Rule 7: Working refinery hooked to a terminal MR bead — stop it.
+ // This catches the race where auto-merge closes the MR bead while the
+ // refinery is still running in the container. Without this, the refinery
+ // can post review comments on an already-merged PR.
+ const workingRefineries = AgentRow.array().parse([
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT ${agent_metadata.bead_id}, ${agent_metadata.role},
+ ${agent_metadata.status}, ${agent_metadata.current_hook_bead_id},
+ ${agent_metadata.dispatch_attempts},
+ ${agent_metadata.last_activity_at},
+ b.${beads.columns.rig_id}
+ FROM ${agent_metadata}
+ LEFT JOIN ${beads} b ON b.${beads.columns.bead_id} = ${agent_metadata.bead_id}
+ WHERE ${agent_metadata.columns.role} = 'refinery'
+ AND ${agent_metadata.status} IN ('working', 'stalled')
+ AND ${agent_metadata.current_hook_bead_id} IS NOT NULL
+ `,
+ []
+ ),
+ ]);
+
+ for (const ref of workingRefineries) {
+ if (!ref.current_hook_bead_id) continue;
+ const mr = beadOps.getBead(sql, ref.current_hook_bead_id);
+ if (!mr || (mr.status !== 'closed' && mr.status !== 'failed')) continue;
+
+ actions.push({
+ type: 'stop_agent',
+ agent_id: ref.bead_id,
+ reason: `MR bead ${ref.current_hook_bead_id} is ${mr.status}`,
+ });
+ actions.push({
+ type: 'unhook_agent',
+ agent_id: ref.bead_id,
+ reason: `MR bead ${mr.status} — cleanup`,
+ });
+ }
+
return actions;
}
@@ -1709,6 +1880,93 @@ function hasRecentNudge(sql: SqlStorage, agentId: string, tier: string): boolean
return rows.length > 0;
}
+/** Check if an MR bead has a non-terminal feedback bead (gt:pr-feedback) blocking it. */
+function hasExistingPrFeedbackBead(sql: SqlStorage, mrBeadId: string): boolean {
+ const rows = [
+ ...query(
+ sql,
+ /* sql */ `
+ SELECT 1 FROM ${bead_dependencies} bd
+ INNER JOIN ${beads} fb ON fb.${beads.columns.bead_id} = bd.${bead_dependencies.columns.depends_on_bead_id}
+ WHERE bd.${bead_dependencies.columns.bead_id} = ?
+ AND bd.${bead_dependencies.columns.dependency_type} = 'blocks'
+ AND fb.${beads.columns.labels} LIKE '%gt:pr-feedback%'
+ AND fb.${beads.columns.status} NOT IN ('closed', 'failed')
+ LIMIT 1
+ `,
+ [mrBeadId]
+ ),
+ ];
+ return rows.length > 0;
+}
+
+/** Build a human-readable title for the feedback bead. */
+function buildFeedbackBeadTitle(
+ prNumber: number,
+ repo: string,
+ hasComments: boolean,
+ hasFailingChecks: boolean
+): string {
+ const parts: string[] = [];
+ if (hasComments) parts.push('review comments');
+ if (hasFailingChecks) parts.push('failing CI');
+ const shortRepo = repo.includes('/') ? repo.split('/').pop() : repo;
+ return `Address ${parts.join(' & ')} on PR #${prNumber}${shortRepo ? ` (${shortRepo})` : ''}`;
+}
+
+/** Build the polecat prompt body for addressing PR feedback. */
+function buildFeedbackPrompt(
+ prNumber: number,
+ repo: string,
+ branch: string,
+ hasComments: boolean,
+ hasFailingChecks: boolean
+): string {
+ const lines: string[] = [];
+ lines.push(`You are addressing feedback on PR #${prNumber} on ${repo}, branch ${branch}.`);
+ lines.push('');
+
+ if (hasComments && hasFailingChecks) {
+ lines.push('This PR has both unresolved review comments and failing CI checks.');
+ lines.push(
+ 'Address the review comments first, then fix the CI failures, as comment fixes may also resolve some CI issues.'
+ );
+ } else if (hasComments) {
+ lines.push('This PR has unresolved review comments.');
+ } else if (hasFailingChecks) {
+ lines.push('This PR has failing CI checks.');
+ }
+
+ lines.push('');
+ lines.push('## Review Comments');
+ lines.push('');
+ lines.push(`Run \`gh pr view ${prNumber} --comments\` to see all review comments.`);
+ lines.push('');
+ lines.push('For each unresolved comment thread:');
+ lines.push(
+ "- If it's a relevant code fix: make the change, push, reply explaining what you did, and resolve the thread"
+ );
+ lines.push("- If it's not relevant: reply explaining why, and resolve the thread");
+ lines.push('');
+ lines.push("It's important to resolve the full thread rather than just the base comment.");
+ lines.push('');
+ lines.push('## CI Checks');
+ lines.push('');
+ lines.push(`Run \`gh pr checks ${prNumber}\` to see the status of all CI checks.`);
+ lines.push('');
+ lines.push('For each failing check:');
+ lines.push('- Read the failure logs via `gh run view --log-failed`');
+ lines.push('- Fix the underlying issue (test failure, lint error, type error, etc.)');
+ lines.push('- Push the fix');
+ lines.push('- Verify the check passes by reviewing the new run');
+ lines.push('');
+ lines.push(
+ 'After addressing everything, push all changes in a single commit (or minimal commits) and call gt_done.'
+ );
+
+ return lines.join('\n');
+}
+
// ════════════════════════════════════════════════════════════════════
// Invariant checker — runs after action application to detect
// violations of the system invariants from spec §6.
diff --git a/cloudflare-gastown/src/dos/town/review-queue.ts b/cloudflare-gastown/src/dos/town/review-queue.ts
index 73cf004a2..4cfbca903 100644
--- a/cloudflare-gastown/src/dos/town/review-queue.ts
+++ b/cloudflare-gastown/src/dos/town/review-queue.ts
@@ -71,7 +71,9 @@ const REVIEW_JOIN = /* sql */ `
SELECT ${beads}.*,
${review_metadata.branch}, ${review_metadata.target_branch},
${review_metadata.merge_commit}, ${review_metadata.pr_url},
- ${review_metadata.retry_count}
+ ${review_metadata.retry_count},
+ ${review_metadata.auto_merge_ready_since},
+ ${review_metadata.last_feedback_check_at}
FROM ${beads}
INNER JOIN ${review_metadata} ON ${beads.bead_id} = ${review_metadata.bead_id}
`;
diff --git a/cloudflare-gastown/src/prompts/polecat-system.prompt.test.ts b/cloudflare-gastown/src/prompts/polecat-system.prompt.test.ts
index c62a4cd9c..2002b9fa9 100644
--- a/cloudflare-gastown/src/prompts/polecat-system.prompt.test.ts
+++ b/cloudflare-gastown/src/prompts/polecat-system.prompt.test.ts
@@ -68,4 +68,40 @@ describe('buildPolecatSystemPrompt', () => {
const prompt = buildPolecatSystemPrompt({ ...params, gates: [] });
expect(prompt).not.toContain('## Pre-Submission Gates');
});
+
+ it('should forbid PR creation when mergeStrategy is not set', () => {
+ const prompt = buildPolecatSystemPrompt(params);
+ expect(prompt).toContain('Do NOT create pull requests');
+ expect(prompt).toContain('Do NOT pass a `pr_url` to `gt_done`');
+ expect(prompt).not.toContain('## Pull Request Creation');
+ });
+
+ it('should forbid PR creation when mergeStrategy is direct', () => {
+ const prompt = buildPolecatSystemPrompt({ ...params, mergeStrategy: 'direct' });
+ expect(prompt).toContain('Do NOT create pull requests');
+ expect(prompt).not.toContain('## Pull Request Creation');
+ });
+
+ it('should include PR creation instructions when mergeStrategy is pr', () => {
+ const prompt = buildPolecatSystemPrompt({
+ ...params,
+ mergeStrategy: 'pr',
+ targetBranch: 'main',
+ });
+ expect(prompt).toContain('## Pull Request Creation');
+ expect(prompt).toContain('gh pr create');
+ expect(prompt).toContain('--base main');
+ expect(prompt).toContain('pr_url');
+ expect(prompt).not.toContain('Do NOT create pull requests');
+ expect(prompt).not.toContain('Do NOT pass a `pr_url` to `gt_done`');
+ });
+
+ it('should use the provided targetBranch in PR creation instructions', () => {
+ const prompt = buildPolecatSystemPrompt({
+ ...params,
+ mergeStrategy: 'pr',
+ targetBranch: 'develop',
+ });
+ expect(prompt).toContain('--base develop');
+ });
});
diff --git a/cloudflare-gastown/src/prompts/polecat-system.prompt.ts b/cloudflare-gastown/src/prompts/polecat-system.prompt.ts
index ea57e1077..9ab42cbf6 100644
--- a/cloudflare-gastown/src/prompts/polecat-system.prompt.ts
+++ b/cloudflare-gastown/src/prompts/polecat-system.prompt.ts
@@ -3,6 +3,9 @@
*
* The prompt establishes identity, available tools, the GUPP principle,
* the done flow, escalation protocol, and commit hygiene.
+ *
+ * When `mergeStrategy` is `'pr'`, the polecat creates a PR before calling
+ * gt_done — the refinery then reviews the PR and adds comments.
*/
export function buildPolecatSystemPrompt(params: {
agentName: string;
@@ -10,6 +13,10 @@ export function buildPolecatSystemPrompt(params: {
townId: string;
identity: string;
gates: string[];
+ /** When set to 'pr', the polecat creates the PR itself and passes pr_url to gt_done. */
+ mergeStrategy?: 'direct' | 'pr';
+ /** Target branch for the PR (e.g. 'main'). Only used when mergeStrategy is 'pr'. */
+ targetBranch?: string;
}): string {
const gatesSection =
params.gates.length > 0
@@ -43,7 +50,7 @@ You have these tools available. Use them to coordinate with the Gastown orchestr
- **gt_prime** — Call at the start of your session to get full context: your agent record, hooked bead, undelivered mail, and open beads. Your context is injected automatically on first message, but call this if you need to refresh.
- **gt_bead_status** — Inspect the current state of any bead by ID.
- **gt_bead_close** — Close a bead when its work is fully complete and merged.
-- **gt_done** — Signal that you are done with your current hooked bead. This pushes your branch, submits it to the review queue, transitions the bead to \`in_review\`, and unhooks you. Always push your branch before calling gt_done.
+- **gt_done** — Signal that you are done with your current hooked bead. Always push your branch before calling gt_done.${params.mergeStrategy === 'pr' ? ' Pass the PR URL you created as the `pr_url` parameter.' : ''}
- **gt_mail_send** — Send a message to another agent in the rig. Use this for coordination, questions, or status sharing.
- **gt_mail_check** — Check for new mail from other agents. Call this periodically or when you suspect coordination messages.
- **gt_escalate** — Escalate a problem you cannot solve. Creates an escalation bead. Use this when you are stuck, blocked, or need human intervention.
@@ -56,8 +63,21 @@ You have these tools available. Use them to coordinate with the Gastown orchestr
2. **Work**: Implement the bead's requirements. Write code, tests, and documentation as needed.
3. **Commit frequently**: Make small, focused commits. Push often. The container's disk is ephemeral — if it restarts, unpushed work is lost.
4. **Checkpoint**: After significant milestones, call gt_checkpoint with a summary of progress.
-5. **Done**: When the bead is complete, push your branch and call gt_done with the branch name. The bead transitions to \`in_review\` and the refinery picks it up for merge. If the review fails (rework), you will be re-dispatched with the bead back in \`in_progress\`.
-${gatesSection}
+5. **Done**: When the bead is complete, push your branch${params.mergeStrategy === 'pr' ? ', create a pull request, and call gt_done with the branch name and the PR URL' : ' and call gt_done with the branch name'}. The bead transitions to \`in_review\` and the refinery reviews it. If the review fails (rework), you will be re-dispatched with the bead back in \`in_progress\`.
+${gatesSection}${params.mergeStrategy === 'pr' ? `
+## Pull Request Creation
+
+After all gates pass and your work is complete, create a pull request before calling gt_done:
+
+1. Push your branch: \`git push origin \`
+2. Create a pull request:
+ - **GitHub:** \`gh pr create --base ${params.targetBranch ?? 'main'} --head --title "" --body ""\`
+ - **GitLab:** \`glab mr create --source-branch --target-branch ${params.targetBranch ?? 'main'} --title "" --description ""\`
+3. Capture the PR/MR URL from the command output.
+4. Call \`gt_done\` with branch="" and pr_url="".
+ - The pr_url MUST be the URL of the created pull request (e.g. \`https://github.com/owner/repo/pull/123\`).
+ - Do NOT use the URL that \`git push\` prints — that is a "create new PR" link, not an existing PR.
+` : ''}
## Commit & Push Hygiene
- Commit after every meaningful unit of work (new function, passing test, config change).
@@ -84,11 +104,14 @@ Periodically call gt_status with a brief, plain-language description of what you
Call gt_status when you START a new meaningful phase of work: beginning a new file, running tests, installing packages, pushing a branch. Do NOT call it on every tool use.
## Important
-
+${params.mergeStrategy === 'pr' ? `
+- Create a pull request after your work is complete and all gates pass. See the "Pull Request Creation" section above.
+- Do NOT merge your branch into the default branch yourself.
+- Do NOT use \`git merge\` to merge into the target branch. Only use \`gh pr create\` or \`glab mr create\`.` : `
- Do NOT create pull requests or merge requests. Your job is to write code on your branch. The Refinery handles merging and PR creation.
- Do NOT merge your branch into the default branch yourself.
- Do NOT use \`gh pr create\`, \`git merge\`, or any equivalent. Just push your branch and call gt_done.
-- Do NOT pass a \`pr_url\` to \`gt_done\`. The URL that \`git push\` prints (e.g. \`https://github.com/.../pull/new/...\`) is NOT a pull request — it is a convenience link for humans. Ignore it.
+- Do NOT pass a \`pr_url\` to \`gt_done\`. The URL that \`git push\` prints (e.g. \`https://github.com/.../pull/new/...\`) is NOT a pull request — it is a convenience link for humans. Ignore it.`}
- Do NOT modify files outside your worktree.
- Do NOT run destructive git operations (force push, hard reset to remote).
- Do NOT install global packages or modify the container environment.
diff --git a/cloudflare-gastown/src/prompts/refinery-system.prompt.ts b/cloudflare-gastown/src/prompts/refinery-system.prompt.ts
index 2f54dde46..b72007dd0 100644
--- a/cloudflare-gastown/src/prompts/refinery-system.prompt.ts
+++ b/cloudflare-gastown/src/prompts/refinery-system.prompt.ts
@@ -15,6 +15,8 @@ export function buildRefinerySystemPrompt(params: {
targetBranch: string;
polecatAgentId: string;
mergeStrategy: MergeStrategy;
+ /** When set, the polecat already created a PR — the refinery reviews it via GitHub. */
+ existingPrUrl?: string;
/** Present when this review is for a bead inside a convoy. */
convoyContext?: {
mergeMode: 'review-then-land' | 'review-and-merge';
@@ -27,13 +29,19 @@ export function buildRefinerySystemPrompt(params: {
? params.gates.map((g, i) => `${i + 1}. \`${g}\``).join('\n')
: '(No quality gates configured — skip to code review)';
+ const convoySection = params.convoyContext ? buildConvoySection(params.convoyContext) : '';
+
+ // When the polecat already created a PR, the refinery reviews it via
+ // GitHub and adds review comments instead of creating a new PR.
+ if (params.existingPrUrl) {
+ return buildPRReviewPrompt({ ...params, prUrl: params.existingPrUrl, gateList, convoySection });
+ }
+
const mergeInstructions =
params.mergeStrategy === 'direct'
? buildDirectMergeInstructions(params)
: buildPRMergeInstructions(params);
- const convoySection = params.convoyContext ? buildConvoySection(params.convoyContext) : '';
-
return `You are the Refinery agent for rig "${params.rigId}" (town "${params.townId}").
Your identity: ${params.identity}
@@ -94,6 +102,98 @@ ${mergeInstructions}
`;
}
+/**
+ * Build the refinery prompt for reviewing an existing PR.
+ *
+ * In this mode, the polecat already created the PR. The refinery reviews
+ * the changes and adds GitHub review comments (approve or request changes).
+ * The auto-resolve system detects unresolved comments and dispatches polecats
+ * to address them, creating a unified feedback loop for both AI and human reviews.
+ */
+function buildPRReviewPrompt(params: {
+ identity: string;
+ rigId: string;
+ townId: string;
+ gates: string[];
+ branch: string;
+ targetBranch: string;
+ polecatAgentId: string;
+ prUrl: string;
+ gateList: string;
+ convoySection: string;
+}): string {
+ return `You are the Refinery agent for rig "${params.rigId}" (town "${params.townId}").
+Your identity: ${params.identity}
+
+## Your Role
+You review pull requests created by polecat agents. You add review comments on the PR via the GitHub/GitLab CLI. You do NOT create PRs — the polecat already created one.
+
+## Current Review
+- **Pull Request:** ${params.prUrl}
+- **Branch:** \`${params.branch}\`
+- **Target branch:** \`${params.targetBranch}\`
+- **Polecat agent ID:** ${params.polecatAgentId}
+${params.convoySection}
+
+## Review Process
+
+### Step 1: Run Quality Gates
+Run these commands in order. If any fail, note the failures for your review.
+
+${params.gateList}
+
+### Step 2: Code Review
+Review the diff on the PR:
+1. Run \`gh pr diff ${params.prUrl}\` or \`git diff ${params.targetBranch}...HEAD\` to see all changes
+2. Check for:
+ - Correctness — does the code do what the bead title/description asked?
+ - Style — consistent with the existing codebase?
+ - Test coverage — are new features tested?
+ - Security — no secrets, no injection vulnerabilities, no unsafe patterns?
+ - Build artifacts — no compiled files, node_modules, or other generated content?
+
+### Step 3: Submit Your Review
+
+**If everything passes (gates + code review):**
+1. Leave a comment on the PR noting that the review passed:
+ \`gh pr comment ${params.prUrl} --body "Refinery code review passed. All quality gates pass."\`
+ Do NOT use \`gh pr review --approve\` — the bot account cannot approve its own PRs.
+2. Call \`gt_done\` with branch="${params.branch}" and pr_url="${params.prUrl}".
+
+**If quality gates fail or code review finds issues:**
+1. Submit a review requesting changes with **specific, actionable inline comments** using the GitHub API:
+ \`\`\`
+ gh api repos/{owner}/{repo}/pulls/{number}/reviews --method POST --input - <<'EOF'
+ {
+ "event": "REQUEST_CHANGES",
+ "body": "",
+ "comments": [
+ {"path": "src/file.ts", "position": 10, "body": ""}
+ ]
+ }
+ EOF
+ \`\`\`
+ Each entry in \`comments\` creates an inline review thread at the specified file and diff position. Prefer inline comments over general body text — they create review threads the system can track and auto-resolve.
+2. Call \`gt_done\` with branch="${params.branch}" and pr_url="${params.prUrl}".
+ The system will detect your unresolved review comments and automatically dispatch a polecat to address them.
+
+## Available Gastown Tools
+- \`gt_prime\` — Get your role context and current assignment
+- \`gt_done\` — Signal your review is complete (pass pr_url="${params.prUrl}")
+- \`gt_escalate\` — Record issues for visibility
+- \`gt_checkpoint\` — Save progress for crash recovery
+
+## Important
+- ALWAYS call \`gt_done\` with pr_url="${params.prUrl}" when you finish — whether you approved or requested changes.
+- Before any git operation, run \`git status\` first to understand the working tree state.
+- Be specific in review comments. "Fix the tests" is not actionable. "Test \`calculateTotal\` in \`tests/cart.test.ts\` fails because the discount logic in \`src/cart.ts:47\` doesn't handle the zero-quantity case" is actionable.
+- Prefer inline PR comments (with file path and line number) over general review body comments. Inline comments create review threads that the system can track and auto-resolve.
+- Do NOT modify the code yourself. Your job is to review and comment — not to fix code.
+- Do NOT merge the PR. The auto-merge system handles merging after all review comments are resolved.
+- If you cannot determine whether the code is correct, escalate with severity "medium" instead of guessing.
+`;
+}
+
function buildConvoySection(ctx: {
mergeMode: 'review-then-land' | 'review-and-merge';
isIntermediateStep: boolean;
diff --git a/cloudflare-gastown/src/types.ts b/cloudflare-gastown/src/types.ts
index 2c0844ecf..0c11e4a04 100644
--- a/cloudflare-gastown/src/types.ts
+++ b/cloudflare-gastown/src/types.ts
@@ -264,6 +264,16 @@ export const TownConfigSchema = z.object({
gates: z.array(z.string()).default([]),
auto_merge: z.boolean().default(true),
require_clean_merge: z.boolean().default(true),
+ /** When enabled, the refinery agent reviews PRs and adds GitHub review
+ * comments. Disable if you use an external code-review bot. */
+ code_review: z.boolean().default(true),
+ /** When enabled, a polecat is automatically dispatched to address
+ * unresolved review comments and failing CI checks on open PRs. */
+ auto_resolve_pr_feedback: z.boolean().default(false),
+ /** After all CI checks pass and all review threads are resolved,
+ * automatically merge the PR after this many minutes.
+ * 0 = immediate, null = disabled (require manual merge). */
+ auto_merge_delay_minutes: z.number().int().min(0).nullable().default(null),
})
.optional(),
@@ -339,6 +349,9 @@ export const TownConfigUpdateSchema = z.object({
gates: z.array(z.string()).optional(),
auto_merge: z.boolean().optional(),
require_clean_merge: z.boolean().optional(),
+ code_review: z.boolean().optional(),
+ auto_resolve_pr_feedback: z.boolean().optional(),
+ auto_merge_delay_minutes: z.number().int().min(0).nullable().optional(),
})
.optional(),
alarm_interval_active: z.number().int().min(5).max(600).optional(),
diff --git a/src/app/(app)/gastown/[townId]/settings/TownSettingsPageClient.tsx b/src/app/(app)/gastown/[townId]/settings/TownSettingsPageClient.tsx
index 07bd79ae6..b82e37c75 100644
--- a/src/app/(app)/gastown/[townId]/settings/TownSettingsPageClient.tsx
+++ b/src/app/(app)/gastown/[townId]/settings/TownSettingsPageClient.tsx
@@ -267,6 +267,9 @@ export function TownSettingsPageClient({ townId, readOnly = false, organizationI
const [maxPolecats, setMaxPolecats] = useState(undefined);
const [refineryGates, setRefineryGates] = useState([]);
const [autoMerge, setAutoMerge] = useState(true);
+ const [refineryCodeReview, setRefineryCodeReview] = useState(true);
+ const [autoResolvePrFeedback, setAutoResolvePrFeedback] = useState(false);
+ const [autoMergeDelayMinutes, setAutoMergeDelayMinutes] = useState(null);
const [mergeStrategy, setMergeStrategy] = useState<'direct' | 'pr'>('direct');
const [stagedConvoysDefault, setStagedConvoysDefault] = useState(false);
const [githubCliPat, setGithubCliPat] = useState('');
@@ -292,6 +295,9 @@ export function TownSettingsPageClient({ townId, readOnly = false, organizationI
setMaxPolecats(cfg.max_polecats_per_rig);
setRefineryGates(cfg.refinery?.gates ?? []);
setAutoMerge(cfg.refinery?.auto_merge ?? true);
+ setRefineryCodeReview(cfg.refinery?.code_review ?? true);
+ setAutoResolvePrFeedback(cfg.refinery?.auto_resolve_pr_feedback ?? false);
+ setAutoMergeDelayMinutes(cfg.refinery?.auto_merge_delay_minutes ?? null);
setMergeStrategy(cfg.merge_strategy === 'pr' ? 'pr' : 'direct');
setStagedConvoysDefault(cfg.staged_convoys_default ?? false);
setGithubCliPat(cfg.github_cli_pat ?? '');
@@ -345,7 +351,10 @@ export function TownSettingsPageClient({ townId, readOnly = false, organizationI
refinery: {
gates: refineryGates.filter(g => g.trim()),
auto_merge: autoMerge,
+ code_review: refineryCodeReview,
require_clean_merge: true,
+ auto_resolve_pr_feedback: autoResolvePrFeedback,
+ auto_merge_delay_minutes: autoMergeDelayMinutes,
},
},
});
@@ -811,6 +820,82 @@ export function TownSettingsPageClient({ townId, readOnly = false, organizationI
+
+
+
+
+
+
+ The refinery agent reviews PRs and adds GitHub review comments. Disable if
+ you already use an external code-review bot.
+
+
+
+
+
+
+
+
+
+ When enabled, a polecat is automatically dispatched to address unresolved
+ review comments and failing CI checks on open PRs.
+
+
+
+
+ {autoResolvePrFeedback && (
+
+
+
+ After all CI checks pass and all review threads are resolved, automatically
+ merge the PR after this delay. Leave empty to require manual merge.
+
+
+
+ {[
+ { label: 'Off', value: null },
+ { label: '0', value: 0 },
+ { label: '15m', value: 15 },
+ { label: '1h', value: 60 },
+ { label: '4h', value: 240 },
+ ].map(preset => (
+
+ ))}
+
+ {autoMergeDelayMinutes !== null && (
+
+ {
+ const v = parseInt(e.target.value, 10);
+ setAutoMergeDelayMinutes(Number.isNaN(v) ? null : Math.max(0, v));
+ }}
+ className="w-20 border-white/[0.08] bg-white/[0.03] text-center font-mono text-xs text-white/85"
+ />
+ minutes
+
+ )}
+
+
+ )}
{/* ── Container ──────────────────────────────────────── */}
diff --git a/src/lib/gastown/types/router.d.ts b/src/lib/gastown/types/router.d.ts
index ce9ece484..a2c9311ed 100644
--- a/src/lib/gastown/types/router.d.ts
+++ b/src/lib/gastown/types/router.d.ts
@@ -1,1386 +1,88 @@
import type { TRPCContext } from './init';
-export declare const gastownRouter: import('@trpc/server').TRPCBuiltRouter<
- {
+export declare const gastownRouter: import("@trpc/server").TRPCBuiltRouter<{
ctx: TRPCContext;
meta: object;
- errorShape: import('@trpc/server').TRPCDefaultErrorShape;
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
transformer: false;
- },
- import('@trpc/server').TRPCDecorateCreateRouterOptions<{
- createTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- name: string;
- };
- output: {
- id: string;
- name: string;
- owner_user_id: string;
- created_at: string;
- updated_at: string;
- };
- meta: object;
- }>;
- listTowns: import('@trpc/server').TRPCQueryProcedure<{
- input: void;
- output: {
- id: string;
- name: string;
- owner_user_id: string;
- created_at: string;
- updated_at: string;
- }[];
- meta: object;
- }>;
- getTown: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- id: string;
- name: string;
- owner_user_id: string;
- created_at: string;
- updated_at: string;
- };
- meta: object;
- }>;
- /**
- * Check whether the current user is an admin viewing a town they don't own.
- * Used by the frontend to show an admin banner.
- */
- checkAdminAccess: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- isAdminViewing: boolean;
- ownerUserId: string | null;
- ownerOrgId: string | null;
- };
- meta: object;
- }>;
- getDrainStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- draining: boolean;
- drainStartedAt: string | null;
- };
- meta: object;
- }>;
- deleteTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- createRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- name: string;
- gitUrl: string;
- defaultBranch?: string | undefined;
- platformIntegrationId?: string | undefined;
- };
- output: {
- id: string;
- town_id: string;
- name: string;
- git_url: string;
- default_branch: string;
- platform_integration_id: string | null;
- created_at: string;
- updated_at: string;
- };
- meta: object;
- }>;
- listRigs: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- id: string;
- town_id: string;
- name: string;
- git_url: string;
- default_branch: string;
- platform_integration_id: string | null;
- created_at: string;
- updated_at: string;
- }[];
- meta: object;
- }>;
- getRig: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- rigId: string;
- townId?: string | undefined;
- };
- output: {
- id: string;
- town_id: string;
- name: string;
- git_url: string;
- default_branch: string;
- platform_integration_id: string | null;
- created_at: string;
- updated_at: string;
- agents: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
- }[];
- beads: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- }[];
- };
- meta: object;
- }>;
- deleteRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- rigId: string;
- };
- output: void;
- meta: object;
- }>;
- listBeads: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- rigId: string;
- townId?: string | undefined;
- status?: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open' | undefined;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- }[];
- meta: object;
- }>;
- deleteBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- rigId: string;
- beadId: string;
- townId?: string | undefined;
- };
- output: void;
- meta: object;
- }>;
- updateBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- rigId: string;
- beadId: string;
- townId?: string | undefined;
- title?: string | undefined;
- body?: string | null | undefined;
- status?: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open' | undefined;
- priority?: 'critical' | 'high' | 'low' | 'medium' | undefined;
- labels?: string[] | undefined;
- metadata?: Record | undefined;
- rig_id?: string | null | undefined;
- parent_bead_id?: string | null | undefined;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- };
- meta: object;
- }>;
- listAgents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- rigId: string;
- townId?: string | undefined;
- };
- output: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
- }[];
- meta: object;
- }>;
- deleteAgent: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- rigId: string;
- agentId: string;
- townId?: string | undefined;
- };
- output: void;
- meta: object;
- }>;
- sling: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- rigId: string;
- title: string;
- body?: string | undefined;
- model?: string | undefined;
- };
- output: {
- bead: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- };
- agent: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
- };
- };
- meta: object;
- }>;
- sendMessage: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- message: string;
- model?: string | undefined;
- rigId?: string | undefined;
- uiContext?: string | undefined;
- };
- output: {
- agentId: string;
- sessionStatus: 'active' | 'idle' | 'starting';
- };
- meta: object;
- }>;
- getMayorStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- configured: boolean;
- townId: string | null;
- session: {
- agentId: string;
- sessionId: string;
- status: 'active' | 'idle' | 'starting';
- lastActivityAt: string;
- } | null;
- };
- meta: object;
- }>;
- getAlarmStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- alarm: {
- nextFireAt: string | null;
- intervalMs: number;
- intervalLabel: string;
- };
- agents: {
- working: number;
- idle: number;
- stalled: number;
- dead: number;
- total: number;
- };
- beads: {
- open: number;
- inProgress: number;
- inReview: number;
- failed: number;
- triageRequests: number;
- };
- patrol: {
- guppWarnings: number;
- guppEscalations: number;
- stalledAgents: number;
- orphanedHooks: number;
- };
- recentEvents: {
- time: string;
- type: string;
- message: string;
- }[];
- };
- meta: object;
- }>;
- ensureMayor: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: {
- agentId: string;
- sessionStatus: 'active' | 'idle' | 'starting';
- };
- meta: object;
- }>;
- getAgentStreamUrl: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- agentId: string;
- townId: string;
- };
- output: {
- url: string;
- ticket: string;
- };
- meta: object;
- }>;
- createPtySession: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- agentId: string;
- };
- output: {
- pty: {
- [x: string]: unknown;
- id: string;
- };
- wsUrl: string;
- };
- meta: object;
- }>;
- resizePtySession: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- agentId: string;
- ptyId: string;
- cols: number;
- rows: number;
- };
- output: void;
- meta: object;
- }>;
- getTownConfig: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- env_vars: Record;
- git_auth: {
- github_token?: string | undefined;
- gitlab_token?: string | undefined;
- gitlab_instance_url?: string | undefined;
- platform_integration_id?: string | undefined;
- };
- owner_user_id?: string | undefined;
- owner_type: 'org' | 'user';
- owner_id?: string | undefined;
- created_by_user_id?: string | undefined;
- organization_id?: string | undefined;
- kilocode_token?: string | undefined;
- default_model?: string | undefined;
- role_models?:
- | {
- mayor?: string | undefined;
- refinery?: string | undefined;
- polecat?: string | undefined;
- }
- | undefined;
- small_model?: string | undefined;
- max_polecats_per_rig?: number | undefined;
- merge_strategy: 'direct' | 'pr';
- refinery?:
- | {
- gates: string[];
- auto_merge: boolean;
- require_clean_merge: boolean;
- }
- | undefined;
- alarm_interval_active?: number | undefined;
- alarm_interval_idle?: number | undefined;
- container?:
- | {
- sleep_after_minutes?: number | undefined;
- }
- | undefined;
- staged_convoys_default: boolean;
- github_cli_pat?: string | undefined;
- git_author_name?: string | undefined;
- git_author_email?: string | undefined;
- disable_ai_coauthor: boolean;
- };
- meta: object;
- }>;
- updateTownConfig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- config: {
- env_vars?: Record | undefined;
- git_auth?:
- | {
- github_token?: string | undefined;
- gitlab_token?: string | undefined;
- gitlab_instance_url?: string | undefined;
- platform_integration_id?: string | undefined;
- }
- | undefined;
- owner_user_id?: string | undefined;
- owner_type?: 'org' | 'user' | undefined;
- owner_id?: string | undefined;
- created_by_user_id?: string | undefined;
- organization_id?: string | undefined;
- kilocode_token?: string | undefined;
- default_model?: string | undefined;
- role_models?:
- | {
- mayor?: string | undefined;
- refinery?: string | undefined;
- polecat?: string | undefined;
- }
- | undefined;
- small_model?: string | undefined;
- max_polecats_per_rig?: number | undefined;
- merge_strategy?: 'direct' | 'pr' | undefined;
- refinery?:
- | {
- gates?: string[] | undefined;
- auto_merge?: boolean | undefined;
- require_clean_merge?: boolean | undefined;
- }
- | undefined;
- alarm_interval_active?: number | undefined;
- alarm_interval_idle?: number | undefined;
- container?:
- | {
- sleep_after_minutes?: number | undefined;
- }
- | undefined;
- staged_convoys_default?: boolean | undefined;
- github_cli_pat?: string | undefined;
- git_author_name?: string | undefined;
- git_author_email?: string | undefined;
- disable_ai_coauthor?: boolean | undefined;
- };
- };
- output: {
- env_vars: Record;
- git_auth: {
- github_token?: string | undefined;
- gitlab_token?: string | undefined;
- gitlab_instance_url?: string | undefined;
- platform_integration_id?: string | undefined;
- };
- owner_user_id?: string | undefined;
- owner_type: 'org' | 'user';
- owner_id?: string | undefined;
- created_by_user_id?: string | undefined;
- organization_id?: string | undefined;
- kilocode_token?: string | undefined;
- default_model?: string | undefined;
- role_models?:
- | {
- mayor?: string | undefined;
- refinery?: string | undefined;
- polecat?: string | undefined;
- }
- | undefined;
- small_model?: string | undefined;
- max_polecats_per_rig?: number | undefined;
- merge_strategy: 'direct' | 'pr';
- refinery?:
- | {
- gates: string[];
- auto_merge: boolean;
- require_clean_merge: boolean;
- }
- | undefined;
- alarm_interval_active?: number | undefined;
- alarm_interval_idle?: number | undefined;
- container?:
- | {
- sleep_after_minutes?: number | undefined;
- }
- | undefined;
- staged_convoys_default: boolean;
- github_cli_pat?: string | undefined;
- git_author_name?: string | undefined;
- git_author_email?: string | undefined;
- disable_ai_coauthor: boolean;
- };
- meta: object;
- }>;
- refreshContainerToken: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- forceRestartContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- destroyContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- getBeadEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- rigId: string;
- townId?: string | undefined;
- beadId?: string | undefined;
- since?: string | undefined;
- limit?: number | undefined;
- };
- output: {
- bead_event_id: string;
- bead_id: string;
- agent_id: string | null;
- event_type: string;
- old_value: string | null;
- new_value: string | null;
- metadata: Record;
- created_at: string;
- rig_id?: string | undefined;
- rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- getTownEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- since?: string | undefined;
- limit?: number | undefined;
- };
- output: {
- bead_event_id: string;
- bead_id: string;
- agent_id: string | null;
- event_type: string;
- old_value: string | null;
- new_value: string | null;
- metadata: Record;
- created_at: string;
- rig_id?: string | undefined;
- rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- getMergeQueueData: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- rigId?: string | undefined;
- limit?: number | undefined;
- since?: string | undefined;
- };
- output: {
- needsAttention: {
- openPRs: {
- mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
- reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
- };
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- failedReviews: {
- mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
- reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
- };
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- stalePRs: {
- mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
- reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
- };
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- };
- activityLog: {
- event: {
- bead_event_id: string;
- bead_id: string;
- agent_id: string | null;
- event_type: string;
- old_value: string | null;
- new_value: string | null;
- metadata: Record;
- created_at: string;
- };
- mrBead: {
- bead_id: string;
- title: string;
- type: string;
- status: string;
- rig_id: string | null;
- metadata: Record;
- } | null;
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- reviewMetadata: {
- pr_url: string | null;
- branch: string | null;
- target_branch: string | null;
- merge_commit: string | null;
- } | null;
- }[];
- };
- meta: object;
- }>;
- listConvoys: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- id: string;
- title: string;
- status: 'active' | 'landed';
- staged: boolean;
- total_beads: number;
- closed_beads: number;
- created_by: string | null;
- created_at: string;
- landed_at: string | null;
- feature_branch: string | null;
- merge_mode: string | null;
- beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
- }[];
- dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
- }[];
- }[];
- meta: object;
- }>;
- getConvoy: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- convoyId: string;
- };
- output: {
- id: string;
- title: string;
- status: 'active' | 'landed';
- staged: boolean;
- total_beads: number;
- closed_beads: number;
- created_by: string | null;
- created_at: string;
- landed_at: string | null;
- feature_branch: string | null;
- merge_mode: string | null;
- beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
- }[];
- dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
- }[];
- } | null;
- meta: object;
- }>;
- closeConvoy: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- convoyId: string;
- };
- output: {
- id: string;
- title: string;
- status: 'active' | 'landed';
- staged: boolean;
- total_beads: number;
- closed_beads: number;
- created_by: string | null;
- created_at: string;
- landed_at: string | null;
- feature_branch: string | null;
- merge_mode: string | null;
- beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
- }[];
- dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
- }[];
- } | null;
- meta: object;
- }>;
- startConvoy: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- convoyId: string;
- };
- output: {
- id: string;
- title: string;
- status: 'active' | 'landed';
- staged: boolean;
- total_beads: number;
- closed_beads: number;
- created_by: string | null;
- created_at: string;
- landed_at: string | null;
- feature_branch: string | null;
- merge_mode: string | null;
- beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
- }[];
- dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
- }[];
- } | null;
- meta: object;
- }>;
- listOrgTowns: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- organizationId: string;
- };
- output: {
- id: string;
- name: string;
- owner_org_id: string;
- created_by_user_id: string;
- created_at: string;
- updated_at: string;
- }[];
- meta: object;
- }>;
- createOrgTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- organizationId: string;
- name: string;
- };
- output: {
- id: string;
- name: string;
- owner_org_id: string;
- created_by_user_id: string;
- created_at: string;
- updated_at: string;
- };
- meta: object;
- }>;
- deleteOrgTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- organizationId: string;
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- listOrgRigs: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- organizationId: string;
- townId: string;
- };
- output: {
- id: string;
- town_id: string;
- name: string;
- git_url: string;
- default_branch: string;
- platform_integration_id: string | null;
- created_at: string;
- updated_at: string;
- }[];
- meta: object;
- }>;
- createOrgRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- organizationId: string;
- townId: string;
- name: string;
- gitUrl: string;
- defaultBranch?: string | undefined;
- platformIntegrationId?: string | undefined;
- };
- output: {
- id: string;
- town_id: string;
- name: string;
- git_url: string;
- default_branch: string;
- platform_integration_id: string | null;
- created_at: string;
- updated_at: string;
- };
- meta: object;
- }>;
- adminListBeads: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- status?: 'closed' | 'failed' | 'in_progress' | 'open' | undefined;
- type?:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule'
- | undefined;
- limit?: number | undefined;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- }[];
- meta: object;
- }>;
- adminListAgents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
- }[];
- meta: object;
- }>;
- adminForceRestartContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- };
- output: void;
- meta: object;
- }>;
- adminForceResetAgent: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- agentId: string;
- };
- output: void;
- meta: object;
- }>;
- adminForceCloseBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- beadId: string;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- };
- meta: object;
- }>;
- adminForceFailBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
- townId: string;
- beadId: string;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- };
- meta: object;
- }>;
- adminGetAlarmStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- alarm: {
- nextFireAt: string | null;
- intervalMs: number;
- intervalLabel: string;
- };
- agents: {
- working: number;
- idle: number;
- stalled: number;
- dead: number;
- total: number;
- };
- beads: {
- open: number;
- inProgress: number;
- inReview: number;
- failed: number;
- triageRequests: number;
- };
- patrol: {
- guppWarnings: number;
- guppEscalations: number;
- stalledAgents: number;
- orphanedHooks: number;
- };
- recentEvents: {
- time: string;
- type: string;
- message: string;
- }[];
- };
- meta: object;
- }>;
- adminGetTownEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- beadId?: string | undefined;
- since?: string | undefined;
- limit?: number | undefined;
- };
- output: {
- bead_event_id: string;
- bead_id: string;
- agent_id: string | null;
- event_type: string;
- old_value: string | null;
- new_value: string | null;
- metadata: Record;
- created_at: string;
- rig_id?: string | undefined;
- rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- adminGetBead: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- beadId: string;
- };
- output: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
- } | null;
- meta: object;
- }>;
- debugAgentMetadata: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: never;
- meta: object;
- }>;
- }>
->;
-export type GastownRouter = typeof gastownRouter;
-/**
- * Wrapped router that nests gastownRouter under a `gastown` key.
- * This preserves the `trpc.gastown.X` call pattern on the frontend,
- * matching the existing RootRouter shape so components don't need
- * to change their procedure paths.
- */
-export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRouter<
- {
- ctx: TRPCContext;
- meta: object;
- errorShape: import('@trpc/server').TRPCDefaultErrorShape;
- transformer: false;
- },
- import('@trpc/server').TRPCDecorateCreateRouterOptions<{
- gastown: import('@trpc/server').TRPCBuiltRouter<
- {
- ctx: TRPCContext;
- meta: object;
- errorShape: import('@trpc/server').TRPCDefaultErrorShape;
- transformer: false;
- },
- import('@trpc/server').TRPCDecorateCreateRouterOptions<{
- createTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
+ createTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
name: string;
- };
- output: {
+ };
+ output: {
id: string;
name: string;
owner_user_id: string;
created_at: string;
updated_at: string;
- };
- meta: object;
- }>;
- listTowns: import('@trpc/server').TRPCQueryProcedure<{
- input: void;
- output: {
+ };
+ meta: object;
+ }>;
+ listTowns: import("@trpc/server").TRPCQueryProcedure<{
+ input: void;
+ output: {
id: string;
name: string;
owner_user_id: string;
created_at: string;
updated_at: string;
- }[];
- meta: object;
- }>;
- getTown: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ getTown: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
id: string;
name: string;
owner_user_id: string;
created_at: string;
updated_at: string;
- };
- meta: object;
- }>;
- /**
- * Check whether the current user is an admin viewing a town they don't own.
- * Used by the frontend to show an admin banner.
- */
- checkAdminAccess: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ getDrainStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ draining: boolean;
+ drainStartedAt: string | null;
+ };
+ meta: object;
+ }>;
+ /**
+ * Check whether the current user is an admin viewing a town they don't own.
+ * Used by the frontend to show an admin banner.
+ */
+ checkAdminAccess: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
isAdminViewing: boolean;
ownerUserId: string | null;
ownerOrgId: string | null;
- };
- meta: object;
- }>;
- getDrainStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
- townId: string;
- };
- output: {
- draining: boolean;
- drainStartedAt: string | null;
- };
- meta: object;
- }>;
- deleteTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ deleteTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: void;
- meta: object;
- }>;
- createRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ createRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
name: string;
gitUrl: string;
defaultBranch?: string | undefined;
platformIntegrationId?: string | undefined;
- };
- output: {
+ };
+ output: {
id: string;
town_id: string;
name: string;
@@ -1389,14 +91,14 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
platform_integration_id: string | null;
created_at: string;
updated_at: string;
- };
- meta: object;
- }>;
- listRigs: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ listRigs: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
id: string;
town_id: string;
name: string;
@@ -1405,15 +107,15 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
platform_integration_id: string | null;
created_at: string;
updated_at: string;
- }[];
- meta: object;
- }>;
- getRig: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ getRig: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
rigId: string;
townId?: string | undefined;
- };
- output: {
+ };
+ output: {
id: string;
town_id: string;
name: string;
@@ -1423,141 +125,120 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
updated_at: string;
agents: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
}[];
beads: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
}[];
- };
- meta: object;
- }>;
- deleteRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ deleteRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
rigId: string;
- };
- output: void;
- meta: object;
- }>;
- listBeads: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ listBeads: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
rigId: string;
townId?: string | undefined;
- status?: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open' | undefined;
- };
- output: {
+ status?: "closed" | "failed" | "in_progress" | "in_review" | "open" | undefined;
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- }[];
- meta: object;
- }>;
- deleteBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ deleteBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
rigId: string;
beadId: string;
townId?: string | undefined;
- };
- output: void;
- meta: object;
- }>;
- updateBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ updateBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
rigId: string;
beadId: string;
townId?: string | undefined;
title?: string | undefined;
body?: string | null | undefined;
- status?: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open' | undefined;
- priority?: 'critical' | 'high' | 'low' | 'medium' | undefined;
+ status?: "closed" | "failed" | "in_progress" | "in_review" | "open" | undefined;
+ priority?: "critical" | "high" | "low" | "medium" | undefined;
labels?: string[] | undefined;
metadata?: Record | undefined;
rig_id?: string | null | undefined;
parent_bead_id?: string | null | undefined;
- };
- output: {
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- };
- meta: object;
- }>;
- listAgents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ listAgents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
rigId: string;
townId?: string | undefined;
- };
- output: {
+ };
+ output: {
id: string;
rig_id: string | null;
role: string;
@@ -1571,361 +252,343 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
agent_status_message: string | null;
agent_status_updated_at: string | null;
- }[];
- meta: object;
- }>;
- deleteAgent: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ deleteAgent: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
rigId: string;
agentId: string;
townId?: string | undefined;
- };
- output: void;
- meta: object;
- }>;
- sling: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ sling: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
rigId: string;
title: string;
body?: string | undefined;
model?: string | undefined;
- };
- output: {
+ };
+ output: {
bead: {
- bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
- title: string;
- body: string | null;
- rig_id: string | null;
- parent_bead_id: string | null;
- assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
- labels: string[];
- metadata: Record;
- created_by: string | null;
- created_at: string;
- updated_at: string;
- closed_at: string | null;
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
};
agent: {
- id: string;
- rig_id: string | null;
- role: string;
- name: string;
- identity: string;
- status: string;
- current_hook_bead_id: string | null;
- dispatch_attempts: number;
- last_activity_at: string | null;
- checkpoint?: unknown;
- created_at: string;
- agent_status_message: string | null;
- agent_status_updated_at: string | null;
- };
- };
- meta: object;
- }>;
- sendMessage: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
+ };
+ };
+ meta: object;
+ }>;
+ sendMessage: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
message: string;
model?: string | undefined;
rigId?: string | undefined;
uiContext?: string | undefined;
- };
- output: {
+ };
+ output: {
agentId: string;
- sessionStatus: 'active' | 'idle' | 'starting';
- };
- meta: object;
- }>;
- getMayorStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ sessionStatus: "active" | "idle" | "starting";
+ };
+ meta: object;
+ }>;
+ getMayorStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
configured: boolean;
townId: string | null;
session: {
- agentId: string;
- sessionId: string;
- status: 'active' | 'idle' | 'starting';
- lastActivityAt: string;
+ agentId: string;
+ sessionId: string;
+ status: "active" | "idle" | "starting";
+ lastActivityAt: string;
} | null;
- };
- meta: object;
- }>;
- getAlarmStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ getAlarmStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
alarm: {
- nextFireAt: string | null;
- intervalMs: number;
- intervalLabel: string;
+ nextFireAt: string | null;
+ intervalMs: number;
+ intervalLabel: string;
};
agents: {
- working: number;
- idle: number;
- stalled: number;
- dead: number;
- total: number;
+ working: number;
+ idle: number;
+ stalled: number;
+ dead: number;
+ total: number;
};
beads: {
- open: number;
- inProgress: number;
- inReview: number;
- failed: number;
- triageRequests: number;
+ open: number;
+ inProgress: number;
+ inReview: number;
+ failed: number;
+ triageRequests: number;
};
patrol: {
- guppWarnings: number;
- guppEscalations: number;
- stalledAgents: number;
- orphanedHooks: number;
+ guppWarnings: number;
+ guppEscalations: number;
+ stalledAgents: number;
+ orphanedHooks: number;
};
recentEvents: {
- time: string;
- type: string;
- message: string;
+ time: string;
+ type: string;
+ message: string;
}[];
- };
- meta: object;
- }>;
- ensureMayor: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ ensureMayor: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
agentId: string;
- sessionStatus: 'active' | 'idle' | 'starting';
- };
- meta: object;
- }>;
- getAgentStreamUrl: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ sessionStatus: "active" | "idle" | "starting";
+ };
+ meta: object;
+ }>;
+ getAgentStreamUrl: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
agentId: string;
townId: string;
- };
- output: {
+ };
+ output: {
url: string;
ticket: string;
- };
- meta: object;
- }>;
- createPtySession: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ createPtySession: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
agentId: string;
- };
- output: {
+ };
+ output: {
pty: {
- [x: string]: unknown;
- id: string;
+ [x: string]: unknown;
+ id: string;
};
wsUrl: string;
- };
- meta: object;
- }>;
- resizePtySession: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ resizePtySession: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
agentId: string;
ptyId: string;
cols: number;
rows: number;
- };
- output: void;
- meta: object;
- }>;
- getTownConfig: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ getTownConfig: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
env_vars: Record;
git_auth: {
- github_token?: string | undefined;
- gitlab_token?: string | undefined;
- gitlab_instance_url?: string | undefined;
- platform_integration_id?: string | undefined;
+ github_token?: string | undefined;
+ gitlab_token?: string | undefined;
+ gitlab_instance_url?: string | undefined;
+ platform_integration_id?: string | undefined;
};
owner_user_id?: string | undefined;
- owner_type: 'org' | 'user';
+ owner_type: "org" | "user";
owner_id?: string | undefined;
created_by_user_id?: string | undefined;
organization_id?: string | undefined;
kilocode_token?: string | undefined;
default_model?: string | undefined;
- role_models?:
- | {
- mayor?: string | undefined;
- refinery?: string | undefined;
- polecat?: string | undefined;
- }
- | undefined;
+ role_models?: {
+ mayor?: string | undefined;
+ refinery?: string | undefined;
+ polecat?: string | undefined;
+ } | undefined;
small_model?: string | undefined;
max_polecats_per_rig?: number | undefined;
- merge_strategy: 'direct' | 'pr';
- refinery?:
- | {
- gates: string[];
- auto_merge: boolean;
- require_clean_merge: boolean;
- }
- | undefined;
+ merge_strategy: "direct" | "pr";
+ refinery?: {
+ gates: string[];
+ auto_merge: boolean;
+ require_clean_merge: boolean;
+ code_review: boolean;
+ auto_resolve_pr_feedback: boolean;
+ auto_merge_delay_minutes: number | null;
+ } | undefined;
alarm_interval_active?: number | undefined;
alarm_interval_idle?: number | undefined;
- container?:
- | {
- sleep_after_minutes?: number | undefined;
- }
- | undefined;
+ container?: {
+ sleep_after_minutes?: number | undefined;
+ } | undefined;
staged_convoys_default: boolean;
github_cli_pat?: string | undefined;
git_author_name?: string | undefined;
git_author_email?: string | undefined;
disable_ai_coauthor: boolean;
- };
- meta: object;
- }>;
- updateTownConfig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ updateTownConfig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
config: {
- env_vars?: Record | undefined;
- git_auth?:
- | {
+ env_vars?: Record | undefined;
+ git_auth?: {
github_token?: string | undefined;
gitlab_token?: string | undefined;
gitlab_instance_url?: string | undefined;
platform_integration_id?: string | undefined;
- }
- | undefined;
- owner_user_id?: string | undefined;
- owner_type?: 'org' | 'user' | undefined;
- owner_id?: string | undefined;
- created_by_user_id?: string | undefined;
- organization_id?: string | undefined;
- kilocode_token?: string | undefined;
- default_model?: string | undefined;
- role_models?:
- | {
+ } | undefined;
+ owner_user_id?: string | undefined;
+ owner_type?: "org" | "user" | undefined;
+ owner_id?: string | undefined;
+ created_by_user_id?: string | undefined;
+ organization_id?: string | undefined;
+ kilocode_token?: string | undefined;
+ default_model?: string | undefined;
+ role_models?: {
mayor?: string | undefined;
refinery?: string | undefined;
polecat?: string | undefined;
- }
- | undefined;
- small_model?: string | undefined;
- max_polecats_per_rig?: number | undefined;
- merge_strategy?: 'direct' | 'pr' | undefined;
- refinery?:
- | {
+ } | undefined;
+ small_model?: string | undefined;
+ max_polecats_per_rig?: number | undefined;
+ merge_strategy?: "direct" | "pr" | undefined;
+ refinery?: {
gates?: string[] | undefined;
auto_merge?: boolean | undefined;
require_clean_merge?: boolean | undefined;
- }
- | undefined;
- alarm_interval_active?: number | undefined;
- alarm_interval_idle?: number | undefined;
- container?:
- | {
+ code_review?: boolean | undefined;
+ auto_resolve_pr_feedback?: boolean | undefined;
+ auto_merge_delay_minutes?: number | null | undefined;
+ } | undefined;
+ alarm_interval_active?: number | undefined;
+ alarm_interval_idle?: number | undefined;
+ container?: {
sleep_after_minutes?: number | undefined;
- }
- | undefined;
- staged_convoys_default?: boolean | undefined;
- github_cli_pat?: string | undefined;
- git_author_name?: string | undefined;
- git_author_email?: string | undefined;
- disable_ai_coauthor?: boolean | undefined;
- };
- };
- output: {
+ } | undefined;
+ staged_convoys_default?: boolean | undefined;
+ github_cli_pat?: string | undefined;
+ git_author_name?: string | undefined;
+ git_author_email?: string | undefined;
+ disable_ai_coauthor?: boolean | undefined;
+ };
+ };
+ output: {
env_vars: Record;
git_auth: {
- github_token?: string | undefined;
- gitlab_token?: string | undefined;
- gitlab_instance_url?: string | undefined;
- platform_integration_id?: string | undefined;
+ github_token?: string | undefined;
+ gitlab_token?: string | undefined;
+ gitlab_instance_url?: string | undefined;
+ platform_integration_id?: string | undefined;
};
owner_user_id?: string | undefined;
- owner_type: 'org' | 'user';
+ owner_type: "org" | "user";
owner_id?: string | undefined;
created_by_user_id?: string | undefined;
organization_id?: string | undefined;
kilocode_token?: string | undefined;
default_model?: string | undefined;
- role_models?:
- | {
- mayor?: string | undefined;
- refinery?: string | undefined;
- polecat?: string | undefined;
- }
- | undefined;
+ role_models?: {
+ mayor?: string | undefined;
+ refinery?: string | undefined;
+ polecat?: string | undefined;
+ } | undefined;
small_model?: string | undefined;
max_polecats_per_rig?: number | undefined;
- merge_strategy: 'direct' | 'pr';
- refinery?:
- | {
- gates: string[];
- auto_merge: boolean;
- require_clean_merge: boolean;
- }
- | undefined;
+ merge_strategy: "direct" | "pr";
+ refinery?: {
+ gates: string[];
+ auto_merge: boolean;
+ require_clean_merge: boolean;
+ code_review: boolean;
+ auto_resolve_pr_feedback: boolean;
+ auto_merge_delay_minutes: number | null;
+ } | undefined;
alarm_interval_active?: number | undefined;
alarm_interval_idle?: number | undefined;
- container?:
- | {
- sleep_after_minutes?: number | undefined;
- }
- | undefined;
+ container?: {
+ sleep_after_minutes?: number | undefined;
+ } | undefined;
staged_convoys_default: boolean;
github_cli_pat?: string | undefined;
git_author_name?: string | undefined;
git_author_email?: string | undefined;
disable_ai_coauthor: boolean;
- };
- meta: object;
- }>;
- refreshContainerToken: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ refreshContainerToken: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: void;
- meta: object;
- }>;
- forceRestartContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ forceRestartContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: void;
- meta: object;
- }>;
- destroyContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ destroyContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: void;
- meta: object;
- }>;
- getBeadEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ getBeadEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
rigId: string;
townId?: string | undefined;
beadId?: string | undefined;
since?: string | undefined;
limit?: number | undefined;
- };
- output: {
+ };
+ output: {
bead_event_id: string;
bead_id: string;
agent_id: string | null;
@@ -1936,16 +599,16 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
rig_id?: string | undefined;
rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- getTownEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ getTownEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
since?: string | undefined;
limit?: number | undefined;
- };
- output: {
+ };
+ output: {
bead_event_id: string;
bead_id: string;
agent_id: string | null;
@@ -1956,198 +619,198 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
rig_id?: string | undefined;
rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- getMergeQueueData: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ getMergeQueueData: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
rigId?: string | undefined;
limit?: number | undefined;
since?: string | undefined;
- };
- output: {
+ };
+ output: {
needsAttention: {
- openPRs: {
- mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
- reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
+ openPRs: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ failedReviews: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ stalePRs: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ };
+ activityLog: {
+ event: {
+ bead_event_id: string;
+ bead_id: string;
+ agent_id: string | null;
+ event_type: string;
+ old_value: string | null;
+ new_value: string | null;
+ metadata: Record;
+ created_at: string;
};
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- failedReviews: {
mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
- reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
- };
+ bead_id: string;
+ title: string;
+ type: string;
+ status: string;
+ rig_id: string | null;
+ metadata: Record;
+ } | null;
sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
+ bead_id: string;
+ title: string;
+ status: string;
} | null;
convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
} | null;
agent: {
- agent_id: string;
- name: string;
- role: string;
+ agent_id: string;
+ name: string;
+ role: string;
} | null;
rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- stalePRs: {
- mrBead: {
- bead_id: string;
- status: string;
- title: string;
- body: string | null;
- rig_id: string | null;
- created_at: string;
- updated_at: string;
- metadata: Record;
- };
reviewMetadata: {
- branch: string;
- target_branch: string;
- merge_commit: string | null;
- pr_url: string | null;
- retry_count: number;
- };
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- body: string | null;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
+ pr_url: string | null;
+ branch: string | null;
+ target_branch: string | null;
+ merge_commit: string | null;
} | null;
- rigName: string | null;
- staleSince: string | null;
- failureReason: string | null;
- }[];
- };
- activityLog: {
- event: {
- bead_event_id: string;
- bead_id: string;
- agent_id: string | null;
- event_type: string;
- old_value: string | null;
- new_value: string | null;
- metadata: Record;
- created_at: string;
- };
- mrBead: {
- bead_id: string;
- title: string;
- type: string;
- status: string;
- rig_id: string | null;
- metadata: Record;
- } | null;
- sourceBead: {
- bead_id: string;
- title: string;
- status: string;
- } | null;
- convoy: {
- convoy_id: string;
- title: string;
- total_beads: number;
- closed_beads: number;
- feature_branch: string | null;
- merge_mode: string | null;
- } | null;
- agent: {
- agent_id: string;
- name: string;
- role: string;
- } | null;
- rigName: string | null;
- reviewMetadata: {
- pr_url: string | null;
- branch: string | null;
- target_branch: string | null;
- merge_commit: string | null;
- } | null;
}[];
- };
- meta: object;
- }>;
- listConvoys: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ listConvoys: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
id: string;
title: string;
- status: 'active' | 'landed';
+ status: "active" | "landed";
staged: boolean;
total_beads: number;
closed_beads: number;
@@ -2157,28 +820,28 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
feature_branch: string | null;
merge_mode: string | null;
beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
}[];
dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
+ bead_id: string;
+ depends_on_bead_id: string;
}[];
- }[];
- meta: object;
- }>;
- getConvoy: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ getConvoy: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
convoyId: string;
- };
- output: {
+ };
+ output: {
id: string;
title: string;
- status: 'active' | 'landed';
+ status: "active" | "landed";
staged: boolean;
total_beads: number;
closed_beads: number;
@@ -2188,28 +851,28 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
feature_branch: string | null;
merge_mode: string | null;
beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
}[];
dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
+ bead_id: string;
+ depends_on_bead_id: string;
}[];
- } | null;
- meta: object;
- }>;
- closeConvoy: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ } | null;
+ meta: object;
+ }>;
+ closeConvoy: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
convoyId: string;
- };
- output: {
+ };
+ output: {
id: string;
title: string;
- status: 'active' | 'landed';
+ status: "active" | "landed";
staged: boolean;
total_beads: number;
closed_beads: number;
@@ -2219,28 +882,28 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
feature_branch: string | null;
merge_mode: string | null;
beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
}[];
dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
+ bead_id: string;
+ depends_on_bead_id: string;
}[];
- } | null;
- meta: object;
- }>;
- startConvoy: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ } | null;
+ meta: object;
+ }>;
+ startConvoy: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
convoyId: string;
- };
- output: {
+ };
+ output: {
id: string;
title: string;
- status: 'active' | 'landed';
+ status: "active" | "landed";
staged: boolean;
total_beads: number;
closed_beads: number;
@@ -2250,62 +913,62 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
feature_branch: string | null;
merge_mode: string | null;
beads: {
- bead_id: string;
- title: string;
- status: string;
- rig_id: string | null;
- assignee_agent_name: string | null;
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
}[];
dependency_edges: {
- bead_id: string;
- depends_on_bead_id: string;
+ bead_id: string;
+ depends_on_bead_id: string;
}[];
- } | null;
- meta: object;
- }>;
- listOrgTowns: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ } | null;
+ meta: object;
+ }>;
+ listOrgTowns: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
organizationId: string;
- };
- output: {
+ };
+ output: {
id: string;
name: string;
owner_org_id: string;
created_by_user_id: string;
created_at: string;
updated_at: string;
- }[];
- meta: object;
- }>;
- createOrgTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ createOrgTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
organizationId: string;
name: string;
- };
- output: {
+ };
+ output: {
id: string;
name: string;
owner_org_id: string;
created_by_user_id: string;
created_at: string;
updated_at: string;
- };
- meta: object;
- }>;
- deleteOrgTown: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ deleteOrgTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
organizationId: string;
townId: string;
- };
- output: void;
- meta: object;
- }>;
- listOrgRigs: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ listOrgRigs: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
organizationId: string;
townId: string;
- };
- output: {
+ };
+ output: {
id: string;
town_id: string;
name: string;
@@ -2314,19 +977,19 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
platform_integration_id: string | null;
created_at: string;
updated_at: string;
- }[];
- meta: object;
- }>;
- createOrgRig: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ createOrgRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
organizationId: string;
townId: string;
name: string;
gitUrl: string;
defaultBranch?: string | undefined;
platformIntegrationId?: string | undefined;
- };
- output: {
+ };
+ output: {
id: string;
town_id: string;
name: string;
@@ -2335,55 +998,40 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
platform_integration_id: string | null;
created_at: string;
updated_at: string;
- };
- meta: object;
- }>;
- adminListBeads: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ adminListBeads: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- status?: 'closed' | 'failed' | 'in_progress' | 'open' | undefined;
- type?:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule'
- | undefined;
+ status?: "closed" | "failed" | "in_progress" | "open" | undefined;
+ type?: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule" | undefined;
limit?: number | undefined;
- };
- output: {
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- }[];
- meta: object;
- }>;
- adminListAgents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ adminListAgents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
id: string;
rig_id: string | null;
role: string;
@@ -2397,132 +1045,118 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
agent_status_message: string | null;
agent_status_updated_at: string | null;
- }[];
- meta: object;
- }>;
- adminForceRestartContainer: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ adminForceRestartContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
- };
- output: void;
- meta: object;
- }>;
- adminForceResetAgent: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ adminForceResetAgent: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
agentId: string;
- };
- output: void;
- meta: object;
- }>;
- adminForceCloseBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ output: void;
+ meta: object;
+ }>;
+ adminForceCloseBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
beadId: string;
- };
- output: {
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- };
- meta: object;
- }>;
- adminForceFailBead: import('@trpc/server').TRPCMutationProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ adminForceFailBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
townId: string;
beadId: string;
- };
- output: {
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- };
- meta: object;
- }>;
- adminGetAlarmStatus: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ adminGetAlarmStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: {
+ };
+ output: {
alarm: {
- nextFireAt: string | null;
- intervalMs: number;
- intervalLabel: string;
+ nextFireAt: string | null;
+ intervalMs: number;
+ intervalLabel: string;
};
agents: {
- working: number;
- idle: number;
- stalled: number;
- dead: number;
- total: number;
+ working: number;
+ idle: number;
+ stalled: number;
+ dead: number;
+ total: number;
};
beads: {
- open: number;
- inProgress: number;
- inReview: number;
- failed: number;
- triageRequests: number;
+ open: number;
+ inProgress: number;
+ inReview: number;
+ failed: number;
+ triageRequests: number;
};
patrol: {
- guppWarnings: number;
- guppEscalations: number;
- stalledAgents: number;
- orphanedHooks: number;
+ guppWarnings: number;
+ guppEscalations: number;
+ stalledAgents: number;
+ orphanedHooks: number;
};
recentEvents: {
- time: string;
- type: string;
- message: string;
+ time: string;
+ type: string;
+ message: string;
}[];
- };
- meta: object;
- }>;
- adminGetTownEvents: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ };
+ meta: object;
+ }>;
+ adminGetTownEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
beadId?: string | undefined;
since?: string | undefined;
limit?: number | undefined;
- };
- output: {
+ };
+ output: {
bead_event_id: string;
bead_id: string;
agent_id: string | null;
@@ -2533,49 +1167,1256 @@ export declare const wrappedGastownRouter: import('@trpc/server').TRPCBuiltRoute
created_at: string;
rig_id?: string | undefined;
rig_name?: string | undefined;
- }[];
- meta: object;
- }>;
- adminGetBead: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ }[];
+ meta: object;
+ }>;
+ adminGetBead: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
beadId: string;
- };
- output: {
+ };
+ output: {
bead_id: string;
- type:
- | 'agent'
- | 'convoy'
- | 'escalation'
- | 'issue'
- | 'merge_request'
- | 'message'
- | 'molecule';
- status: 'closed' | 'failed' | 'in_progress' | 'in_review' | 'open';
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
title: string;
body: string | null;
rig_id: string | null;
parent_bead_id: string | null;
assignee_agent_bead_id: string | null;
- priority: 'critical' | 'high' | 'low' | 'medium';
+ priority: "critical" | "high" | "low" | "medium";
labels: string[];
metadata: Record;
created_by: string | null;
created_at: string;
updated_at: string;
closed_at: string | null;
- } | null;
- meta: object;
- }>;
- debugAgentMetadata: import('@trpc/server').TRPCQueryProcedure<{
- input: {
+ } | null;
+ meta: object;
+ }>;
+ debugAgentMetadata: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
townId: string;
- };
- output: never;
- meta: object;
+ };
+ output: never;
+ meta: object;
+ }>;
+}>>;
+export type GastownRouter = typeof gastownRouter;
+/**
+ * Wrapped router that nests gastownRouter under a `gastown` key.
+ * This preserves the `trpc.gastown.X` call pattern on the frontend,
+ * matching the existing RootRouter shape so components don't need
+ * to change their procedure paths.
+ */
+export declare const wrappedGastownRouter: import("@trpc/server").TRPCBuiltRouter<{
+ ctx: TRPCContext;
+ meta: object;
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
+ transformer: false;
+}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
+ gastown: import("@trpc/server").TRPCBuiltRouter<{
+ ctx: TRPCContext;
+ meta: object;
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
+ transformer: false;
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
+ createTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ name: string;
+ };
+ output: {
+ id: string;
+ name: string;
+ owner_user_id: string;
+ created_at: string;
+ updated_at: string;
+ };
+ meta: object;
+ }>;
+ listTowns: import("@trpc/server").TRPCQueryProcedure<{
+ input: void;
+ output: {
+ id: string;
+ name: string;
+ owner_user_id: string;
+ created_at: string;
+ updated_at: string;
+ }[];
+ meta: object;
+ }>;
+ getTown: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ id: string;
+ name: string;
+ owner_user_id: string;
+ created_at: string;
+ updated_at: string;
+ };
+ meta: object;
+ }>;
+ getDrainStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ draining: boolean;
+ drainStartedAt: string | null;
+ };
+ meta: object;
+ }>;
+ /**
+ * Check whether the current user is an admin viewing a town they don't own.
+ * Used by the frontend to show an admin banner.
+ */
+ checkAdminAccess: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ isAdminViewing: boolean;
+ ownerUserId: string | null;
+ ownerOrgId: string | null;
+ };
+ meta: object;
+ }>;
+ deleteTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ createRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ name: string;
+ gitUrl: string;
+ defaultBranch?: string | undefined;
+ platformIntegrationId?: string | undefined;
+ };
+ output: {
+ id: string;
+ town_id: string;
+ name: string;
+ git_url: string;
+ default_branch: string;
+ platform_integration_id: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ meta: object;
+ }>;
+ listRigs: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ id: string;
+ town_id: string;
+ name: string;
+ git_url: string;
+ default_branch: string;
+ platform_integration_id: string | null;
+ created_at: string;
+ updated_at: string;
+ }[];
+ meta: object;
+ }>;
+ getRig: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ rigId: string;
+ townId?: string | undefined;
+ };
+ output: {
+ id: string;
+ town_id: string;
+ name: string;
+ git_url: string;
+ default_branch: string;
+ platform_integration_id: string | null;
+ created_at: string;
+ updated_at: string;
+ agents: {
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
+ }[];
+ beads: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ }[];
+ };
+ meta: object;
+ }>;
+ deleteRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ rigId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ listBeads: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ rigId: string;
+ townId?: string | undefined;
+ status?: "closed" | "failed" | "in_progress" | "in_review" | "open" | undefined;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ }[];
+ meta: object;
+ }>;
+ deleteBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ rigId: string;
+ beadId: string;
+ townId?: string | undefined;
+ };
+ output: void;
+ meta: object;
+ }>;
+ updateBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ rigId: string;
+ beadId: string;
+ townId?: string | undefined;
+ title?: string | undefined;
+ body?: string | null | undefined;
+ status?: "closed" | "failed" | "in_progress" | "in_review" | "open" | undefined;
+ priority?: "critical" | "high" | "low" | "medium" | undefined;
+ labels?: string[] | undefined;
+ metadata?: Record | undefined;
+ rig_id?: string | null | undefined;
+ parent_bead_id?: string | null | undefined;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ };
+ meta: object;
+ }>;
+ listAgents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ rigId: string;
+ townId?: string | undefined;
+ };
+ output: {
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
+ }[];
+ meta: object;
+ }>;
+ deleteAgent: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ rigId: string;
+ agentId: string;
+ townId?: string | undefined;
+ };
+ output: void;
+ meta: object;
+ }>;
+ sling: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ rigId: string;
+ title: string;
+ body?: string | undefined;
+ model?: string | undefined;
+ };
+ output: {
+ bead: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ };
+ agent: {
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
+ };
+ };
+ meta: object;
+ }>;
+ sendMessage: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ message: string;
+ model?: string | undefined;
+ rigId?: string | undefined;
+ uiContext?: string | undefined;
+ };
+ output: {
+ agentId: string;
+ sessionStatus: "active" | "idle" | "starting";
+ };
+ meta: object;
+ }>;
+ getMayorStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ configured: boolean;
+ townId: string | null;
+ session: {
+ agentId: string;
+ sessionId: string;
+ status: "active" | "idle" | "starting";
+ lastActivityAt: string;
+ } | null;
+ };
+ meta: object;
+ }>;
+ getAlarmStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ alarm: {
+ nextFireAt: string | null;
+ intervalMs: number;
+ intervalLabel: string;
+ };
+ agents: {
+ working: number;
+ idle: number;
+ stalled: number;
+ dead: number;
+ total: number;
+ };
+ beads: {
+ open: number;
+ inProgress: number;
+ inReview: number;
+ failed: number;
+ triageRequests: number;
+ };
+ patrol: {
+ guppWarnings: number;
+ guppEscalations: number;
+ stalledAgents: number;
+ orphanedHooks: number;
+ };
+ recentEvents: {
+ time: string;
+ type: string;
+ message: string;
+ }[];
+ };
+ meta: object;
+ }>;
+ ensureMayor: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ agentId: string;
+ sessionStatus: "active" | "idle" | "starting";
+ };
+ meta: object;
+ }>;
+ getAgentStreamUrl: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ agentId: string;
+ townId: string;
+ };
+ output: {
+ url: string;
+ ticket: string;
+ };
+ meta: object;
+ }>;
+ createPtySession: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ agentId: string;
+ };
+ output: {
+ pty: {
+ [x: string]: unknown;
+ id: string;
+ };
+ wsUrl: string;
+ };
+ meta: object;
+ }>;
+ resizePtySession: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ agentId: string;
+ ptyId: string;
+ cols: number;
+ rows: number;
+ };
+ output: void;
+ meta: object;
+ }>;
+ getTownConfig: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ env_vars: Record;
+ git_auth: {
+ github_token?: string | undefined;
+ gitlab_token?: string | undefined;
+ gitlab_instance_url?: string | undefined;
+ platform_integration_id?: string | undefined;
+ };
+ owner_user_id?: string | undefined;
+ owner_type: "org" | "user";
+ owner_id?: string | undefined;
+ created_by_user_id?: string | undefined;
+ organization_id?: string | undefined;
+ kilocode_token?: string | undefined;
+ default_model?: string | undefined;
+ role_models?: {
+ mayor?: string | undefined;
+ refinery?: string | undefined;
+ polecat?: string | undefined;
+ } | undefined;
+ small_model?: string | undefined;
+ max_polecats_per_rig?: number | undefined;
+ merge_strategy: "direct" | "pr";
+ refinery?: {
+ gates: string[];
+ auto_merge: boolean;
+ require_clean_merge: boolean;
+ code_review: boolean;
+ auto_resolve_pr_feedback: boolean;
+ auto_merge_delay_minutes: number | null;
+ } | undefined;
+ alarm_interval_active?: number | undefined;
+ alarm_interval_idle?: number | undefined;
+ container?: {
+ sleep_after_minutes?: number | undefined;
+ } | undefined;
+ staged_convoys_default: boolean;
+ github_cli_pat?: string | undefined;
+ git_author_name?: string | undefined;
+ git_author_email?: string | undefined;
+ disable_ai_coauthor: boolean;
+ };
+ meta: object;
+ }>;
+ updateTownConfig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ config: {
+ env_vars?: Record | undefined;
+ git_auth?: {
+ github_token?: string | undefined;
+ gitlab_token?: string | undefined;
+ gitlab_instance_url?: string | undefined;
+ platform_integration_id?: string | undefined;
+ } | undefined;
+ owner_user_id?: string | undefined;
+ owner_type?: "org" | "user" | undefined;
+ owner_id?: string | undefined;
+ created_by_user_id?: string | undefined;
+ organization_id?: string | undefined;
+ kilocode_token?: string | undefined;
+ default_model?: string | undefined;
+ role_models?: {
+ mayor?: string | undefined;
+ refinery?: string | undefined;
+ polecat?: string | undefined;
+ } | undefined;
+ small_model?: string | undefined;
+ max_polecats_per_rig?: number | undefined;
+ merge_strategy?: "direct" | "pr" | undefined;
+ refinery?: {
+ gates?: string[] | undefined;
+ auto_merge?: boolean | undefined;
+ require_clean_merge?: boolean | undefined;
+ code_review?: boolean | undefined;
+ auto_resolve_pr_feedback?: boolean | undefined;
+ auto_merge_delay_minutes?: number | null | undefined;
+ } | undefined;
+ alarm_interval_active?: number | undefined;
+ alarm_interval_idle?: number | undefined;
+ container?: {
+ sleep_after_minutes?: number | undefined;
+ } | undefined;
+ staged_convoys_default?: boolean | undefined;
+ github_cli_pat?: string | undefined;
+ git_author_name?: string | undefined;
+ git_author_email?: string | undefined;
+ disable_ai_coauthor?: boolean | undefined;
+ };
+ };
+ output: {
+ env_vars: Record;
+ git_auth: {
+ github_token?: string | undefined;
+ gitlab_token?: string | undefined;
+ gitlab_instance_url?: string | undefined;
+ platform_integration_id?: string | undefined;
+ };
+ owner_user_id?: string | undefined;
+ owner_type: "org" | "user";
+ owner_id?: string | undefined;
+ created_by_user_id?: string | undefined;
+ organization_id?: string | undefined;
+ kilocode_token?: string | undefined;
+ default_model?: string | undefined;
+ role_models?: {
+ mayor?: string | undefined;
+ refinery?: string | undefined;
+ polecat?: string | undefined;
+ } | undefined;
+ small_model?: string | undefined;
+ max_polecats_per_rig?: number | undefined;
+ merge_strategy: "direct" | "pr";
+ refinery?: {
+ gates: string[];
+ auto_merge: boolean;
+ require_clean_merge: boolean;
+ code_review: boolean;
+ auto_resolve_pr_feedback: boolean;
+ auto_merge_delay_minutes: number | null;
+ } | undefined;
+ alarm_interval_active?: number | undefined;
+ alarm_interval_idle?: number | undefined;
+ container?: {
+ sleep_after_minutes?: number | undefined;
+ } | undefined;
+ staged_convoys_default: boolean;
+ github_cli_pat?: string | undefined;
+ git_author_name?: string | undefined;
+ git_author_email?: string | undefined;
+ disable_ai_coauthor: boolean;
+ };
+ meta: object;
+ }>;
+ refreshContainerToken: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ forceRestartContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ destroyContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ getBeadEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ rigId: string;
+ townId?: string | undefined;
+ beadId?: string | undefined;
+ since?: string | undefined;
+ limit?: number | undefined;
+ };
+ output: {
+ bead_event_id: string;
+ bead_id: string;
+ agent_id: string | null;
+ event_type: string;
+ old_value: string | null;
+ new_value: string | null;
+ metadata: Record;
+ created_at: string;
+ rig_id?: string | undefined;
+ rig_name?: string | undefined;
+ }[];
+ meta: object;
+ }>;
+ getTownEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ since?: string | undefined;
+ limit?: number | undefined;
+ };
+ output: {
+ bead_event_id: string;
+ bead_id: string;
+ agent_id: string | null;
+ event_type: string;
+ old_value: string | null;
+ new_value: string | null;
+ metadata: Record;
+ created_at: string;
+ rig_id?: string | undefined;
+ rig_name?: string | undefined;
+ }[];
+ meta: object;
+ }>;
+ getMergeQueueData: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ rigId?: string | undefined;
+ limit?: number | undefined;
+ since?: string | undefined;
+ };
+ output: {
+ needsAttention: {
+ openPRs: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ failedReviews: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ stalePRs: {
+ mrBead: {
+ bead_id: string;
+ status: string;
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ created_at: string;
+ updated_at: string;
+ metadata: Record;
+ };
+ reviewMetadata: {
+ branch: string;
+ target_branch: string;
+ merge_commit: string | null;
+ pr_url: string | null;
+ retry_count: number;
+ };
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ body: string | null;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ staleSince: string | null;
+ failureReason: string | null;
+ }[];
+ };
+ activityLog: {
+ event: {
+ bead_event_id: string;
+ bead_id: string;
+ agent_id: string | null;
+ event_type: string;
+ old_value: string | null;
+ new_value: string | null;
+ metadata: Record;
+ created_at: string;
+ };
+ mrBead: {
+ bead_id: string;
+ title: string;
+ type: string;
+ status: string;
+ rig_id: string | null;
+ metadata: Record;
+ } | null;
+ sourceBead: {
+ bead_id: string;
+ title: string;
+ status: string;
+ } | null;
+ convoy: {
+ convoy_id: string;
+ title: string;
+ total_beads: number;
+ closed_beads: number;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ } | null;
+ agent: {
+ agent_id: string;
+ name: string;
+ role: string;
+ } | null;
+ rigName: string | null;
+ reviewMetadata: {
+ pr_url: string | null;
+ branch: string | null;
+ target_branch: string | null;
+ merge_commit: string | null;
+ } | null;
+ }[];
+ };
+ meta: object;
+ }>;
+ listConvoys: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ id: string;
+ title: string;
+ status: "active" | "landed";
+ staged: boolean;
+ total_beads: number;
+ closed_beads: number;
+ created_by: string | null;
+ created_at: string;
+ landed_at: string | null;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ beads: {
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
+ }[];
+ dependency_edges: {
+ bead_id: string;
+ depends_on_bead_id: string;
+ }[];
+ }[];
+ meta: object;
+ }>;
+ getConvoy: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ convoyId: string;
+ };
+ output: {
+ id: string;
+ title: string;
+ status: "active" | "landed";
+ staged: boolean;
+ total_beads: number;
+ closed_beads: number;
+ created_by: string | null;
+ created_at: string;
+ landed_at: string | null;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ beads: {
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
+ }[];
+ dependency_edges: {
+ bead_id: string;
+ depends_on_bead_id: string;
+ }[];
+ } | null;
+ meta: object;
+ }>;
+ closeConvoy: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ convoyId: string;
+ };
+ output: {
+ id: string;
+ title: string;
+ status: "active" | "landed";
+ staged: boolean;
+ total_beads: number;
+ closed_beads: number;
+ created_by: string | null;
+ created_at: string;
+ landed_at: string | null;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ beads: {
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
+ }[];
+ dependency_edges: {
+ bead_id: string;
+ depends_on_bead_id: string;
+ }[];
+ } | null;
+ meta: object;
+ }>;
+ startConvoy: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ convoyId: string;
+ };
+ output: {
+ id: string;
+ title: string;
+ status: "active" | "landed";
+ staged: boolean;
+ total_beads: number;
+ closed_beads: number;
+ created_by: string | null;
+ created_at: string;
+ landed_at: string | null;
+ feature_branch: string | null;
+ merge_mode: string | null;
+ beads: {
+ bead_id: string;
+ title: string;
+ status: string;
+ rig_id: string | null;
+ assignee_agent_name: string | null;
+ }[];
+ dependency_edges: {
+ bead_id: string;
+ depends_on_bead_id: string;
+ }[];
+ } | null;
+ meta: object;
+ }>;
+ listOrgTowns: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ organizationId: string;
+ };
+ output: {
+ id: string;
+ name: string;
+ owner_org_id: string;
+ created_by_user_id: string;
+ created_at: string;
+ updated_at: string;
+ }[];
+ meta: object;
+ }>;
+ createOrgTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ organizationId: string;
+ name: string;
+ };
+ output: {
+ id: string;
+ name: string;
+ owner_org_id: string;
+ created_by_user_id: string;
+ created_at: string;
+ updated_at: string;
+ };
+ meta: object;
+ }>;
+ deleteOrgTown: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ organizationId: string;
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ listOrgRigs: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ organizationId: string;
+ townId: string;
+ };
+ output: {
+ id: string;
+ town_id: string;
+ name: string;
+ git_url: string;
+ default_branch: string;
+ platform_integration_id: string | null;
+ created_at: string;
+ updated_at: string;
+ }[];
+ meta: object;
+ }>;
+ createOrgRig: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ organizationId: string;
+ townId: string;
+ name: string;
+ gitUrl: string;
+ defaultBranch?: string | undefined;
+ platformIntegrationId?: string | undefined;
+ };
+ output: {
+ id: string;
+ town_id: string;
+ name: string;
+ git_url: string;
+ default_branch: string;
+ platform_integration_id: string | null;
+ created_at: string;
+ updated_at: string;
+ };
+ meta: object;
+ }>;
+ adminListBeads: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ status?: "closed" | "failed" | "in_progress" | "open" | undefined;
+ type?: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule" | undefined;
+ limit?: number | undefined;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ }[];
+ meta: object;
+ }>;
+ adminListAgents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ id: string;
+ rig_id: string | null;
+ role: string;
+ name: string;
+ identity: string;
+ status: string;
+ current_hook_bead_id: string | null;
+ dispatch_attempts: number;
+ last_activity_at: string | null;
+ checkpoint?: unknown;
+ created_at: string;
+ agent_status_message: string | null;
+ agent_status_updated_at: string | null;
+ }[];
+ meta: object;
+ }>;
+ adminForceRestartContainer: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ adminForceResetAgent: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ agentId: string;
+ };
+ output: void;
+ meta: object;
+ }>;
+ adminForceCloseBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ beadId: string;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ };
+ meta: object;
+ }>;
+ adminForceFailBead: import("@trpc/server").TRPCMutationProcedure<{
+ input: {
+ townId: string;
+ beadId: string;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ };
+ meta: object;
+ }>;
+ adminGetAlarmStatus: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: {
+ alarm: {
+ nextFireAt: string | null;
+ intervalMs: number;
+ intervalLabel: string;
+ };
+ agents: {
+ working: number;
+ idle: number;
+ stalled: number;
+ dead: number;
+ total: number;
+ };
+ beads: {
+ open: number;
+ inProgress: number;
+ inReview: number;
+ failed: number;
+ triageRequests: number;
+ };
+ patrol: {
+ guppWarnings: number;
+ guppEscalations: number;
+ stalledAgents: number;
+ orphanedHooks: number;
+ };
+ recentEvents: {
+ time: string;
+ type: string;
+ message: string;
+ }[];
+ };
+ meta: object;
+ }>;
+ adminGetTownEvents: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ beadId?: string | undefined;
+ since?: string | undefined;
+ limit?: number | undefined;
+ };
+ output: {
+ bead_event_id: string;
+ bead_id: string;
+ agent_id: string | null;
+ event_type: string;
+ old_value: string | null;
+ new_value: string | null;
+ metadata: Record;
+ created_at: string;
+ rig_id?: string | undefined;
+ rig_name?: string | undefined;
+ }[];
+ meta: object;
+ }>;
+ adminGetBead: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ beadId: string;
+ };
+ output: {
+ bead_id: string;
+ type: "agent" | "convoy" | "escalation" | "issue" | "merge_request" | "message" | "molecule";
+ status: "closed" | "failed" | "in_progress" | "in_review" | "open";
+ title: string;
+ body: string | null;
+ rig_id: string | null;
+ parent_bead_id: string | null;
+ assignee_agent_bead_id: string | null;
+ priority: "critical" | "high" | "low" | "medium";
+ labels: string[];
+ metadata: Record;
+ created_by: string | null;
+ created_at: string;
+ updated_at: string;
+ closed_at: string | null;
+ } | null;
+ meta: object;
+ }>;
+ debugAgentMetadata: import("@trpc/server").TRPCQueryProcedure<{
+ input: {
+ townId: string;
+ };
+ output: never;
+ meta: object;
}>;
- }>
- >;
- }>
->;
+ }>>;
+}>>;
export type WrappedGastownRouter = typeof wrappedGastownRouter;
diff --git a/src/lib/gastown/types/schemas.d.ts b/src/lib/gastown/types/schemas.d.ts
index 5ccdf157d..02530a5c3 100644
--- a/src/lib/gastown/types/schemas.d.ts
+++ b/src/lib/gastown/types/schemas.d.ts
@@ -1,16 +1,12 @@
-import { z } from 'zod';
-export declare const TownOutput: z.ZodObject<
- {
+import type { z } from 'zod';
+export declare const TownOutput: z.ZodObject<{
id: z.ZodString;
name: z.ZodString;
owner_user_id: z.ZodString;
created_at: z.ZodString;
updated_at: z.ZodString;
- },
- z.core.$strip
->;
-export declare const RigOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const RigOutput: z.ZodObject<{
id: z.ZodString;
town_id: z.ZodString;
name: z.ZodString;
@@ -19,27 +15,24 @@ export declare const RigOutput: z.ZodObject<
platform_integration_id: z.ZodDefault>>;
created_at: z.ZodString;
updated_at: z.ZodString;
- },
- z.core.$strip
->;
-export declare const BeadOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const BeadOutput: z.ZodObject<{
bead_id: z.ZodString;
type: z.ZodEnum<{
- agent: 'agent';
- convoy: 'convoy';
- escalation: 'escalation';
- issue: 'issue';
- merge_request: 'merge_request';
- message: 'message';
- molecule: 'molecule';
+ agent: "agent";
+ convoy: "convoy";
+ escalation: "escalation";
+ issue: "issue";
+ merge_request: "merge_request";
+ message: "message";
+ molecule: "molecule";
}>;
status: z.ZodEnum<{
- closed: 'closed';
- failed: 'failed';
- in_progress: 'in_progress';
- in_review: 'in_review';
- open: 'open';
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
}>;
title: z.ZodString;
body: z.ZodNullable;
@@ -47,10 +40,10 @@ export declare const BeadOutput: z.ZodObject<
parent_bead_id: z.ZodNullable;
assignee_agent_bead_id: z.ZodNullable;
priority: z.ZodEnum<{
- critical: 'critical';
- high: 'high';
- low: 'low';
- medium: 'medium';
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
}>;
labels: z.ZodArray;
metadata: z.ZodRecord;
@@ -58,36 +51,23 @@ export declare const BeadOutput: z.ZodObject<
created_at: z.ZodString;
updated_at: z.ZodString;
closed_at: z.ZodNullable;
- },
- z.core.$strip
->;
-export declare const AgentOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const AgentOutput: z.ZodObject<{
id: z.ZodString;
rig_id: z.ZodNullable;
- role: z.ZodUnion<
- [
- z.ZodEnum<{
- mayor: 'mayor';
- polecat: 'polecat';
- refinery: 'refinery';
- }>,
- z.ZodString,
- ]
- >;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
name: z.ZodString;
identity: z.ZodString;
- status: z.ZodUnion<
- [
- z.ZodEnum<{
- dead: 'dead';
- idle: 'idle';
- stalled: 'stalled';
- working: 'working';
- }>,
- z.ZodString,
- ]
- >;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
current_hook_bead_id: z.ZodNullable;
dispatch_attempts: z.ZodDefault;
last_activity_at: z.ZodNullable;
@@ -95,11 +75,8 @@ export declare const AgentOutput: z.ZodObject<
created_at: z.ZodString;
agent_status_message: z.ZodDefault>>;
agent_status_updated_at: z.ZodDefault>>;
- },
- z.core.$strip
->;
-export declare const BeadEventOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const BeadEventOutput: z.ZodObject<{
bead_event_id: z.ZodString;
bead_id: z.ZodString;
agent_id: z.ZodNullable;
@@ -110,68 +87,45 @@ export declare const BeadEventOutput: z.ZodObject<
created_at: z.ZodString;
rig_id: z.ZodOptional;
rig_name: z.ZodOptional;
- },
- z.core.$strip
->;
-export declare const MayorSendResultOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const MayorSendResultOutput: z.ZodObject<{
agentId: z.ZodString;
sessionStatus: z.ZodEnum<{
- active: 'active';
- idle: 'idle';
- starting: 'starting';
+ active: "active";
+ idle: "idle";
+ starting: "starting";
}>;
- },
- z.core.$strip
->;
-export declare const MayorStatusOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const MayorStatusOutput: z.ZodObject<{
configured: z.ZodBoolean;
townId: z.ZodNullable;
- session: z.ZodNullable<
- z.ZodObject<
- {
- agentId: z.ZodString;
- sessionId: z.ZodString;
- status: z.ZodEnum<{
- active: 'active';
- idle: 'idle';
- starting: 'starting';
- }>;
- lastActivityAt: z.ZodString;
- },
- z.core.$strip
- >
- >;
- },
- z.core.$strip
->;
-export declare const StreamTicketOutput: z.ZodObject<
- {
+ session: z.ZodNullable;
+ lastActivityAt: z.ZodString;
+ }, z.core.$strip>>;
+}, z.core.$strip>;
+export declare const StreamTicketOutput: z.ZodObject<{
url: z.ZodString;
ticket: z.ZodString;
- },
- z.core.$strip
->;
-export declare const PtySessionOutput: z.ZodObject<
- {
- pty: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const PtySessionOutput: z.ZodObject<{
+ pty: z.ZodObject<{
id: z.ZodString;
- },
- z.core.$loose
- >;
+ }, z.core.$loose>;
wsUrl: z.ZodString;
- },
- z.core.$strip
->;
-export declare const ConvoyOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const ConvoyOutput: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
status: z.ZodEnum<{
- active: 'active';
- landed: 'landed';
+ active: "active";
+ landed: "landed";
}>;
staged: z.ZodBoolean;
total_beads: z.ZodNumber;
@@ -181,16 +135,13 @@ export declare const ConvoyOutput: z.ZodObject<
landed_at: z.ZodNullable;
feature_branch: z.ZodNullable;
merge_mode: z.ZodNullable;
- },
- z.core.$strip
->;
-export declare const ConvoyDetailOutput: z.ZodObject<
- {
+}, z.core.$strip>;
+export declare const ConvoyDetailOutput: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
status: z.ZodEnum<{
- active: 'active';
- landed: 'landed';
+ active: "active";
+ landed: "landed";
}>;
staged: z.ZodBoolean;
total_beads: z.ZodNumber;
@@ -200,50 +151,36 @@ export declare const ConvoyDetailOutput: z.ZodObject<
landed_at: z.ZodNullable;
feature_branch: z.ZodNullable;
merge_mode: z.ZodNullable;
- beads: z.ZodArray<
- z.ZodObject<
- {
- bead_id: z.ZodString;
- title: z.ZodString;
- status: z.ZodString;
- rig_id: z.ZodNullable;
- assignee_agent_name: z.ZodNullable;
- },
- z.core.$strip
- >
- >;
- dependency_edges: z.ZodArray<
- z.ZodObject<
- {
- bead_id: z.ZodString;
- depends_on_bead_id: z.ZodString;
- },
- z.core.$strip
- >
- >;
- },
- z.core.$strip
->;
-export declare const SlingResultOutput: z.ZodObject<
- {
- bead: z.ZodObject<
- {
+ beads: z.ZodArray;
+ assignee_agent_name: z.ZodNullable;
+ }, z.core.$strip>>;
+ dependency_edges: z.ZodArray>;
+}, z.core.$strip>;
+export declare const SlingResultOutput: z.ZodObject<{
+ bead: z.ZodObject<{
bead_id: z.ZodString;
type: z.ZodEnum<{
- agent: 'agent';
- convoy: 'convoy';
- escalation: 'escalation';
- issue: 'issue';
- merge_request: 'merge_request';
- message: 'message';
- molecule: 'molecule';
+ agent: "agent";
+ convoy: "convoy";
+ escalation: "escalation";
+ issue: "issue";
+ merge_request: "merge_request";
+ message: "message";
+ molecule: "molecule";
}>;
status: z.ZodEnum<{
- closed: 'closed';
- failed: 'failed';
- in_progress: 'in_progress';
- in_review: 'in_review';
- open: 'open';
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
}>;
title: z.ZodString;
body: z.ZodNullable;
@@ -251,10 +188,10 @@ export declare const SlingResultOutput: z.ZodObject<
parent_bead_id: z.ZodNullable;
assignee_agent_bead_id: z.ZodNullable;
priority: z.ZodEnum<{
- critical: 'critical';
- high: 'high';
- low: 'low';
- medium: 'medium';
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
}>;
labels: z.ZodArray;
metadata: z.ZodRecord;
@@ -262,36 +199,23 @@ export declare const SlingResultOutput: z.ZodObject<
created_at: z.ZodString;
updated_at: z.ZodString;
closed_at: z.ZodNullable;
- },
- z.core.$strip
- >;
- agent: z.ZodObject<
- {
+ }, z.core.$strip>;
+ agent: z.ZodObject<{
id: z.ZodString;
rig_id: z.ZodNullable;
- role: z.ZodUnion<
- [
- z.ZodEnum<{
- mayor: 'mayor';
- polecat: 'polecat';
- refinery: 'refinery';
- }>,
- z.ZodString,
- ]
- >;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
name: z.ZodString;
identity: z.ZodString;
- status: z.ZodUnion<
- [
- z.ZodEnum<{
- dead: 'dead';
- idle: 'idle';
- stalled: 'stalled';
- working: 'working';
- }>,
- z.ZodString,
- ]
- >;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
current_hook_bead_id: z.ZodNullable;
dispatch_attempts: z.ZodDefault;
last_activity_at: z.ZodNullable;
@@ -299,14 +223,9 @@ export declare const SlingResultOutput: z.ZodObject<
created_at: z.ZodString;
agent_status_message: z.ZodDefault>>;
agent_status_updated_at: z.ZodDefault>>;
- },
- z.core.$strip
- >;
- },
- z.core.$strip
->;
-export declare const RigDetailOutput: z.ZodObject<
- {
+ }, z.core.$strip>;
+}, z.core.$strip>;
+export declare const RigDetailOutput: z.ZodObject<{
id: z.ZodString;
town_id: z.ZodString;
name: z.ZodString;
@@ -315,1185 +234,752 @@ export declare const RigDetailOutput: z.ZodObject<
platform_integration_id: z.ZodDefault>>;
created_at: z.ZodString;
updated_at: z.ZodString;
- agents: z.ZodArray<
- z.ZodObject<
- {
- id: z.ZodString;
- rig_id: z.ZodNullable;
- role: z.ZodUnion<
- [
- z.ZodEnum<{
- mayor: 'mayor';
- polecat: 'polecat';
- refinery: 'refinery';
- }>,
- z.ZodString,
- ]
- >;
- name: z.ZodString;
- identity: z.ZodString;
- status: z.ZodUnion<
- [
- z.ZodEnum<{
- dead: 'dead';
- idle: 'idle';
- stalled: 'stalled';
- working: 'working';
- }>,
- z.ZodString,
- ]
- >;
- current_hook_bead_id: z.ZodNullable;
- dispatch_attempts: z.ZodDefault;
- last_activity_at: z.ZodNullable;
- checkpoint: z.ZodOptional;
- created_at: z.ZodString;
- agent_status_message: z.ZodDefault>>;
- agent_status_updated_at: z.ZodDefault>>;
- },
- z.core.$strip
- >
- >;
- beads: z.ZodArray<
- z.ZodObject<
- {
- bead_id: z.ZodString;
- type: z.ZodEnum<{
- agent: 'agent';
- convoy: 'convoy';
- escalation: 'escalation';
- issue: 'issue';
- merge_request: 'merge_request';
- message: 'message';
- molecule: 'molecule';
- }>;
- status: z.ZodEnum<{
- closed: 'closed';
- failed: 'failed';
- in_progress: 'in_progress';
- in_review: 'in_review';
- open: 'open';
- }>;
- title: z.ZodString;
- body: z.ZodNullable;
- rig_id: z.ZodNullable;
- parent_bead_id: z.ZodNullable;
- assignee_agent_bead_id: z.ZodNullable;
- priority: z.ZodEnum<{
- critical: 'critical';
- high: 'high';
- low: 'low';
- medium: 'medium';
- }>;
- labels: z.ZodArray;
- metadata: z.ZodRecord;
- created_by: z.ZodNullable;
- created_at: z.ZodString;
- updated_at: z.ZodString;
- closed_at: z.ZodNullable;
- },
- z.core.$strip
- >
- >;
- },
- z.core.$strip
->;
-export declare const RpcTownOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- id: z.ZodString;
- name: z.ZodString;
- owner_user_id: z.ZodString;
- created_at: z.ZodString;
- updated_at: z.ZodString;
- },
- z.core.$strip
- >
->;
-export declare const RpcRigOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- id: z.ZodString;
- town_id: z.ZodString;
- name: z.ZodString;
- git_url: z.ZodString;
- default_branch: z.ZodString;
- platform_integration_id: z.ZodDefault>>;
- created_at: z.ZodString;
- updated_at: z.ZodString;
- },
- z.core.$strip
- >
->;
-export declare const RpcBeadOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- bead_id: z.ZodString;
- type: z.ZodEnum<{
- agent: 'agent';
- convoy: 'convoy';
- escalation: 'escalation';
- issue: 'issue';
- merge_request: 'merge_request';
- message: 'message';
- molecule: 'molecule';
- }>;
- status: z.ZodEnum<{
- closed: 'closed';
- failed: 'failed';
- in_progress: 'in_progress';
- in_review: 'in_review';
- open: 'open';
- }>;
- title: z.ZodString;
- body: z.ZodNullable;
- rig_id: z.ZodNullable;
- parent_bead_id: z.ZodNullable;
- assignee_agent_bead_id: z.ZodNullable;
- priority: z.ZodEnum<{
- critical: 'critical';
- high: 'high';
- low: 'low';
- medium: 'medium';
- }>;
- labels: z.ZodArray;
- metadata: z.ZodRecord;
- created_by: z.ZodNullable;
- created_at: z.ZodString;
- updated_at: z.ZodString;
- closed_at: z.ZodNullable;
- },
- z.core.$strip
- >
->;
-export declare const RpcAgentOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- id: z.ZodString;
- rig_id: z.ZodNullable;
- role: z.ZodUnion<
- [
- z.ZodEnum<{
- mayor: 'mayor';
- polecat: 'polecat';
- refinery: 'refinery';
- }>,
- z.ZodString,
- ]
- >;
- name: z.ZodString;
- identity: z.ZodString;
- status: z.ZodUnion<
- [
- z.ZodEnum<{
- dead: 'dead';
- idle: 'idle';
- stalled: 'stalled';
- working: 'working';
- }>,
- z.ZodString,
- ]
- >;
- current_hook_bead_id: z.ZodNullable;
- dispatch_attempts: z.ZodDefault;
- last_activity_at: z.ZodNullable;
- checkpoint: z.ZodOptional;
- created_at: z.ZodString;
- agent_status_message: z.ZodDefault>>;
- agent_status_updated_at: z.ZodDefault>>;
- },
- z.core.$strip
- >
->;
-export declare const RpcBeadEventOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- bead_event_id: z.ZodString;
- bead_id: z.ZodString;
- agent_id: z.ZodNullable;
- event_type: z.ZodString;
- old_value: z.ZodNullable;
- new_value: z.ZodNullable;
- metadata: z.ZodRecord;
- created_at: z.ZodString;
- rig_id: z.ZodOptional;
- rig_name: z.ZodOptional;
- },
- z.core.$strip
- >
->;
-export declare const RpcMayorSendResultOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- agentId: z.ZodString;
- sessionStatus: z.ZodEnum<{
- active: 'active';
- idle: 'idle';
- starting: 'starting';
- }>;
- },
- z.core.$strip
- >
->;
-export declare const RpcMayorStatusOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- configured: z.ZodBoolean;
- townId: z.ZodNullable;
- session: z.ZodNullable<
- z.ZodObject<
- {
- agentId: z.ZodString;
- sessionId: z.ZodString;
- status: z.ZodEnum<{
- active: 'active';
- idle: 'idle';
- starting: 'starting';
- }>;
- lastActivityAt: z.ZodString;
- },
- z.core.$strip
- >
- >;
- },
- z.core.$strip
- >
->;
-export declare const RpcStreamTicketOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- url: z.ZodString;
- ticket: z.ZodString;
- },
- z.core.$strip
- >
->;
-export declare const RpcPtySessionOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- pty: z.ZodObject<
- {
- id: z.ZodString;
- },
- z.core.$loose
- >;
- wsUrl: z.ZodString;
- },
- z.core.$strip
- >
->;
-export declare const RpcConvoyOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- id: z.ZodString;
- title: z.ZodString;
- status: z.ZodEnum<{
- active: 'active';
- landed: 'landed';
- }>;
- staged: z.ZodBoolean;
- total_beads: z.ZodNumber;
- closed_beads: z.ZodNumber;
- created_by: z.ZodNullable;
- created_at: z.ZodString;
- landed_at: z.ZodNullable;
- feature_branch: z.ZodNullable;
- merge_mode: z.ZodNullable;
- },
- z.core.$strip
- >
->;
-export declare const RpcConvoyDetailOutput: z.ZodPipe<
- z.ZodAny,
- z.ZodObject<
- {
- id: z.ZodString;
- title: z.ZodString;
- status: z.ZodEnum<{
- active: 'active';
- landed: 'landed';
- }>;
- staged: z.ZodBoolean;
- total_beads: z.ZodNumber;
- closed_beads: z.ZodNumber;
- created_by: z.ZodNullable;
- created_at: z.ZodString;
- landed_at: z.ZodNullable;
- feature_branch: z.ZodNullable;
- merge_mode: z.ZodNullable;
- beads: z.ZodArray<
- z.ZodObject<
- {
- bead_id: z.ZodString;
- title: z.ZodString;
- status: z.ZodString;
- rig_id: z.ZodNullable;
- assignee_agent_name: z.ZodNullable;
- },
- z.core.$strip
- >
- >;
- dependency_edges: z.ZodArray<
- z.ZodObject<
- {
+ agents: z.ZodArray;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
+ name: z.ZodString;
+ identity: z.ZodString;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
+ current_hook_bead_id: z.ZodNullable;
+ dispatch_attempts: z.ZodDefault;
+ last_activity_at: z.ZodNullable;
+ checkpoint: z.ZodOptional;
+ created_at: z.ZodString;
+ agent_status_message: z.ZodDefault>>;
+ agent_status_updated_at: z.ZodDefault>>;
+ }, z.core.$strip>>;
+ beads: z.ZodArray;
+ status: z.ZodEnum<{
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
+ }>;
+ title: z.ZodString;
+ body: z.ZodNullable;
+ rig_id: z.ZodNullable;
+ parent_bead_id: z.ZodNullable;
+ assignee_agent_bead_id: z.ZodNullable;
+ priority: z.ZodEnum<{
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
+ }>;
+ labels: z.ZodArray;
+ metadata: z.ZodRecord;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ closed_at: z.ZodNullable;
+ }, z.core.$strip>>;
+}, z.core.$strip>;
+export declare const RpcTownOutput: z.ZodPipe>;
+export declare const RpcRigOutput: z.ZodPipe>>;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+}, z.core.$strip>>;
+export declare const RpcBeadOutput: z.ZodPipe;
+ status: z.ZodEnum<{
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
+ }>;
+ title: z.ZodString;
+ body: z.ZodNullable;
+ rig_id: z.ZodNullable;
+ parent_bead_id: z.ZodNullable;
+ assignee_agent_bead_id: z.ZodNullable;
+ priority: z.ZodEnum<{
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
+ }>;
+ labels: z.ZodArray;
+ metadata: z.ZodRecord;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ closed_at: z.ZodNullable;
+}, z.core.$strip>>;
+export declare const RpcAgentOutput: z.ZodPipe;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
+ name: z.ZodString;
+ identity: z.ZodString;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
+ current_hook_bead_id: z.ZodNullable;
+ dispatch_attempts: z.ZodDefault;
+ last_activity_at: z.ZodNullable;
+ checkpoint: z.ZodOptional;
+ created_at: z.ZodString;
+ agent_status_message: z.ZodDefault>>;
+ agent_status_updated_at: z.ZodDefault>>;
+}, z.core.$strip>>;
+export declare const RpcBeadEventOutput: z.ZodPipe;
+ event_type: z.ZodString;
+ old_value: z.ZodNullable;
+ new_value: z.ZodNullable;
+ metadata: z.ZodRecord;
+ created_at: z.ZodString;
+ rig_id: z.ZodOptional;
+ rig_name: z.ZodOptional;
+}, z.core.$strip>>;
+export declare const RpcMayorSendResultOutput: z.ZodPipe;
+}, z.core.$strip>>;
+export declare const RpcMayorStatusOutput: z.ZodPipe;
+ session: z.ZodNullable;
+ lastActivityAt: z.ZodString;
+ }, z.core.$strip>>;
+}, z.core.$strip>>;
+export declare const RpcStreamTicketOutput: z.ZodPipe>;
+export declare const RpcPtySessionOutput: z.ZodPipe;
+ wsUrl: z.ZodString;
+}, z.core.$strip>>;
+export declare const RpcConvoyOutput: z.ZodPipe;
+ staged: z.ZodBoolean;
+ total_beads: z.ZodNumber;
+ closed_beads: z.ZodNumber;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ landed_at: z.ZodNullable;
+ feature_branch: z.ZodNullable;
+ merge_mode: z.ZodNullable;
+}, z.core.$strip>>;
+export declare const RpcConvoyDetailOutput: z.ZodPipe;
+ staged: z.ZodBoolean;
+ total_beads: z.ZodNumber;
+ closed_beads: z.ZodNumber;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ landed_at: z.ZodNullable;
+ feature_branch: z.ZodNullable;
+ merge_mode: z.ZodNullable;
+ beads: z.ZodArray;
+ assignee_agent_name: z.ZodNullable;
+ }, z.core.$strip>>;
+ dependency_edges: z.ZodArray>;
+}, z.core.$strip>>;
+export declare const RpcSlingResultOutput: z.ZodPipe;
+ status: z.ZodEnum<{
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
+ }>;
+ title: z.ZodString;
+ body: z.ZodNullable;
+ rig_id: z.ZodNullable;
+ parent_bead_id: z.ZodNullable;
+ assignee_agent_bead_id: z.ZodNullable;
+ priority: z.ZodEnum<{
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
+ }>;
+ labels: z.ZodArray;
+ metadata: z.ZodRecord;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ closed_at: z.ZodNullable;
+ }, z.core.$strip>;
+ agent: z.ZodObject<{
+ id: z.ZodString;
+ rig_id: z.ZodNullable;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
+ name: z.ZodString;
+ identity: z.ZodString;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
+ current_hook_bead_id: z.ZodNullable;
+ dispatch_attempts: z.ZodDefault;
+ last_activity_at: z.ZodNullable;
+ checkpoint: z.ZodOptional;
+ created_at: z.ZodString;
+ agent_status_message: z.ZodDefault>>;
+ agent_status_updated_at: z.ZodDefault>>;
+ }, z.core.$strip>;
+}, z.core.$strip>>;
+export declare const RpcAlarmStatusOutput: z.ZodPipe;
+ intervalMs: z.ZodNumber;
+ intervalLabel: z.ZodString;
+ }, z.core.$strip>;
+ agents: z.ZodObject<{
+ working: z.ZodNumber;
+ idle: z.ZodNumber;
+ stalled: z.ZodNumber;
+ dead: z.ZodNumber;
+ total: z.ZodNumber;
+ }, z.core.$strip>;
+ beads: z.ZodObject<{
+ open: z.ZodNumber;
+ inProgress: z.ZodNumber;
+ inReview: z.ZodNumber;
+ failed: z.ZodNumber;
+ triageRequests: z.ZodNumber;
+ }, z.core.$strip>;
+ patrol: z.ZodObject<{
+ guppWarnings: z.ZodNumber;
+ guppEscalations: z.ZodNumber;
+ stalledAgents: z.ZodNumber;
+ orphanedHooks: z.ZodNumber;
+ }, z.core.$strip>;
+ recentEvents: z.ZodArray>;
+}, z.core.$strip>>;
+export declare const RpcRigDetailOutput: z.ZodPipe>>;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ agents: z.ZodArray;
+ role: z.ZodUnion<[z.ZodEnum<{
+ mayor: "mayor";
+ polecat: "polecat";
+ refinery: "refinery";
+ }>, z.ZodString]>;
+ name: z.ZodString;
+ identity: z.ZodString;
+ status: z.ZodUnion<[z.ZodEnum<{
+ dead: "dead";
+ idle: "idle";
+ stalled: "stalled";
+ working: "working";
+ }>, z.ZodString]>;
+ current_hook_bead_id: z.ZodNullable;
+ dispatch_attempts: z.ZodDefault;
+ last_activity_at: z.ZodNullable;
+ checkpoint: z.ZodOptional;
+ created_at: z.ZodString;
+ agent_status_message: z.ZodDefault>>;
+ agent_status_updated_at: z.ZodDefault>>;
+ }, z.core.$strip>>;
+ beads: z.ZodArray;
+ status: z.ZodEnum<{
+ closed: "closed";
+ failed: "failed";
+ in_progress: "in_progress";
+ in_review: "in_review";
+ open: "open";
+ }>;
+ title: z.ZodString;
+ body: z.ZodNullable;
+ rig_id: z.ZodNullable;
+ parent_bead_id: z.ZodNullable;
+ assignee_agent_bead_id: z.ZodNullable;
+ priority: z.ZodEnum<{
+ critical: "critical";
+ high: "high";
+ low: "low";
+ medium: "medium";
+ }>;
+ labels: z.ZodArray;
+ metadata: z.ZodRecord;
+ created_by: z.ZodNullable;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ closed_at: z.ZodNullable;
+ }, z.core.$strip>>;
+}, z.core.$strip>>;
+export declare const MergeQueueDataOutput: z.ZodObject<{
+ needsAttention: z.ZodObject<{
+ openPRs: z.ZodArray;
+ rig_id: z.ZodNullable;
+ created_at: z.ZodString;
+ updated_at: z.ZodString;
+ metadata: z.ZodRecord;
+ }, z.core.$strip>;
+ reviewMetadata: z.ZodObject<{
+ branch: z.ZodString;
+ target_branch: z.ZodString;
+ merge_commit: z.ZodNullable;
+ pr_url: z.ZodNullable;
+ retry_count: z.ZodNumber;
+ }, z.core.$strip>;
+ sourceBead: z.ZodNullable