Skip to content

vscode: re-bucket Builders tree — actively-communicated blocked builders sort below active, truly-blocked stays on top #798

@amrmelsayed

Description

@amrmelsayed

Problem

The Builders tree treats every gate-blocked builder as equally urgent — they all sort to the top with a bell icon and a wait-time suffix. In practice, "blocked" splits into two very different cases:

  1. Truly blocked — the builder hit a gate (e.g. plan-approval) and nobody has touched it since. This is the one that wants attention.
  2. Blocked while the architect is actively in dialog with it — the gate is open but the engineer is mid-conversation, sending messages, iterating. Surfacing this row at the top with the same urgency as a truly-stuck builder is noise; the engineer already knows it's there because they're typing to it right now.

The current top-bucket placement makes the second case feel like an alarm that won't quiet.

Current state

  • Sort order in packages/vscode/src/views/builders.ts:23–28 (orderForDisplay):
    1. blocked (any b.blocked truthy), sorted by blockedSince ascending.
    2. idleWaiting (isIdleWaiting(b, now)).
    3. active (everything else).
  • OverviewBuilder.lastDataAt (@cluesmith/codev-types, exposed via overview.ts:73) already tracks when Tower last received output from the builder's shell — updated whenever the architect's input echoes back or the agent emits anything.
  • isIdleWaiting(b) (packages/core/src/builder-helpers.ts:30) returns true for non-blocked, non-complete builders silent past IDLE_WAITING_THRESHOLD_MS = 5 minutes (line 19). The same threshold is the natural cut-off for "actively communicating" applied to blocked builders.

Proposed behavior

1. New predicate

Add isActivelyCommBlocked(b, now) in packages/core/src/builder-helpers.ts:

export function isActivelyCommBlocked(b: OverviewBuilder, now: number = Date.now()): boolean {
  if (!b.blocked) return false;
  if (!b.lastDataAt) return false;
  return now - new Date(b.lastDataAt).getTime() <= IDLE_WAITING_THRESHOLD_MS;
}

Co-located with isIdleWaiting so the threshold policy stays in one place.

2. New four-bucket sort order

Top-down:

  1. Truly blockedb.blocked && !isActivelyCommBlocked(b). Sorted longest-blockedSince first.
  2. Idle waitingisIdleWaiting(b) (unchanged).
  3. Active — everything that doesn't match the others (unchanged).
  4. Actively-comm blockedisActivelyCommBlocked(b). Sorted most-recent-lastDataAt first (so the one you're typing into right now is at the bottom-most slot — close to where the input cursor sits in the terminal pane).

3. Visual treatment

  • Truly blocked: existing bell icon + wait-time suffix; unchanged.
  • Actively-comm blocked: distinct icon (e.g. $(comment-discussion) instead of $(bell)) and the row label still surfaces the gate (blocked on <gate>) so the user remembers which gate is in play — but the visual weight is de-emphasised (no wait-time scream).
  • Idle-waiting and active: unchanged.

4. Threshold reuse

Use the existing IDLE_WAITING_THRESHOLD_MS = 5 minutes — no new tunable. A blocked builder you haven't typed to in >5 minutes promotes itself back to "truly blocked" (top bucket), which matches user intent: dialog has stalled, the gate is now really blocking.

Acceptance criteria

  • isActivelyCommBlocked() predicate added to packages/core/src/builder-helpers.ts, alongside isIdleWaiting.
  • orderForDisplay in views/builders.ts returns four buckets in the order above.
  • Truly-blocked rows render unchanged (bell icon, wait-time suffix).
  • Actively-comm-blocked rows render with a distinct icon and no wait-time suffix; gate label preserved.
  • Sort ordering reactively updates as lastDataAt ticks past the threshold (no extra polling — relies on existing SSE refresh cadence).
  • No additional Tower instrumentation needed; uses existing lastDataAt.

Out of scope

  • A separate threshold for "actively communicating" different from IDLE_WAITING_THRESHOLD_MS (intentionally — one threshold, one policy).
  • Manual "I'm working on this" flag.
  • Auto-collapse / auto-pin of the actively-comm group.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions