Skip to content

chore(gastown): promote gastown-staging to main#2835

Open
jrf0110 wants to merge 4 commits intomainfrom
gastown-staging
Open

chore(gastown): promote gastown-staging to main#2835
jrf0110 wants to merge 4 commits intomainfrom
gastown-staging

Conversation

@jrf0110
Copy link
Copy Markdown
Contributor

@jrf0110 jrf0110 commented Apr 27, 2026

Summary

Promotes the current state of gastown-staging into main. This PR rolls up 2 PRs that were reviewed and merged into the staging branch since it last diverged from main.

Constituent PRs

Verification

  • All constituent PRs were reviewed and CI-green before merging to gastown-staging
  • Deployed to the staging environment and verified working

jrf0110 and others added 3 commits April 27, 2026 14:14
* feat(gastown): add createBead/startBead/enrichBead tRPC procedures and gt:held reconciler exclusion (#2772)

* feat(gastown): add createBead/startBead/enrichBead tRPC procedures and gt:held reconciler exclusion

- Add HELD_LABEL and HELD_LABEL_LIKE constants to patrol.ts
- Exclude gt:held beads from reconciler Rule 1 dispatch
- Add createHeldBead(), notifyMayorOfNewBead(), startHeldBead() to TownDO
- Add createBead, startBead, enrichBead tRPC procedures to router
- Update sling procedure to accept optional labels
- Add create-bead-ui-convoy.md shared context for convoy polecats

* fix(gastown): verify bead rig ownership in startHeldBead and use waitUntil for mayor notification

- CRITICAL: startHeldBead now accepts a rigId and throws if the bead does not belong to that rig, preventing cross-rig gt:held label removal within the same town
- WARNING: mayor notification in createBead uses ctx.executionCtx.waitUntil() so the Worker stays alive until the RPC completes; add executionCtx to TRPCContext and pass it from the Hono createContext callback

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* feat(gastown): add CreateBeadDrawer with MDXEditor and AI enrichment (#2773)

* feat(gastown): add CreateBeadDrawer with MDXEditor and AI enrichment

- Install @mdxeditor/editor for rich markdown editing
- Add MarkdownEditor.tsx wrapper with dark theme CSS overrides
- Add CreateBeadDrawer.tsx: right-side vaul drawer (620px) with:
  - Title input (AI-populated, respects user edits via userEditedTitle flag)
  - Label chips with AI-suggested sparkle labels and manual add
  - MDXEditor body (dynamic import, min-height 300px)
  - Start immediately checkbox (default: held for mayor planning)
  - 1500ms debounced enrichBead mutation on body > 20 chars
  - Toast on success: 'Work dispatched' vs 'Bead created — notifying mayor'
- Replace SlingDialog with CreateBeadDrawer in RigDetailPageClient
- Add createBead, startBead, enrichBead to router.d.ts type declarations

* fix: discard stale AI enrichment responses using sequence counter

Track enrichment request sequence numbers so that older in-flight
responses cannot overwrite newer suggestions or repopulate a closed
drawer with stale title/labels. The sequence is advanced on each
new request and also when the drawer closes, causing any pending
callbacks to bail out early on completion.

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* feat(gastown): update mayor system prompt to handle held bead notifications (#2774)

* feat(gastown): update mayor system prompt to handle held bead notifications

- Update notifyMayorOfNewBead() message to match spec exactly, including
  explicit instruction to create a message bead with parent_bead_id so
  the response surfaces in the bead drawer
- Add '## User-Created Beads' section to mayor system prompt with guidance
  on acknowledging beads, offering planning options, and using gt_sling /
  gt_bead_update to respond and start work
- Update create-bead-ui-convoy.md with Bead 3 notes for future implementors

gt_bead_update in mayor-tools.ts already exposes both labels and body — no
changes needed there.

* fix(gastown): correct user-created bead reply instructions in mayor prompt

- Remove unsupported gt_sling({ type: 'message', parent_bead_id }) call;
  gt_sling only creates issue-type beads and does not accept type or
  parent_bead_id fields — this would fail validation at runtime.
- Fix gt_bead_update label removal: labels: [] replaces the full label
  set. Instruct the mayor to filter out only gt:held from current labels
  to avoid dropping other labels like gt:pr-fixup etc.

* fix: align notifyMayorOfNewBead message with system prompt - remove contradictory gt_sling message bead instruction

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* fix: move plan to .plans/ and remove unsafe as cast in router

* feat(gastown): surface held beads and mayor responses in rig UI (#2775)

* feat(gastown): surface held beads and mayor responses in rig UI

- BeadBoard: show Clock icon and amber 'Held' badge for gt:held beads,
  hide the gt:held label from the label chips, add hover 'Start now'
  quick-action button wired to onStartBead prop
- RigDetailPageClient: wire startBead mutation and pass onStartBead to BeadBoard
- BeadPanel: detect gt:held label, render amber 'Held' status badge and
  'Start now' button that calls startBead tRPC mutation; render mayor
  responses (type='message' child beads) as a conversation thread above
  the event timeline

* chore: delete create-bead-ui-convoy.md (final convoy bead)

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* fix(gastown): address PR #2776 review — title-edit race and startImmediately return

- CreateBeadDrawer: read userEditedTitle via a ref in the enrichment
  response callback so edits made after the request starts win over the
  AI-suggested title.
- createBead tRPC: when startImmediately is set, return the post-start
  bead from startHeldBead() so the gt:held label removal and status
  changes are reflected in the mutation result.

* fix(gastown): use MDXEditor dark-theme for readable toolbar icons

The markdown editor toolbar icons were rendering as dark-gray-on-gray
because MDXEditor's default light theme drives SVG color via
--baseTextContrast (near-black), which clashed with our dark drawer
background. Switch to the library's built-in dark-theme class to swap
the full Radix color palette in one shot, and drop the brittle
hashed-class-fragment overrides that tried to re-derive it.

* fix(gastown): wrap Clock icon with span for title tooltip

lucide-react icons don't accept a `title` prop (SVGAttributes). Wrap
the held-bead Clock icon in a span carrying the tooltip, and keep an
aria-label on the icon itself for screen readers.

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>
* fix(gastown): reduce TownContainer sleepAfter from 30m to 10m (#2828)

Cut idle container cost ~3x. Cold start is ~10-30s so users won't notice;
real work triggers wake via ensureContainerReady as before.

Co-authored-by: John Fawcett <john@kilcoode.ai>

* fix(gastown): stop keeping container awake for waiting mayor + refresh token on warm-send (#2829)

* fix(gastown): stop keeping container awake for waiting mayor + refresh token on warm-send

- Remove 'waiting' from mayorAlive in refreshContainerToken so a mayor
  left in 'waiting' no longer triggers hourly /refresh-token pings that
  reset sleepAfter and keep the container awake forever (#1409).
- Call dispatch.ensureContainerToken on the warm-send mayor path in
  _sendMayorMessage before sendMessageToAgent. Closes the silent-401
  failure class for mayors that were waiting longer than the 8h token
  expiry when the user sends a new message.

* fix(gastown): tolerate ensureContainerToken failures on warm-send path

ensureContainerToken throws on non-2xx /refresh-token responses. On the
warm mayor send path, that would abort _sendMayorMessage before
sendMessageToAgent runs, dropping the user's prompt on a transient
refresh failure. Wrap the call in try/catch and log — the stored envVar
fallback and next alarm tick will recover the token.

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* fix(gastown): drop alarm cadence for long-stalled agents; auto-idle after 2h30m (#2830)

* fix(gastown): drop alarm cadence for long-stalled agents; auto-idle after 2h30m

hasActiveWork() no longer counts 'stalled' agents older than 30 minutes as
active work, so towns with only stuck stalled rows drop from the 5s fast
alarm cadence to the 5m idle cadence (60x fewer /status pings per agent
per hour).

reconcileAgents() now emits a stalled->idle + unhook transition for any
agent whose last_activity_at is older than 2h 30min, closing the cleanup
loop when the container_status: exited|not_found event never fires
(hard crash, /status stuck returning running/unknown).

* fix(gastown): skip long-stalled agents in reconcileGUPP to preserve auto-idle

Agents already past STALLED_AUTO_IDLE_MS are owned by reconcileAgents
(stalled → idle + unhook). Without this guard, reconcileGUPP would also
match them (activity > GUPP_FORCE_STOP_MS = 2h) and emit a later
transition_agent back to stalled in the same pass. applyAction doesn't
enforce the 'from' field, so the later action would win and leave the
agent stuck stalled, defeating the cleanup.

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>

* fix(gastown): align GUPP stalled-skip with auto-idle eligibility

Previously, GUPP skipped stalled agents when elapsed (based on
last_event_at) exceeded STALLED_AUTO_IDLE_MS, expecting reconcileAgents
to handle them. But reconcileAgents' auto-idle check uses
last_activity_at. If a container keeps heartbeating with a stale SDK
event timestamp, GUPP would skip while reconcileAgents would not yet
idle the row, leaving it stuck stalled with the alarm firing.

Mirror the auto-idle check against last_activity_at so GUPP only defers
to reconcileAgents when the row is actually eligible for cleanup.

---------

Co-authored-by: John Fawcett <john@kilcoode.ai>
]);

for (const agent of longStalledAgents) {
if (!agent.last_activity_at) continue;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Stalled agents can be idled too early

last_activity_at is updated by every heartbeat, including after GUPP has transitioned an agent to stalled; because this check uses last_activity_at, a stalled but still-running agent will stay in the active set for as long as heartbeats continue, then be idled and unhooked 2.5 hours after the container is force-stopped, which is only ~30 minutes after GUPP's 2-hour force-stop threshold. That effectively cuts the intended stalled triage/recovery window down from 2.5 hours to about 30 minutes for normal force-stopped agents. Use a timestamp that is set when the row enters stalled (or preserve last_event_at/a metadata marker for that transition) so the auto-idle duration is measured from the stalled transition rather than the last heartbeat before force-stop.


if (body.length > 20) {
debounceRef.current = setTimeout(() => {
const seq = ++enrichSeqRef.current;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Stale enrichment responses can update the current draft

The sequence number only advances when a debounced request actually fires. If the user edits the body while a previous enrichment request is already in flight, the old response can arrive before the next debounce fires (or after the body was shortened below the threshold) and still match enrichSeqRef.current, overwriting the title/labels with suggestions for stale text. Advance the sequence on every body change before scheduling, or compare against the body snapshot in the response handler, so any edit invalidates outstanding responses.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Apr 27, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
services/gastown/src/dos/town/reconciler.ts 759 Stalled agents can be idled too early
apps/web/src/components/gastown/CreateBeadDrawer.tsx 93 Stale enrichment responses can update the current draft
Other Observations (not in diff)

No issues found in unchanged code that cannot receive inline comments.

Files Reviewed (20 files)
  • .plans/create-bead-ui-convoy.md - 0 issues
  • apps/web/package.json - 0 issues
  • apps/web/src/app/(app)/gastown/[townId]/rigs/[rigId]/RigDetailPageClient.tsx - 0 issues
  • apps/web/src/components/gastown/BeadBoard.tsx - 0 issues
  • apps/web/src/components/gastown/CreateBeadDrawer.tsx - 1 issue
  • apps/web/src/components/gastown/MarkdownEditor.tsx - 0 issues
  • apps/web/src/components/gastown/drawer-panels/BeadPanel.tsx - 0 issues
  • apps/web/src/lib/gastown/types/router.d.ts - 0 issues
  • services/gastown/src/dos/Town.do.ts - 0 issues
  • services/gastown/src/dos/TownContainer.do.ts - 0 issues
  • services/gastown/src/dos/town/patrol.ts - 0 issues
  • services/gastown/src/dos/town/reconciler.ts - 1 issue
  • services/gastown/src/dos/town/scheduling.ts - 0 issues
  • services/gastown/src/gastown.worker.ts - 0 issues
  • services/gastown/src/prompts/mayor-system.prompt.ts - 0 issues
  • services/gastown/src/prompts/polecat-system.prompt.ts - 0 issues
  • services/gastown/src/prompts/refinery-system.prompt.ts - 0 issues
  • services/gastown/src/prompts/triage-system.prompt.ts - 0 issues
  • services/gastown/src/trpc/init.ts - 0 issues
  • services/gastown/src/trpc/router.ts - 0 issues

Reviewed by gpt-5.5-2026-04-23 · 14,944,970 tokens

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant