feat(claude): respawn panes on account set/unset + safe --purge (T-480 part 2)

The extension consumer for the per-repo account verbs, making set/unset
fully functional. The Claude extension subscribes to accountActionChannel:

- set / unset → ClaudeSessionOrchestrator.respawnForWorkspace(cwd): closes the
  workspace's solo sessions (awaiting real process death, T-437) and re-spawns
  each on the same id with --resume, so the conversation continues under the
  newly-bound CLAUDE_CONFIG_DIR (resolved at spawn by agentBootstrap). Team and
  forked sessions are skipped — re-joining the broker / re-forking on an account
  swap is out of scope; they adopt the account on their next natural spawn.
- remove --purge → deletes the config dir behind isPurgeableAccountDir, a strict
  guard that only ever removes a ~/.claude-* directory that is a direct child of
  $HOME. The purge payload now carries the dir (the account is gone from the
  registry by publish time).

login still only publishes its action — spawning the `claude login` terminal
pane needs argv+env terminal-pane support and is split to T-485.

Covered: respawnForWorkspace (respawn solo, skip fork/other-repo) and the purge
guard's accept/reject matrix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 21:18:49 +02:00
co-authored by Claude Opus 4.8
parent 744b6cff43
commit 34b9690005
10 changed files with 265 additions and 3 deletions
@@ -127,7 +127,7 @@ void main() {
final r = await run(['remove', 'work'], flags: {'purge': true});
expect(r.ok, isTrue, reason: r.error?.message);
expect(store.accounts, isEmpty);
expect(published.single.data, {'action': 'purge', 'name': 'work'});
expect(published.single.data, {'action': 'purge', 'name': 'work', 'dir': '/home/u/.claude-work'});
});
test('login publishes a login action with the dir; unknown account errors', () async {