diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md index 1cf9ad0..e005432 100644 --- a/packages/vscode/CHANGELOG.md +++ b/packages/vscode/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.2.0 + +Follows the daemon's "worktree IDE" milestone (P7→P12), while keeping the extension a lightweight +**visual worktree manager** — no editor/diff duplicated here, that lives in the web IDE: + +- **Detailed git status** in the worktree tree: staged / unstaged / conflict counts and the last commit + subject (from the daemon's enriched `WorktreeGitStatus`). +- **Open Worktree IDE** command — deep-links to the web `/workspace/:repoId/:wt` view. +- **Fetch** and **Pull** (fast-forward only or rebase) commands on worktrees, alongside commit/push. +- **Archived sessions**: the `session_archived` event is handled live; finished, auto-archived sessions + are badged and hidden by default, with a new `arboretum.showArchivedSessions` setting to reveal them. + ## 0.1.0 Initial release — native VS Code integration for the Arboretum daemon: diff --git a/packages/vscode/README.md b/packages/vscode/README.md index 2769969..1e8f5f0 100644 --- a/packages/vscode/README.md +++ b/packages/vscode/README.md @@ -9,7 +9,11 @@ integration on top of the [Arboretum](https://git.lidge.fr/johanleroy/arboretum) daemon's PTY) — you get VS Code's own rendering, scrollback, copy/paste and links for free. - **Waiting alerts**: a status-bar counter and native notifications when a Claude session is waiting for input — answer **Yes/No** without even opening the terminal (uses the daemon's `answer` command). -- **Git mutations** from the tree: create worktree, commit, push, promote to main. +- **Git mutations** from the tree: create worktree, commit, push, fetch, pull, promote to main — with a + **detailed git status** on each worktree (staged / unstaged / conflicts and the last commit subject). +- **Open Worktree IDE**: jump from any worktree to its full `/workspace` view (file tree, Monaco editor, + inline diffs, integrated terminal) in the browser. The extension stays a lightweight visual worktree + manager — the heavy editing lives in the web IDE. - **Workspace-aware**: the worktree matching your open folder is highlighted; start a session or create a worktree for the current folder in one command. @@ -33,6 +37,7 @@ integration on top of the [Arboretum](https://git.lidge.fr/johanleroy/arboretum) | --- | --- | --- | | `arboretum.url` | `http://127.0.0.1:7317` | Base URL of the daemon (REST + WebSocket). | | `arboretum.showExternalSessions` | `false` | Also show Claude sessions started outside Arboretum (CLI). | +| `arboretum.showArchivedSessions` | `false` | Also show finished sessions auto-archived after their retention window. | | `arboretum.notifyOnWaiting` | `true` | Native notification when a session starts waiting for input. | The extension authenticates with `Authorization: Bearer ` on both REST and the WebSocket upgrade. @@ -48,13 +53,13 @@ The extension is bundled with esbuild (`@arboretum/shared` is inlined → the VS npm install npm run build:vscode # builds @arboretum/shared then bundles the extension cd packages/vscode && npx @vscode/vsce package --no-dependencies -# → git-arboretum-0.1.0.vsix +# → git-arboretum-0.2.0.vsix ``` Install it with **Extensions: Install from VSIX…** in the Command Palette, or: ```bash -code --install-extension git-arboretum-0.1.0.vsix # also: codium / cursor +code --install-extension git-arboretum-0.2.0.vsix # also: codium / cursor ``` ### Other distribution channels (optional) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 4eabb76..60b911f 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -2,7 +2,7 @@ "name": "git-arboretum", "displayName": "Arboretum", "description": "Pilot your git worktrees and Claude Code sessions from VS Code — native terminals, live tree, waiting alerts.", - "version": "0.1.1", + "version": "0.2.0", "private": true, "publisher": "johanleroy", "license": "MIT", @@ -42,6 +42,11 @@ "default": false, "markdownDescription": "Show Claude sessions discovered outside Arboretum (started from the CLI) in the trees. Off by default to avoid clutter." }, + "arboretum.showArchivedSessions": { + "type": "boolean", + "default": false, + "markdownDescription": "Show finished sessions that Arboretum auto-archived after their retention window. Off by default to keep the trees focused on live work." + }, "arboretum.notifyOnWaiting": { "type": "boolean", "default": true, @@ -94,7 +99,10 @@ { "command": "arboretum.createWorktree", "title": "Create Worktree…", "category": "Arboretum", "icon": "$(add)" }, { "command": "arboretum.commitWorktree", "title": "Commit…", "category": "Arboretum", "icon": "$(git-commit)" }, { "command": "arboretum.pushWorktree", "title": "Push", "category": "Arboretum", "icon": "$(repo-push)" }, + { "command": "arboretum.fetchWorktree", "title": "Fetch", "category": "Arboretum", "icon": "$(cloud-download)" }, + { "command": "arboretum.pullWorktree", "title": "Pull…", "category": "Arboretum", "icon": "$(arrow-down)" }, { "command": "arboretum.promoteWorktree", "title": "Promote to Main", "category": "Arboretum", "icon": "$(arrow-up)" }, + { "command": "arboretum.openWorktreeIde", "title": "Open Worktree IDE", "category": "Arboretum", "icon": "$(link-external)" }, { "command": "arboretum.startSession", "title": "Start Claude Session", "category": "Arboretum", "icon": "$(play)" }, { "command": "arboretum.startGroupSession", "title": "Start Group Session…", "category": "Arboretum", "icon": "$(play)" }, { "command": "arboretum.startSessionHere", "title": "Start Session in Current Folder", "category": "Arboretum" }, @@ -111,9 +119,13 @@ "view/item/context": [ { "command": "arboretum.createWorktree", "when": "viewItem == arboretum:repo", "group": "inline" }, { "command": "arboretum.startSession", "when": "viewItem == arboretum:repo", "group": "1_session" }, + { "command": "arboretum.openWorktreeIde", "when": "viewItem == arboretum:worktree", "group": "inline" }, { "command": "arboretum.commitWorktree", "when": "viewItem == arboretum:worktree", "group": "1_git" }, { "command": "arboretum.pushWorktree", "when": "viewItem == arboretum:worktree", "group": "1_git" }, + { "command": "arboretum.fetchWorktree", "when": "viewItem == arboretum:worktree", "group": "1_git" }, + { "command": "arboretum.pullWorktree", "when": "viewItem == arboretum:worktree", "group": "1_git" }, { "command": "arboretum.promoteWorktree", "when": "viewItem == arboretum:worktree", "group": "2_git" }, + { "command": "arboretum.openWorktreeIde", "when": "viewItem == arboretum:worktree", "group": "2_git" }, { "command": "arboretum.startSession", "when": "viewItem == arboretum:worktree", "group": "3_session" }, { "command": "arboretum.attachSession", "when": "viewItem =~ /arboretum:session:live/", "group": "inline" }, { "command": "arboretum.observeSession", "when": "viewItem =~ /arboretum:session:live/", "group": "1_term" }, @@ -137,7 +149,10 @@ { "command": "arboretum.createWorktree", "when": "false" }, { "command": "arboretum.commitWorktree", "when": "false" }, { "command": "arboretum.pushWorktree", "when": "false" }, + { "command": "arboretum.fetchWorktree", "when": "false" }, + { "command": "arboretum.pullWorktree", "when": "false" }, { "command": "arboretum.promoteWorktree", "when": "false" }, + { "command": "arboretum.openWorktreeIde", "when": "false" }, { "command": "arboretum.startSession", "when": "false" }, { "command": "arboretum.startGroupSession", "when": "false" }, { "command": "arboretum.revealWorktree", "when": "false" } diff --git a/packages/vscode/src/api/rest-client.ts b/packages/vscode/src/api/rest-client.ts index 537d5ae..ccc8f80 100644 --- a/packages/vscode/src/api/rest-client.ts +++ b/packages/vscode/src/api/rest-client.ts @@ -9,10 +9,12 @@ import type { CreateSessionRequest, CreateWorktreeRequest, CreateWorktreeResponse, + FetchWorktreeRequest, GroupSessionResponse, GroupsListResponse, MeResponse, PromoteWorktreeRequest, + PullWorktreeRequest, PushWorktreeRequest, RepoBranchesResponse, ReposListResponse, @@ -93,6 +95,14 @@ export class RestClient { return this.request('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/worktrees/push`, body); } + fetchWorktree(repoId: string, body: FetchWorktreeRequest): Promise { + return this.request('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/worktrees/fetch`, body); + } + + pullWorktree(repoId: string, body: PullWorktreeRequest): Promise { + return this.request('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/worktrees/pull`, body); + } + promoteWorktree(repoId: string, body: PromoteWorktreeRequest): Promise { return this.request('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/worktrees/promote`, body); } @@ -106,9 +116,12 @@ export class RestClient { return this.request('POST', '/api/v1/sessions', body); } - listSessions(includeHidden = false): Promise { - const q = includeHidden ? '?includeHidden=true' : ''; - return this.request('GET', `/api/v1/sessions${q}`); + listSessions(opts: { includeHidden?: boolean; includeArchived?: boolean } = {}): Promise { + const params = new URLSearchParams(); + if (opts.includeHidden) params.set('includeHidden', 'true'); + if (opts.includeArchived) params.set('includeArchived', 'true'); + const q = params.toString(); + return this.request('GET', `/api/v1/sessions${q ? `?${q}` : ''}`); } resumeSession(id: string): Promise { diff --git a/packages/vscode/src/api/ws-client.ts b/packages/vscode/src/api/ws-client.ts index 8aa4d02..8904e9b 100644 --- a/packages/vscode/src/api/ws-client.ts +++ b/packages/vscode/src/api/ws-client.ts @@ -115,6 +115,8 @@ export interface ArbWsClientOptions { onSession?: (e: SessionEvent) => void; onWorktree?: (e: WorktreeEvent) => void; onGroup?: (e: GroupEvent) => void; + /** P10 : une session managée terminée vient d'être auto-archivée. */ + onSessionArchived?: (sessionId: string) => void; /** appelé au premier hello_ok après (re)connexion → re-seed du store via REST. */ onReady?: () => void; log?: (msg: string) => void; @@ -387,6 +389,9 @@ export class ArbWsClient { case 'session_exit': this.opts.onSession?.(msg); return; + case 'session_archived': + this.opts.onSessionArchived?.(msg.sessionId); + return; case 'repo_update': case 'repo_removed': case 'worktree_update': diff --git a/packages/vscode/src/commands.ts b/packages/vscode/src/commands.ts index 8b85515..ce92159 100644 --- a/packages/vscode/src/commands.ts +++ b/packages/vscode/src/commands.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import type { GroupSummary, RepoSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared'; import { RestError, type RestClient } from './api/rest-client.js'; +import { workspaceUrl } from './config.js'; import type { ArbWsClient, AttachmentSink } from './api/ws-client.js'; import type { Store } from './state/store.js'; import type { SessionTerminalManager } from './terminal/session-pty.js'; @@ -174,10 +175,43 @@ export function registerCommands(context: vscode.ExtensionContext, deps: Command }), ); }); + cmd('arboretum.fetchWorktree', (node) => { + const wt = asWorktree(node as AnyNode); + if (!wt) return; + void vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Arboretum: fetching…' }, () => + run('fetch', async () => { + await rest.fetchWorktree(wt.repoId, { path: wt.worktree.path }); + void vscode.window.showInformationMessage('Arboretum: fetched.'); + }), + ); + }); + cmd('arboretum.pullWorktree', (node) => { + const wt = asWorktree(node as AnyNode); + if (!wt) return; + void run('pull', async () => { + const mode = await vscode.window.showQuickPick( + [ + { label: 'Fast-forward only', mode: 'ff-only' as const }, + { label: 'Rebase', mode: 'rebase' as const }, + ], + { placeHolder: 'Pull strategy' }, + ); + if (!mode) return; + await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Arboretum: pulling…' }, async () => { + await rest.pullWorktree(wt.repoId, { path: wt.worktree.path, mode: mode.mode }); + void vscode.window.showInformationMessage('Arboretum: pulled.'); + }); + }); + }); cmd('arboretum.promoteWorktree', (node) => { const wt = asWorktree(node as AnyNode); if (wt) void promoteWorktreeFlow(deps, wt.repoId, wt.worktree); }); + cmd('arboretum.openWorktreeIde', (node) => { + const wt = asWorktree(node as AnyNode); + if (!wt) return; + void vscode.env.openExternal(vscode.Uri.parse(workspaceUrl(rest.url, wt.repoId, wt.worktree.path))); + }); // ---- démarrage de session ---- cmd('arboretum.startSession', (node) => { diff --git a/packages/vscode/src/config.ts b/packages/vscode/src/config.ts index 5cc9228..c5ed2de 100644 --- a/packages/vscode/src/config.ts +++ b/packages/vscode/src/config.ts @@ -5,6 +5,7 @@ export interface ConfigSnapshot { url: string; showExternalSessions: boolean; + showArchivedSessions: boolean; notifyOnWaiting: boolean; } @@ -19,3 +20,12 @@ export function wsUrlFromBase(baseUrl: string): string { const base = normalizeBaseUrl(baseUrl); return `${base.replace(/^http/, 'ws')}/ws`; } + +/** + * Construit l'URL de la vue IDE web d'un worktree — deep-link vers la route `/workspace/:repoId/:wt` + * (le routeur web est en history mode). `repoId` et le chemin du worktree sont encodés par segment. + */ +export function workspaceUrl(baseUrl: string, repoId: string, worktreePath: string): string { + const base = normalizeBaseUrl(baseUrl); + return `${base}/workspace/${encodeURIComponent(repoId)}/${encodeURIComponent(worktreePath)}`; +} diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index aaacc87..5bfad32 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -21,6 +21,9 @@ function readUrl(): string { function readShowExternal(): boolean { return vscode.workspace.getConfiguration('arboretum').get('showExternalSessions', false); } +function readShowArchived(): boolean { + return vscode.workspace.getConfiguration('arboretum').get('showArchivedSessions', false); +} function readNotify(): boolean { return vscode.workspace.getConfiguration('arboretum').get('notifyOnWaiting', true); } @@ -36,6 +39,7 @@ export async function activate(context: vscode.ExtensionContext): Promise const auth = new Auth(context.secrets); const store = new Store(); store.showExternalSessions = readShowExternal(); + store.showArchivedSessions = readShowArchived(); const rest = new RestClient({ baseUrl: readUrl() }); const reseed = async (): Promise => { @@ -54,6 +58,7 @@ export async function activate(context: vscode.ExtensionContext): Promise onSession: (e) => store.applySession(e), onWorktree: (e) => store.applyWorktree(e), onGroup: (e) => store.applyGroup(e), + onSessionArchived: (id) => store.archiveSession(id), onReady: () => void reseed(), onStatus: (s) => { const connected = s === 'open'; @@ -132,6 +137,10 @@ export async function activate(context: vscode.ExtensionContext): Promise if (e.affectsConfiguration('arboretum.showExternalSessions')) { store.setShowExternalSessions(readShowExternal()); } + if (e.affectsConfiguration('arboretum.showArchivedSessions')) { + store.setShowArchivedSessions(readShowArchived()); + await reseed(); // recharge la liste avec/sans les sessions archivées (includeArchived) + } }), ); diff --git a/packages/vscode/src/state/store.ts b/packages/vscode/src/state/store.ts index 4c95e81..3d3de8e 100644 --- a/packages/vscode/src/state/store.ts +++ b/packages/vscode/src/state/store.ts @@ -27,6 +27,8 @@ export class Store { /** affiche aussi les sessions externes (découvertes hors Arboretum). */ showExternalSessions = false; + /** affiche aussi les sessions managées auto-archivées par ancienneté (P10). */ + showArchivedSessions = false; onDidChange(cb: () => void): () => void { this.listeners.add(cb); @@ -40,6 +42,13 @@ export class Store { this.emit(); } + /** Bascule l'affichage des sessions archivées (le re-seed REST est piloté par l'extension). */ + setShowArchivedSessions(value: boolean): void { + if (this.showArchivedSessions === value) return; + this.showArchivedSessions = value; + this.emit(); + } + private emit(): void { for (const cb of this.listeners) cb(); } @@ -49,7 +58,7 @@ export class Store { const [repos, worktrees, sessions, groups] = await Promise.all([ rest.listRepos(), rest.listWorktrees(), - rest.listSessions(false), + rest.listSessions({ includeArchived: this.showArchivedSessions }), rest.listGroups(), ]); this.repos.clear(); @@ -95,6 +104,14 @@ export class Store { this.emit(); } + /** P10 : une session managée terminée vient d'être auto-archivée (message WS `session_archived`). */ + archiveSession(sessionId: string): void { + const prev = this.sessions.get(sessionId); + if (!prev) return; + this.sessions.set(sessionId, { ...prev, archived: true }); + this.emit(); + } + applyWorktree(e: WorktreeEvent): void { switch (e.type) { case 'repo_update': @@ -152,6 +169,7 @@ export class Store { return [...this.sessions.values()] .filter((s) => normPath(s.cwd) === wp) .filter((s) => this.showExternalSessions || s.source === 'managed') + .filter((s) => this.showArchivedSessions || !s.archived) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); } @@ -172,6 +190,7 @@ export class Store { return [...this.sessions.values()] .filter((s) => s.groupId === groupId) .filter((s) => this.showExternalSessions || s.source === 'managed') + .filter((s) => this.showArchivedSessions || !s.archived) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); } diff --git a/packages/vscode/src/views/tree-items.ts b/packages/vscode/src/views/tree-items.ts index ab19f6e..7fd1775 100644 --- a/packages/vscode/src/views/tree-items.ts +++ b/packages/vscode/src/views/tree-items.ts @@ -21,7 +21,7 @@ export function worktreeTreeItem(worktree: WorktreeSummary): vscode.TreeItem { item.contextValue = 'arboretum:worktree'; item.iconPath = new vscode.ThemeIcon(worktree.isMain ? 'star-full' : 'git-branch'); item.description = worktreeStatus(worktree); - item.tooltip = worktree.path; + item.tooltip = worktreeTooltip(worktree); item.resourceUri = vscode.Uri.file(worktree.path); return item; } @@ -32,9 +32,28 @@ function worktreeStatus(w: WorktreeSummary): string { if (w.git.ahead > 0) parts.push(`↑${w.git.ahead}`); if (w.git.behind > 0) parts.push(`↓${w.git.behind}`); if (w.git.dirtyCount > 0) parts.push(`●${w.git.dirtyCount}`); + // Compteurs fins (P7, additifs) : staged/conflits surfacés directement dans l'arbre. + if (w.git.stagedCount && w.git.stagedCount > 0) parts.push(`+${w.git.stagedCount}`); + if (w.git.conflictCount && w.git.conflictCount > 0) parts.push(`!${w.git.conflictCount}`); return parts.join(' '); } +/** Tooltip détaillé : chemin + statut git fin (P7) + dernier commit. */ +function worktreeTooltip(w: WorktreeSummary): vscode.MarkdownString { + const md = new vscode.MarkdownString(); + md.appendMarkdown(`\`${w.path}\`\n\n`); + const g = w.git; + const status: string[] = []; + if (g.stagedCount && g.stagedCount > 0) status.push(`${g.stagedCount} staged`); + if (g.unstagedCount && g.unstagedCount > 0) status.push(`${g.unstagedCount} unstaged`); + if (g.conflictCount && g.conflictCount > 0) status.push(`${g.conflictCount} conflicts`); + if (status.length === 0 && g.dirtyCount > 0) status.push(`${g.dirtyCount} changed`); + md.appendMarkdown(status.length > 0 ? status.join(' · ') : 'clean'); + if (g.ahead > 0 || g.behind > 0) md.appendMarkdown(` (↑${g.ahead} ↓${g.behind})`); + if (g.lastCommitSubject) md.appendMarkdown(`\n\n_${g.lastCommitSubject}_`); + return md; +} + export function sessionContextValue(s: SessionSummary): string { let v = `arboretum:session:${s.live ? 'live' : 'dead'}`; if (s.source === 'discovered') v += ':discovered'; @@ -72,6 +91,7 @@ function sessionDescription(s: SessionSummary): string { // Affiche 'available' pour idle (cohérent avec le libellé web) ; l'enum reste 'idle' côté protocole. if (s.live && s.activity) parts.push(s.activity === 'idle' ? 'available' : s.activity); else if (!s.live) parts.push('exited'); + if (s.archived) parts.push('archived'); if (s.source === 'discovered') parts.push('external'); if (s.groupId) parts.push('group'); if (s.waitingFor) parts.push(`— ${s.waitingFor}`); diff --git a/packages/vscode/test/config.test.ts b/packages/vscode/test/config.test.ts index e12a1dd..74f15b4 100644 --- a/packages/vscode/test/config.test.ts +++ b/packages/vscode/test/config.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { normalizeBaseUrl, wsUrlFromBase } from '../src/config.js'; +import { normalizeBaseUrl, workspaceUrl, wsUrlFromBase } from '../src/config.js'; describe('config', () => { it('normalise la base URL (slash final, défaut)', () => { @@ -12,4 +12,11 @@ describe('config', () => { expect(wsUrlFromBase('http://127.0.0.1:7317')).toBe('ws://127.0.0.1:7317/ws'); expect(wsUrlFromBase('https://box.ts.net')).toBe('wss://box.ts.net/ws'); }); + + it('construit l’URL de la vue IDE web (workspaceUrl, encodage par segment)', () => { + expect(workspaceUrl('http://127.0.0.1:7317', 'repo id', '/home/user/wt')).toBe( + 'http://127.0.0.1:7317/workspace/repo%20id/%2Fhome%2Fuser%2Fwt', + ); + expect(workspaceUrl('https://box.ts.net/', 'r', '/a/b')).toBe('https://box.ts.net/workspace/r/%2Fa%2Fb'); + }); }); diff --git a/packages/vscode/test/rest-client.test.ts b/packages/vscode/test/rest-client.test.ts index 5473081..d17775f 100644 --- a/packages/vscode/test/rest-client.test.ts +++ b/packages/vscode/test/rest-client.test.ts @@ -48,4 +48,31 @@ describe('RestClient', () => { expect(captured[0]?.init.method).toBe('POST'); expect(JSON.parse(String(captured[0]?.init.body))).toEqual({ path: '/w', message: 'msg' }); }); + + it('fetch/pull worktree : chemins et corps', async () => { + const captured: Captured[] = []; + const rest = new RestClient( + { baseUrl: 'http://h:1', token: 't' }, + fakeFetch({ ok: true, status: 200, body: { worktree: {} } }, captured), + ); + await rest.fetchWorktree('r', { path: '/w' }); + await rest.pullWorktree('r', { path: '/w', mode: 'rebase' }); + expect(captured[0]?.url).toBe('http://h:1/api/v1/repos/r/worktrees/fetch'); + expect(captured[1]?.url).toBe('http://h:1/api/v1/repos/r/worktrees/pull'); + expect(JSON.parse(String(captured[1]?.init.body))).toEqual({ path: '/w', mode: 'rebase' }); + }); + + it('listSessions construit la query includeHidden/includeArchived', async () => { + const captured: Captured[] = []; + const rest = new RestClient( + { baseUrl: 'http://h:1', token: 't' }, + fakeFetch({ ok: true, status: 200, body: { sessions: [] } }, captured), + ); + await rest.listSessions(); + await rest.listSessions({ includeArchived: true }); + await rest.listSessions({ includeHidden: true, includeArchived: true }); + expect(captured[0]?.url).toBe('http://h:1/api/v1/sessions'); + expect(captured[1]?.url).toBe('http://h:1/api/v1/sessions?includeArchived=true'); + expect(captured[2]?.url).toBe('http://h:1/api/v1/sessions?includeHidden=true&includeArchived=true'); + }); }); diff --git a/packages/vscode/test/ws-client.test.ts b/packages/vscode/test/ws-client.test.ts index 78f0caf..7521a1f 100644 --- a/packages/vscode/test/ws-client.test.ts +++ b/packages/vscode/test/ws-client.test.ts @@ -124,6 +124,18 @@ describe('ArbWsClient', () => { expect(acks[acks.length - 1]).toMatchObject({ bytes: FLOW.ACK_EVERY_BYTES }); }); + it('route session_archived vers onSessionArchived', () => { + const archived: string[] = []; + const f = new FakeSocket(); + const c = new ArbWsClient({ socketFactory: () => f, onSessionArchived: (id) => archived.push(id) }); + c.configure('http://127.0.0.1:7317', 'tok'); + c.connect(); + f.fireOpen(); + f.fireText({ type: 'hello_ok', protocol: 1, serverVersion: '2.0.0' }); + f.fireText({ type: 'session_archived', sessionId: 'sess-42' }); + expect(archived).toEqual(['sess-42']); + }); + it('envoie stdin/resize sur le bon canal', async () => { const p = client.attach({ sessionId: 'sess', mode: 'interactive', cols: 80, rows: 24, sink: makeNoopSink() }); fake.fireText({ type: 'attached', channel: 5, sessionId: 'sess', mode: 'interactive', controlling: true });