chore(gastown): promote gastown-staging to main#2835
Conversation
* 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>
c3f92e6 to
8720aca
Compare
| ]); | ||
|
|
||
| for (const agent of longStalledAgents) { | ||
| if (!agent.last_activity_at) continue; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Other Observations (not in diff)No issues found in unchanged code that cannot receive inline comments. Files Reviewed (20 files)
Reviewed by gpt-5.5-2026-04-23 · 14,944,970 tokens |
Summary
Promotes the current state of
gastown-stagingintomain. This PR rolls up 2 PRs that were reviewed and merged into the staging branch since it last diverged frommain.Constituent PRs
Verification
gastown-staging