feat(workspace): tell the model about extension tools a live IDE bridge serves

Merged
RA
2mo ago
+588-36

Issue for this PR

Closes #1263

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The ## Workspace integrations system-prompt section projected only the three warehouse capabilities per connection type, so extension-type tools the engine serves through a live VS Code bridge (the dbt project tools, sql-tools) never reached the model — even while they sat in its catalog. #1236 taught the presentation surfaces about them; this is the model-facing half.

  • declared() now also groups the extension keys under their catalog integration (Declared.extensions, optional — the flat keys/extensionKeys lists remain the contract every existing reader was written against), and the attach outcome carries the groups so precedence never makes a second lookup.
  • derive names an extension tool only when two signals agree: its key materialised in the live catalog and a bridge for this project is live at this turn (liveBridge, the same read-only mirror #1236 uses). Either missing is silence, not a warning — a dormant bridge renders exactly what a workspace with no extension tools renders.
  • The groups ride the nothing-materialised snapshot too, because a workspace can serve extension tools and no warehouse capability at all; that state now speaks when (and only when) it carries them.
  • servedExtensions applies the same reachability filter as servedInventory, at projection time, so an analyst is never told about tools it may not call.
  • The section appends the groups under a fixed intro that names the condition they depend on (the window being open); the extension-only shape keeps the local-tools sentence so nothing is inferred from seeing datamate_* keys listed. When the cap is hit, extension lines drop before any warehouse type.

Claims

  • C1 — Byte-identical when nothing changes. Every snapshot without served extension tools renders the section exactly as before, including all silent disabled states and the unlinked-project case. The disabled-reason table is untouched; EMPTY's shape is untouched unless extension tools are present.
  • C2 — Two signals, or silence. An extension tool is named only when its key is in the live engine catalog and liveBridge(projectDirectory) is true for this turn. A dormant bridge, a catalog key no declared group names, or an outcome without groups (an older engine) all render the no-extension section.
  • C3 — No new lookup, inert names. Grouping and display names come from the catalog read the overlay already caches per attach; nothing is fetched per turn. Integration names pass through inertWorkspaceName before reaching the prompt.
  • C4 — Reachability respected. Tools the caller's ruleset denies are filtered at projection time; the analyst shape renders nothing.
  • C5 — Extension-only workspaces speak. With extension tools served and nothing shadowed, the nothing-materialised snapshot carries the groups (and the bound id) and renders an extension-only section that keeps the local-tools sentence; the same snapshot without a live bridge is silent, as it always was.
  • C6 — Cap discipline. Extension lines are dropped before any warehouse type; an omission line states how many groups were dropped; a single oversized group cannot breach MAX_SECTION_CHARS.

Residuals

  • R1 — Bridge liveness is the presentation mirror (liveBridge), not the engine's own connection state. A window closed after this step's snapshot refresh can leave the block one step stale — the same bound the per-step snapshot already has for everything else in the section.
  • R2 — liveBridge path matching uses path.relative prefix semantics rather than the engine's platform-aware normalisation (inherited from #1236's R1); a Windows drive-letter case mismatch can under-report a live bridge, never over-report.
  • R3 — Tool keys are quoted raw from the engine catalog, as the warehouse lines already do; only integration names are sanitised.
  • R4 — The engine discovers the bridge at spawn and does not re-list tools; a window opened after the engine started is not advertised until the engine restarts. Engine behaviour, unchanged here.
  • R5 — derive rises 17 → 20 cognitive (complexity appendix below). The three added breaks are the extension gate, the outcome-shape spread, and the bound-id spread on the nothing-materialised return; extracting them would move the branches into a helper, not remove them, so the shape is recorded rather than reshuffled.

How did you verify your code works?

  • E2E on the ralphtest rig (see the E2E comment): live bridge → 14 of 16 declared extension keys served and named; real model turn quotes the section verbatim; sidecar-aside control renders no extension block.
  • bun test over the eight workspace suites (awareness, precedence, engine-overlay, engine-probes, engine-types, datamate-list-integrations, plugin/workspace, engine-install-offer): 312 tests, all green; tsgo --noEmit clean.
  • New coverage: named under the integration quoting only materialised keys (declared-but-absent control); dormant bridge byte-identical to a no-extension section; catalog keys without a declared group not claimed; extension-only shape from nothing-materialised and silent again without the bridge; analyst renders nothing; hostile integration name inert; cap drops extension lines first; the attach outcome carries groups only when the allowlist names any; declared() grouping (name from catalog, id fallback, empty groups dropped) and the exact flat-only shape when no extension integration exists.

Screenshots / recordings

Not a UI change. The rendered system-prompt section is asserted verbatim in awareness.test.ts, and the E2E comment on this PR shows the live render from a real VS Code bridge (probe), the model quoting it back verbatim (one real turn), and the negative control with the bridge removed.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b

Appendix — complexity delta (awareness: extension tools via live bridge)

89ddf85656 → 686b2e559c · only functions this diff touches · advisory, not a gate.

FunctionFileCognitiveCCNStatus
derive L555packages/opencode/src/altimate/workspace/precedence.ts17 → 20 ▲16 → 17 ▲hard to follow
systemSection L146packages/opencode/src/altimate/workspace/awareness.tsnew → 10new → 8new — easy
declared L124packages/opencode/src/altimate/workspace/engine-probes.ts8 → 9 ▲11 → 9 ▼easy
extensionsServed L287packages/opencode/src/altimate/workspace/precedence.tsnew → 9new → 12new — easy

Summary: 13 touched · 3 rose · 1 improved · 6 new (max cognitive 10) · net cognitive Δ +26

ℹ️ How to read these numbers

Cognitive (Sonar spec) counts breaks in linear reading flow — each if/loop/catch/ternary/boolean-operator switch adds 1, and nesting makes every further break cost more. It approximates how much you must hold in your head to follow the function: 0–5 trivial · 6–10 easy · 11–15 moderate (15 = Sonar's recommended per-function cap) · 16–25 hard to follow · >25 needs decomposition.

CCN (cyclomatic) counts independent paths — also the minimum number of test cases for full branch coverage of the function.

Only functions this diff touches are measured, as deltas — pre-existing complexity is not counted against this change. Rising numbers aren't automatically wrong; they're where review attention should go. Test files excluded.

Summary by CodeRabbit

  • New Features
    • Workspace awareness now includes tools provided by connected VS Code extensions.
    • Extension tools appear only when the IDE bridge is active and tools are available.
    • Extension names and capabilities are displayed alongside warehouse tools, including extension-only workspaces.
    • Workspace identity displays the linked workspace name, including when no capabilities are materialized.
    • Tool listings respect display limits, prioritizing warehouse directives and explaining omitted extension tools when applicable.
    • Workspace status distinguishes available, unavailable, and omitted capabilities.