From dadc73d19fae9ecfca1d096816423f1cccda4997 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Tue, 21 Apr 2026 18:18:19 +0200 Subject: [PATCH] Restore Active Agents view registration in VS Code The Active Agents provider was missing getTreeItem, so VS Code crashed the SCM view during render even when session records were present. This restores the required provider method in the installable template bundle and locks the failure mode with a mocked extension-host regression test. Constraint: This branch installs the VS Code companion from templates/vscode/guardex-active-agents Rejected: Reordering installer source precedence | broader behavior change than needed for the reported crash Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the provider contract test aligned with any future extension activation refactors Tested: node --test test/vscode-active-agents-session-state.test.js Tested: openspec validate agent-codex-demo-vscode-active-agents-2026-04-21-18-04 --type change --strict Tested: openspec validate --specs Not-tested: Live VS Code extension host after window reload --- .../.openspec.yaml | 2 + .../proposal.md | 14 +++ .../specs/demo-vscode-active-agents/spec.md | 14 +++ .../tasks.md | 29 +++++ .../vscode/guardex-active-agents/extension.js | 4 + ...vscode-active-agents-session-state.test.js | 110 ++++++++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/.openspec.yaml create mode 100644 openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/proposal.md create mode 100644 openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/specs/demo-vscode-active-agents/spec.md create mode 100644 openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/tasks.md diff --git a/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/.openspec.yaml b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/.openspec.yaml new file mode 100644 index 0000000..4b8c565 --- /dev/null +++ b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-21 diff --git a/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/proposal.md b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/proposal.md new file mode 100644 index 0000000..6c368d9 --- /dev/null +++ b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/proposal.md @@ -0,0 +1,14 @@ +## Why + +- The Guardex Active Agents VS Code companion currently throws `this._dataProvider.getTreeItem is not a function` at render time. +- That crash keeps the Source Control view from showing live sandbox sessions even when `.omx/state/active-sessions/*.json` exists. + +## What Changes + +- Add the missing `getTreeItem` implementation to the Active Agents tree provider in the extension bundle this branch installs. +- Add focused regression coverage that activates the extension against a mocked VS Code host and asserts the provider satisfies the tree-data contract. + +## Impact + +- Affected surfaces: `templates/vscode/guardex-active-agents/extension.js`, `test/vscode-active-agents-session-state.test.js`, and this change workspace. +- Risk is narrow because the fix only restores the provider method VS Code already expects and the new test covers the exact failure mode. diff --git a/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/specs/demo-vscode-active-agents/spec.md b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/specs/demo-vscode-active-agents/spec.md new file mode 100644 index 0000000..edff5d2 --- /dev/null +++ b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/specs/demo-vscode-active-agents/spec.md @@ -0,0 +1,14 @@ +## ADDED Requirements + +### Requirement: Active Agents tree provider satisfies the VS Code contract +The Guardex Active Agents extension SHALL register a tree data provider that implements the tree item resolution contract required by `gitguardex.activeAgents`. + +#### Scenario: Provider exposes tree item resolution +- **WHEN** the Active Agents extension activates +- **THEN** the registered provider exposes a callable `getTreeItem` +- **AND** rendering tree items does not throw `this._dataProvider.getTreeItem is not a function`. + +#### Scenario: Regression coverage locks the provider contract +- **WHEN** the extension is exercised under the repo test harness with a mocked VS Code host +- **THEN** activation succeeds +- **AND** the regression test fails if the provider is registered without `getTreeItem`. diff --git a/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/tasks.md b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/tasks.md new file mode 100644 index 0000000..12a20ba --- /dev/null +++ b/openspec/changes/agent-codex-demo-vscode-active-agents-2026-04-21-18-04/tasks.md @@ -0,0 +1,29 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline. + +## 1. Specification + +- [x] 1.1 Capture the Active Agents VS Code failure mode and acceptance criteria for `agent-codex-demo-vscode-active-agents-2026-04-21-18-04`. +- [x] 1.2 Define the tree-provider requirements in `specs/demo-vscode-active-agents/spec.md`. + +## 2. Implementation + +- [x] 2.1 Add the missing `getTreeItem` implementation to the Active Agents extension bundle this branch installs. +- [x] 2.2 Add focused regression coverage for extension activation/provider registration. + +## 3. Verification + +- [x] 3.1 Run `node --test test/vscode-active-agents-session-state.test.js`. +- [x] 3.2 Run `openspec validate agent-codex-demo-vscode-active-agents-2026-04-21-18-04 --type change --strict`. +- [x] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `bash scripts/agent-branch-finish.sh --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch). diff --git a/templates/vscode/guardex-active-agents/extension.js b/templates/vscode/guardex-active-agents/extension.js index 7473f73..7b340f5 100644 --- a/templates/vscode/guardex-active-agents/extension.js +++ b/templates/vscode/guardex-active-agents/extension.js @@ -53,6 +53,10 @@ class ActiveAgentsProvider { this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event; } + getTreeItem(element) { + return element; + } + refresh() { this.onDidChangeTreeDataEmitter.fire(); } diff --git a/test/vscode-active-agents-session-state.test.js b/test/vscode-active-agents-session-state.test.js index 9922076..4d89e5f 100644 --- a/test/vscode-active-agents-session-state.test.js +++ b/test/vscode-active-agents-session-state.test.js @@ -15,6 +15,7 @@ const sessionSchema = require(path.join( 'guardex-active-agents', 'session-schema.js', )); +const extensionEntry = path.join(repoRoot, 'templates', 'vscode', 'guardex-active-agents', 'extension.js'); function runNode(scriptPath, args, options = {}) { return cp.spawnSync('node', [scriptPath, ...args], { @@ -23,6 +24,92 @@ function runNode(scriptPath, args, options = {}) { }); } +function loadExtensionWithMockVscode(mockVscode) { + const Module = require('node:module'); + const originalLoad = Module._load; + delete require.cache[require.resolve(extensionEntry)]; + + Module._load = function patchedModuleLoad(request, parent, isMain) { + if (request === 'vscode') { + return mockVscode; + } + return originalLoad.call(this, request, parent, isMain); + }; + + try { + return require(extensionEntry); + } finally { + Module._load = originalLoad; + } +} + +function createMockVscode(tempRoot) { + const registrations = { + providers: [], + }; + + class TreeItem { + constructor(label, collapsibleState) { + this.label = label; + this.collapsibleState = collapsibleState; + } + } + + class ThemeIcon { + constructor(id) { + this.id = id; + } + } + + class EventEmitter { + constructor() { + this.event = () => {}; + } + + fire() {} + } + + const disposable = () => ({ dispose() {} }); + const fileWatcher = { + onDidCreate() {}, + onDidChange() {}, + onDidDelete() {}, + dispose() {}, + }; + + return { + registrations, + vscode: { + TreeItem, + ThemeIcon, + EventEmitter, + TreeItemCollapsibleState: { + None: 0, + Expanded: 1, + }, + commands: { + executeCommand: async () => {}, + registerCommand: () => disposable(), + }, + Uri: { + file: (fsPath) => ({ fsPath }), + }, + window: { + registerTreeDataProvider: (viewId, provider) => { + registrations.providers.push({ viewId, provider }); + return disposable(); + }, + }, + workspace: { + createFileSystemWatcher: () => fileWatcher, + findFiles: async () => [], + onDidChangeWorkspaceFolders: () => disposable(), + workspaceFolders: [{ uri: { fsPath: tempRoot } }], + }, + }, + }; +} + test('agent-session-state writes and removes active session records', () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-active-session-')); const branch = 'agent/codex/demo-task'; @@ -132,3 +219,26 @@ test('install-vscode-active-agents-extension installs the current extension vers assert.equal(fs.existsSync(staleDir), false); assert.match(result.stdout, /Reload the VS Code window/); }); + +test('active-agents extension registers a provider with getTreeItem', async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-view-')); + const { registrations, vscode } = createMockVscode(tempRoot); + const extension = loadExtensionWithMockVscode(vscode); + const context = { subscriptions: [] }; + + extension.activate(context); + + assert.equal(registrations.providers.length, 1); + assert.equal(registrations.providers[0].viewId, 'gitguardex.activeAgents'); + + const provider = registrations.providers[0].provider; + assert.equal(typeof provider.getTreeItem, 'function'); + + const [rootItem] = await provider.getChildren(); + assert.equal(rootItem.label, 'No active Guardex agents'); + assert.equal(provider.getTreeItem(rootItem), rootItem); + + for (const subscription of context.subscriptions) { + subscription.dispose?.(); + } +});