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?.(); + } +});