feat(gastown): configurable merge strategy (direct/PR)#701
Conversation
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Other Observations (not in diff)No additional issues found outside the diff. Files Reviewed (75 files)Infrastructure & Build:
Core Backend (Worker):
tRPC Migration:
Auth & Security:
Platform PR/MR:
Container Agent:
Frontend (tRPC migration + merge strategy UI):
Deleted:
Tests:
|
3846180 to
b3274c2
Compare
091068c to
b01e42a
Compare
b01e42a to
8d28245
Compare
8d28245 to
69680fe
Compare
f5a6c64 to
de8771d
Compare
7324fca to
58324af
Compare
58324af to
3fdb05f
Compare
facacdc to
7bc9749
Compare
7bc9749 to
ce705d3
Compare
ce705d3 to
d10c0e8
Compare
d10c0e8 to
b899c21
Compare
b899c21 to
b80f29f
Compare
db8deff to
5dd6a1a
Compare
… review bead fixes - Add configurable merge strategy (direct/PR) for rigs and towns - Refinery agents now handle merging (direct) or PR creation (gh/glab CLI) themselves - Strategy-aware refinery system prompt with explicit merge/PR instructions - Fix review beads missing PR link: write pr_url to beads.metadata on submission - Fix PR beads not closing: validate pr_url is a real PR, add polling diagnostics - Add small_model to TownConfig for configurable lightweight model - Use resolveModel()/resolveSmallModel() throughout instead of hardcoded model strings - All kilo config sub-agent roles derive model from town config - Add PR link display to BeadPanel.tsx drawer - Polecat prompt: clarify not to pass git push convenience URLs as pr_url
- Add kiloAuthMiddleware: validates Kilo user JWTs (NEXTAUTH_SECRET) via verifyKiloToken from @kilocode/worker-utils - Apply kiloAuthMiddleware to user-facing routes (/api/users/*, /api/towns/*/config, /api/towns/*/container/*, /api/towns/*/convoys/*, /api/towns/*/escalations/*, /api/towns/*/mayor/*) - Remove global CF Access middleware from all routes - Replace CF Access validation in WebSocket upgrade handler with Kilo JWT validation - Update gastown-client.ts to send Bearer token (via generateInternalServiceToken) instead of CF-Access-Client-Id/Secret headers - Add NEXTAUTH_SECRET binding to wrangler.jsonc secrets store - Add NEXTAUTH_SECRET to Env type in worker-configuration.d.ts - Keep existing agent JWT auth (GASTOWN_SESSION_TOKEN) for container→worker routes - Health and dashboard endpoints remain unauthenticated
…C proxy The gastown frontend now calls the Cloudflare Worker's tRPC endpoint directly instead of proxying through the Next.js backend. This removes a network hop and lets the worker handle auth end-to-end. Key changes: - Add /api/gastown/token endpoint that mints short-lived JWTs with isAdmin and apiTokenPepper claims for browser→worker auth - Create standalone gastown tRPC client (src/lib/gastown/trpc.ts) with token management, GastownTRPCProvider, and gastownWsUrl helper - Add .output() schemas to all worker tRPC procedures for type-safe generated declarations - Add GIT_TOKEN_SERVICE binding to worker for git credential refresh (replaces server-side refreshTownGitCredentials) - Add configureRig/addRig calls to worker's createRig procedure - Add setTownId calls to ensure TownDO uses correct UUID for containers - Add CORS for /trpc/* routes and always-on kiloAuthMiddleware - Worker requireAdmin now checks JWT claims instead of DB lookup - WebSocket URLs returned as relative paths (frontend constructs full URL) - Update ~25 frontend files: useTRPC → useGastownTRPC - Remove gastown-router.ts, gastown-client.ts, and root-router registration
…robustness - Remove console.log(secret) that leaked NEXTAUTH_SECRET to stdout - Replace raw error logging with sanitized message in kilo-auth middleware - Add verifyRigOwnership check to deleteRig tRPC procedure - Use TownConfigUpdateSchema instead of z.record for updateTownConfig input - Validate GitLab host in checkPRStatus before sending PRIVATE-TOKEN - Handle empty model strings in kiloModel with fallback to kilo/auto - Replace 'as' cast with Zod validation in gastown trpc.ts fetchToken - Add warning log for unrecognized PR URL formats in checkPRStatus - Handle HTTP 422 (duplicate PR) in createGitHubPR by fetching existing PR - Add console.warn for label application failures instead of silent catch - Emit pr_creation_failed event when refinery provides invalid pr_url - Add git status instruction to refinery prompt per reviewer feedback - Use MergeStrategy type import in refinery prompt instead of inline union - Strip workspace: references from dependencies (not just devDependencies) - Remove unused TOKEN_EXPIRY import to fix lint error
- Add SSRF protection to createGitLabMR: validate instanceUrl host against gitlab.com or configured instance URL before sending PRIVATE-TOKEN - Log HTTP error status codes in checkPRStatus for both GitHub and GitLab API responses instead of silently returning null - Add TownConfigUpdateSchema tests verifying no phantom defaults are injected on empty input
4440eb8 to
0bae000
Compare
| // The browser constructs the full ws(s):// URL using the known GASTOWN_URL. | ||
|
|
||
| export function gastownWsUrl(relativePath: string): string { | ||
| const base = new URL(GASTOWN_URL); |
There was a problem hiding this comment.
[WARNING]: new URL(GASTOWN_URL) throws TypeError when NEXT_PUBLIC_GASTOWN_URL is not configured
GASTOWN_URL defaults to '' (empty string) in src/lib/constants.ts:48 when the env var is unset. new URL('') throws TypeError: Invalid URL, crashing every gastownWsUrl() call.
Similarly, gastownTrpcUrl on line 71 would evaluate to /trpc (a relative path), which may not work with httpBatchLink that typically expects an absolute URL.
Consider guarding against an empty GASTOWN_URL:
export function gastownWsUrl(relativePath: string): string {
if (!GASTOWN_URL) throw new Error('NEXT_PUBLIC_GASTOWN_URL is not configured');
const base = new URL(GASTOWN_URL);
...
}Or validate early at module scope so the error surfaces at startup rather than on the first WS connection attempt.
| townId: z.string().uuid(), | ||
| message: z.string().min(1), | ||
| model: z.string().default('anthropic/claude-sonnet-4.6'), | ||
| rigId: z.string().uuid().optional(), |
There was a problem hiding this comment.
[SUGGESTION]: Unused input field — rigId is declared but never read
rigId is accepted in the sendMessage input schema but is not passed to townStub.sendMayorMessage(input.message, input.model). If it's intended for future use, consider removing it now and adding it when the mayor actually uses it. Dead schema fields create confusion about what the procedure actually depends on.
Summary
Implements #473 — Configurable Merge Strategy, a sub-issue of #204.
Video walkthrough: https://storage.j0.hn/gastown-merge-strategy-1.mp4
merge_strategyconfig field (direct|pr) at the town and rig level, with rig-level overrides inheriting from the town defaultpr, the Refinery creates a GitHub PR or GitLab MR instead of pushing directly to the default branch, enabling human review before agent work landsChanges by area
Schema & Config
MergeStrategyZod enum andmerge_strategyfield onTownConfigSchema(defaults todirect)merge_strategyoverride onRigConfigresolveMergeStrategy()helper: rig override → town default →directPR/MR Creation (
platform-pr.util.ts— new)parseGitUrl()— extracts platform/owner/repo from HTTPS and SSH git URLscreateGitHubPR()/createGitLabMR()— REST API calls with labelsbuildPRBody()— Markdown body with quality gate results and agent attributionReview Queue & Town DO
setReviewPrUrl(),markReviewInReview(),listPendingPRReviews()on review queueexecuteMergeStrategy()dispatches to direct merge or PR creationtriggerPRCreation()creates the PR and tracks the URL on the MR beadpollPendingPRs()+checkPRStatus()poll GitHub/GitLab APIs for PR statepr_createdandpr_creation_failedbead event typesRefinery Prompt
mergeStrategyparam; whenpr, instructs the refinery to push the branch and callgt_done(TownDO creates the PR) instead of merging itselfDashboard UI
BeadDetailDrawerDev Infrastructure
192.168.65.254) instead ofhost.docker.internalwhich doesn't work on wrangler'sworkerd-network0.0.0.0so containers can reach the workercatalog:references in Dockerfiles for bun compatibilityresolveJWTSecretTests — 23 unit tests covering merge strategy resolution, git URL parsing, and PR body generation
How to test
merge_strategy: "pr"in town settingsCloses #473