Skip to content

feat(vscode): idle-waiting builders, sidebar polish, and PIR default-branch fix (#777 Layer 2)#779

Merged
amrmelsayed merged 11 commits into
mainfrom
feat/pir-and-vscode-updates-2
May 20, 2026
Merged

feat(vscode): idle-waiting builders, sidebar polish, and PIR default-branch fix (#777 Layer 2)#779
amrmelsayed merged 11 commits into
mainfrom
feat/pir-and-vscode-updates-2

Conversation

@amrmelsayed
Copy link
Copy Markdown
Collaborator

@amrmelsayed amrmelsayed commented May 20, 2026

A grab-bag of VSCode-extension polish around the Builders view, plus a PIR-protocol fix that addresses Layer 2 (PIR slice) of #777.

What's new in the extension

  • Count badge on the Codev activity-bar icon. Builders needing your attention (blocked at a gate or idle waiting on input) contribute to a numeric badge on the sidebar icon — visible from anywhere in VSCode without opening the sidebar.
  • Builders waiting silently for input now surface as "waiting on input." A builder whose terminal has been idle for > 5 min and isn't blocked or finished gets a chat-bubble icon in the Builders tree and an `N waiting` segment in the status bar. Catches the case of an agent quietly waiting on a clarifying question; distinct from being blocked at a formal porch gate.
  • Single-click a builder row to expand its file list. Clicking opens the terminal and expands the file list underneath; the chevron is no longer a separate click.
  • Send a backlog issue to the architect inline. Each backlog row has an inline button that pastes the issue's `#` reference into the architect input.
  • Issue markdown previews update live while open — they refresh in place when the underlying issue changes (new comment, label edit, etc.).

Preview

image

Fixed

Internal / chore

  • Dropped a transitional `isIdleWaiting` re-export in `views/builders.ts`; consumers import from `@cluesmith/codev-core/builder-helpers` directly.
  • `pnpm vscode:publish` now publishes to both the VS Code Marketplace and Open VSX in one command.
  • README layout diagram tidied.

Verification

  • types + core build clean.
  • vscode compile (`tsc --noEmit` + lint + esbuild) clean.
  • vscode mocha tests: 67 passing (13 new for the idle-waiting predicate + ordering).
  • codev vitest: 2974 passing, 13 skipped.
  • dashboard `tsc -b` clean.
  • Manual end-to-end: idle-waiting verified on a non-`main`-default consumer repo — `/api/overview` now populates `lastDataAt` correctly, the icon flips to chat-bubble after the threshold.

Branch notes

Branch was renamed from `feat/vscode-updates-2` mid-development as the scope grew to include PIR work.

Closes the PIR slice of #777; remaining layers tracked there.

Promote the existing Unreleased entries (image paste via Cmd+Alt+V,
per-builder changed-files with SCM-style decorations, Codev: View Diff,
accordion mode for the Builders tree, live-status dot for active
builders, Backlog hiding builder-active issues) to a versioned section
so the published CHANGELOG carries a clean per-version history.

package.json already declares 3.0.7; no version bump needed. Dropped
the empty Unreleased scaffold so a dangling header doesn't ship in the
production changelog.
Previously `vscode:publish` ran `vsce publish` only — easy to silently
skip Open VSX by using the wrong entry point and drift the two
registries apart. The dual-registry shell script that packages once and
uploads to both lived at the repo root as a separate entry point.

Wire `vscode:publish[:pre]` to invoke the shell script directly so the
npm command is the single canonical publish path, and move the script
inside the package to drop the `../../` traversal.

- Move `scripts/publish-vscode.sh` -> `packages/vscode/scripts/publish.sh`
  (rename preserves history + executable bit; `-vscode` suffix dropped
  as redundant inside the package). cwd resolution simplified to
  `$SCRIPT_DIR/..` — direct invocation still works from any cwd.
- `packages/vscode/package.json`: `vscode:publish` / `vscode:publish:pre`
  now run `sh scripts/publish.sh [--pre-release]`.
- Final log line echoes both listing URLs (VS Code Marketplace + Open
  VSX) — the Open VSX URL was missing from the publish log before.

No external doc references the old script path (verified via
`git grep`).
When a builder is blocked at a human-approval gate the Codev sidebar
icon now carries a numeric bubble with the count — the same 'needs me'
signal the status-bar bell already surfaces, now visible even with the
sidebar collapsed. Tooltip explains the number.

VSCode has no container-level badge API; per-view TreeView.badge is the
only way to paint the activity-bar icon, and badges bubble up from a
container's views to the icon when the sidebar is hidden. Badging
buildersView is the natural carrier for the blocked signal and avoids
inflating an aggregate sum across multiple views.

- updateActivityBadge() reads data.builders.filter(b => b.blocked)
  (same predicate as updateStatusBarCounts) and sets the badge only
  when connected to Tower and the count is > 0; otherwise clears it.
- Wired into the existing overviewCache.onDidChange reconcile loop so
  it stays live without new event plumbing.
- Seeded once after createTreeView so a cached overview shows the
  badge immediately, not on the next refresh tick.

Per-view titles (Builders (N) / Pull Requests (N) / Backlog (N) /
Recently Closed (N)) unchanged — different semantics: titles show
current activity, badge shows action required. 54 tests passing.
Single-click on a builder row now opens its terminal *and* expands
the row's changed-file list — single-click does what most users
expected. Chevron click still toggles expansion; right-click is
unchanged.

New codev.openBuilderRow wrapper command runs the existing
openBuilderByRoleOrId, then calls buildersView.reveal(item,
{expand:true, focus:false}). The reveal-driven expand fires
onDidExpandElement, so the accordion handler picks it up and collapses
peers when codev.buildersAutoCollapse is on; the existing openBuilderId
idempotency guard keeps the re-fired event a no-op. focus:false keeps
the terminal focused, not the tree.

codev.openBuilderById is untouched, so other callers (terminal links,
etc.) still open the terminal without expanding the row.
Adds an @-mention icon on each backlog row. Clicking it opens (or
focuses) the architect terminal and types '#<issueId> ' into its prompt
without submitting, so the user can add their own context (e.g. 'use
PIR for this') before hitting Enter.

New TerminalManager.injectArchitectText(text) calls Terminal.sendText
with addNewLine=false on the 'architect' entry — that flows through the
Pseudoterminal's handleInput exactly like a user keystroke. The
codev.referenceIssueInArchitect command awaits codev.openArchitectTerminal
first so the architect is guaranteed available, then injects. The button
is a view/item/context inline@1 entry on backlog-item rows, mirroring
the same idiom as the green-check Approve button on blocked builders.

Row-click behavior is unchanged: still opens the markdown preview via
codev.viewBacklogIssue. The new button is a separate affordance.
Two surgical changes to the layout diagram:

- Drop the leading '#' from example issue refs in the right column
  ([#42] [#43] -> [42] [43], Builder #42 -> Builder 42). The Marketplace's
  README renderer autolinks any '#NN' pattern to repository.url's issues
  endpoint (even inside fenced code blocks), so 'Builder #42' in the
  diagram became a clickable link to issue #42 in the published version.
  Bare numbers don't match the '#\d+' autolink, so the diagram now stays
  plain. Backslash-escape doesn't work — the '\' renders literally
  inside a fenced code block.

- Add a bottom-panel row to reflect 3.0.6's 'dev-server terminals live
  in the bottom panel' decision. The diagram previously only showed the
  editor groups, so a reader couldn't tell where dev servers go.
A builder that hasn't emitted PTY output for > 5 minutes while still
being able to make progress (not blocked at a gate, not complete or
verified) is flagged as idle-waiting — a proxy for "agent paused at a
clarifying question", distinct from both running and blocked.

Pipeline:
  shellper tracks lastDataAt per PTY byte → sends it in every WELCOME
  (new optional protocol field) → Tower's ShellperClient hydrates on
  connect and bumps per DATA frame → PtySession.attachShellper hydrates
  Spec 467's existing lastDataAt from the client's value (covering
  Tower-restart cold start, where the in-memory ShellperClient is fresh
  but the detached shellper process still knows the genuine last-byte
  moment) → /api/overview enrichment maps the runtime terminal registry
  back to the discovery list via the newly-stamped roleId → ISO string
  on OverviewBuilder.lastDataAt → isIdleWaiting returns true once
  Date.now() − lastDataAt > 5min.

roleId is computed once per builder at discovery (worktreeNameToRoleId
in discoverBuilders) and stamped on BuilderOverview so the bridge
between filesystem-discovered builders and the runtime terminal
registry is a single field lookup. The enrichment loop matches the
shape of the established pattern in /api/state's builder list
(tower-routes.ts:1572): iterate entry.builders, look up the session
via TerminalManager, do the work. No per-request regex on the
worktree basename.

WELCOME-side hydration is the crux of correctness across Tower
restarts: the shellper process survives, but Tower's ShellperClient
does not. Reading the shellper's own tracker on WELCOME eliminates
what would otherwise be a 5-minute warm-up window after each restart,
once shellpers themselves have been spawned with the new code.

idleMs is unchanged — it measures cumulative gate-wait time (sum
over gates of approvedAt − requestedAt), which is orthogonal to PTY
silence and stays 0 for the case this surfaces.

The threshold and predicate live in @cluesmith/codev-core/builder-
helpers so the VSCode extension and the web dashboard cannot drift.
The types package stays wire-contract-only.

Surfaces:
  - Builders tree: 3-bucket order (blocked → idle → active),
    comment-discussion icon, "waiting on input [Xm silent]" label,
    contextValue awaiting-builder-<protocol> with existing menu
    rules extended to match it alongside builder-/blocked-builder-.
  - Status bar gains an "N waiting" segment when > 0.
  - Activity-bar badge counts blocked + idle with composed tooltip.
  - Dashboard BuilderCard: "Waiting on input" + --waiting class.

13 new test cases cover the predicate's threshold edges (>, not >=),
blocked / completed / verified exclusions, null-lastDataAt fallback,
and the 3-bucket orderForDisplay invariants including blocked-takes-
precedence-over-idle and blockedSince-ascending sort within the
blocked bucket.
…se sites

Followup to the idle-waiting feature commit. views/builders.ts no
longer re-exports `isIdleWaiting` — extension.ts and the unit test
file import it directly from @cluesmith/codev-core/builder-helpers,
the canonical home. The re-export was a convenience kept after the
centralisation to avoid touching call sites; with two consumers it
was indirection without benefit.

Side effect: removes the only export that wasn't view-local from
views/builders.ts. The module now cleanly contains only the tree
provider, the row helpers (orderForDisplay, timeSince), and the
view's local types.
IssueContentProvider was missing the onDidChange hook that lets a
TextDocumentContentProvider invalidate a cached document — so even when
viewBacklogIssue re-fetched and overwrote the content map on re-click,
VSCode reused its cached TextDocument and the markdown preview stayed
frozen at the first view's content until the tab was closed or the
window reloaded.

Two changes, single file (plus a one-line caller update):

- Add EventEmitter<Uri> on the provider, expose as onDidChange, and fire
  from set(). set() also dedups (no fire when the new markdown matches
  the cached value), so identical refetches don't churn the preview.

- Subscribe to OverviewCache.onDidChange in activateIssueView so open
  previews refresh on the existing sidebar-poll + SSE heartbeat — no
  custom timer. A leading-edge 30s throttle absorbs SSE bursts during
  builder activity. The content map's keys ARE the 'what's open' set,
  bounded exactly by visibility: onDidCloseTextDocument drops entries
  when the preview tab closes (VSCode unloads the codev-issue:
  TextDocument). No magic cap, no LRU bookkeeping.

Net behavior: re-click refreshes immediately; passive updates land
within the next overview tick (default ~60s, ~30s minimum); identical
refetches are silent; closed previews stop being polled.
PIR's builder/reviewer prompts hardcoded `main` as the integration
branch in five literal git commands and one review question, plus two
conventions. On repos whose integration branch isn't `main` (e.g. `dev`,
`ci`, `master`), agents either errored (no `main` branch) or produced
diffs/rebases against an irrelevant branch — polluting planning and
review reasoning.

Replace each hard-fail literal command with the in-tree resolver pattern
already used by `packages/vscode/src/commands/view-diff.ts:262-271`:

  $(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null \
      | sed 's|^origin/||' || echo main)

Reads the actual default branch from `origin/HEAD` (set on every
properly-cloned remote); falls back to `main` only when `origin/HEAD`
is unset. Behaviour-preserving on `main`-default repos, correct on the
non-`main` ones the bug surfaced on.

Sites updated (in both `codev/protocols/pir/` and the
`codev-skeleton/protocols/pir/` template that ships via `codev init`):

  - builder-prompt.md:85  (resumption diff for dev-approval)
  - prompts/implement.md  (resumption-feedback diff, files-summary stat,
    push-prohibition wording)
  - prompts/review.md  (files-changed stat, push-prohibition wording,
    rebase-on-conflict block)
  - consult-types/pr-review.md  (PR-review "up to date with default
    branch?" question)

Conventions ("Don't push to main") reworded to "Don't push to the
default branch" — semantically identical, no fossilised name.

This fixes only PIR's Layer 2 sites for codev#777. Layer 1
(consult/index.ts:989, 1297), Layer 2 in the other protocols
(BUGFIX, SPIR, ASPIR, AIR, MAINTAIN), Layer 3 test infrastructure,
and the optional porch-side prompt substitution mechanism are tracked
on the same issue and are out of scope here.

PIR is the first protocol to adopt this resolver pattern in markdown
prompts; future Layer 2 work on the other protocols should follow the
same shape for consistency.
@amrmelsayed amrmelsayed requested a review from waleedkadous May 20, 2026 05:45
Copy link
Copy Markdown
Contributor

@waleedkadous waleedkadous left a comment

Choose a reason for hiding this comment

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

Some super nice features!!

@amrmelsayed amrmelsayed merged commit e0dc8a0 into main May 20, 2026
6 checks passed
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.

2 participants