feat(workspace): honour a workspace pinned by the IDE extension
Server half of "pin altimate-code serve to the datamate selected in the IDE". The extension half is AltimateAI/vscode-altimate-mcp-server#471 — this one lands first.
Why
serve is launched by the VS Code / Cursor extension, which already knows which datamate the user picked in its panel. Nothing carried that across, so skills and memory followed whatever binding the project had on the backend rather than the selection on screen.
The change is one hook
skill-sync.ts:663 and memory-sync.ts:143 both funnel through resolveBindingOutcome. Honouring the pin there is all that is needed — neither module changes.
pin.ts(new) — parsesALTIMATE_PINNED_WORKSPACE_{ID,NAME,ROOT}into a taggedabsent | invalid | valid. A present-but-broken pin isinvalidand fails closed, because falling through to normal resolution can legitimately return a different workspace, and silently working against a workspace the user did not pick is the one outcome this must never produce.state.ts— the pin outranks the cached binding and server auto-adoption; is validated againstlistDatamates()(memoized onREVALIDATE_MS, since this runs per turn and per memory write); is scoped to the launch root; and is never persisted.pinnedis stripped from anything read off disk so a hand-edited cache cannot impersonate extension authorization.
Why a separate env namespace
ALTIMATE_RESOLVED_WORKSPACE_* looks like the obvious home, but launch-resolve.ts:69 sets only ..._ID for the TUI's --workspace flag. Sharing the namespace would make every such TUI session look like a half-populated pin and the fail-closed rule would break --workspace outright. readPin also returns absent unless ALTIMATE_CODE_SERVE is set, so the two mechanisms can never both be live.
Offline
Splits on whether this process ever validated the pin: never-validated is unknown (nothing was established, and the env name is a presentation fallback, never an authorization one); previously-validated keeps serving within the TTL — which is what this module already does for a cached binding rather than "tear a working setup down over a network blip". Verified non-membership always fails closed.
Verification
23 new tests (575 workspace tests total, 0 failures) covering the parser, precedence, fail-closed paths, root scoping, the offline split and memoization.
End-to-end against a real serve with the extension branch, OPENCODE_PRINT_LOGS=1:
workspace gate enabled=true serve=1 pinId=237
syncSkills entered canon=/home/coder/project enabled=true
resolved the workspace pinned by the IDE extension datamateId=237 datamateName=activity_test
workspace has no custom skills; removed the local snapshot
datamateName came back from the server, not the env, so the listDatamates() validation path is exercised.
Then, with a custom skill added to that workspace and memory enabled on it:
workspace skills synced datamateId=237 skills=1
- Skills — the workspace's skill landed at
.altimate-code/skill/_workspace/<id>/SKILL.mdwith a manifest pinned todatamateId: 237, and its contents matched the skill added in the workspace UI. - Memory — the agent's
altimate_memory_writeproduced a local block, and it appears in that workspace's "What's been remembered" list in the product UI.
Both are the pinned datamate's, not the project's backend binding — which is the whole point of the change.
Debugging note: Log.create().info() is gated on printEnabled(), which defaults to OFF. Without OPENCODE_PRINT_LOGS=1 this subsystem is completely silent and looks dead. That cost me a long detour; worth knowing before debugging it.
Not in this PR
memory-synchas noadoptedchecks at all, so ongoing mirroring already fires for server-adopted bindings despite the contract documented atstate.ts:349. Pre-existing, not introduced here, and fixing it changes behaviour for current users — deserves its own PR and release note.- No backfill trigger for a pin.
backfillOnBind's only caller isrecordApprovedBinding(the explicit-link path), and the pin is deliberately not persisted, so pre-existing local memory is not pushed. New saves mirror normally throughmirrorBlock. - No purge-before-fetch in
skill-syncwhen the pin changes to a different workspace.
🤖 Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added IDE workspace pinning to associate sessions with a validated workspace.
- Workspace pins are checked for account access and directory scope before use.
- Temporary API outages can preserve previously validated workspace access during a limited grace period.
- Invalid, inaccessible, or unverified pins fail safely without binding the session.
-
Bug Fixes
- Prevented nested commands from unintentionally inheriting workspace pin settings.
- Improved protection against symlink-based paths escaping the pinned workspace.