feat(workspace): attach the bound workspace's integration engine

Draft
RA
2mo ago
+9388-26

Issue for this PR

Closes #1153

Type of change

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

What does this PR do?

Lets a terminal session acquire the local datamate engine for the workspace its project is bound to, instead of falling through to the hosted endpoint, which serves a different tool set.

New module workspace/engine-sync.ts, idempotent per session and gated on the workspace pilot flag. Its rules:

  1. Reuse only what is attributable. An existing entry is reused only when it is live, its command pins the engine to this workspace, and that binary clears the version floor. Being connected proves none of that: an unpinned engine follows whichever workspace its owner has active, and the extension writes exactly such an entry. Anything live but not attributable is replaced by a pinned local spawn, and what it was is reported. That costs other clients nothing, since a stdio entry is a per-client child process.
  2. Opportunistic use, never an install. An engine on PATH clearing the floor is spawned for this workspace and persisted to the project config so later sessions start it at boot.
  3. Offer, never silently install. With no engine, say which tools are unavailable and how to install one.
  4. Never fall back to hosted on failure. The two tool sets diverge in both directions, so a silent fallback would change the workspace's declared contract.
  5. Report declared-but-not-delivered. The engine says nothing about the difference between the workspace allowlist and what it built; this diffs them.

Attach state is never persisted outside the project being attached, and an entry that cannot be attributed is never torn down.

First-turn readiness. A turn resolves its tool list before the per-turn work that starts the attach, so a session that spawned its own engine listed the engine's tools one turn late. The attach now starts ahead of tool resolution with a bounded wait, so those tools make the first tool list. Past the cap the turn proceeds and a tools-changed notification delivers them. Unbound and disabled sessions wait for nothing.

Also: the integrations listing hides extension-type integrations, which need a live VS Code bridge, and says how many it hid.

Engine gate cleared: the required engine version is published, verified against a clean install from the registry.

How did you verify your code works?

Unit — 68 tests over the module's seams; test/altimate/workspace/ 177 pass / 0 fail; typecheck clean. Three failures elsewhere in the wider suite reproduce identically on a clean worktree at origin/main.

End-to-end, against a real bound workspace, each row re-run on the current commit:

ScenarioResult
Fresh bound dir, no persisted entryEngine tools present on turn 1 (previously the manager tool alone); pinned entry written
Dead URL entryReplaced by a local spawn, replacement reported
Persisted entry, broken binaryRetried once, surfaced; no hosted entry added
Unbound dirNo attach; turn-1 latency within noise
Integrations listingExtension-type entries hidden, count reported
Live entry pinned hereReused; config byte-identical after the run
Live entry unpinnedReplaced by a pinned spawn; config rewritten
Entry explicitly disabledRespected; no engine attached, config byte-identical
Engine installed mid-sessionTurn 1 reports no engine; after install, next turn attaches with tools

Five rounds of automated review were triaged on this branch: 15 findings, 15 confirmed real, 14 fixed. Full log, including two cases where one fix silently disarmed another, is in the Codex review log comment.

Screenshots / recordings

Not a UI change; toasts are TUI-only and terminal evidence is above.

Checklist

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

Consolidation

The attach flow has been restructured and hardened. The decision is a pure
function over one snapshot; every mutation sits behind a check of both halves of
the world it depends on; every install undoes itself on any non-success exit;
every refusal reaches the user exactly once. MCP now records what it spawned, so
attribution compares the running engine against the config rather than trusting
the config alone.

Reviewed by nineteen automated rounds, then three rounds of five parallel
adversarial passes. The round-by-round log, the invariants the suite holds, and
the residuals at the width the code actually has are in the pinned comment below.

The two most serious defects were not wrong code — they were this module being
internally consistent and wrong about the world. Behind most of the rest: a rule
that stopped being applied somewhere past where it was written down.