Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-22
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Why

- The Active Agents VS Code companion still showed version `0.0.1` after plugin edits because nothing enforced a visible version bump when the shipped extension changed.
- That makes local reinstall verification ambiguous in VS Code and makes it too easy to ship plugin edits behind a stale installed version label.
- The companion also depended on workspace marker discovery or the first view open before activation, so a freshly reloaded VS Code window could still look stale even after installing the newest extension files.
- Even after the repo had a newer companion build, the installed extension still needed a manual install-script run, so the newest repo copy was easy to miss.

## What Changes

- Bump the shipped Active Agents extension manifest version.
- Add a focused regression that requires a higher extension version whenever plugin-shipping files change on a branch.
- Keep the live and template extension manifests aligned so installs and scaffolds report the same version.
- Add `onStartupFinished` to the shipped Active Agents manifests and lock that startup activation contract in the focused regression suite.
- Auto-install the newest workspace companion build when the running extension version is older, then offer a Reload Window prompt so the new version boots immediately.

## Impact

- Local VS Code installs show a new extension version after plugin edits.
- Reloaded VS Code windows activate the Active Agents companion immediately instead of waiting for view-open or marker-discovery triggers.
- When a workspace ships a newer companion version than the running extension, the companion updates itself and offers an immediate reload path.
- Future plugin branches fail fast in tests if they forget to bump the extension version.
- Runtime behavior changes are limited to earlier extension activation after startup, one-shot auto-update/reload prompting, and extension metadata/install-path visibility.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## MODIFIED Requirements

### Requirement: Active Agents extension installs a visible current plugin version

The Active Agents VS Code companion SHALL expose a higher extension version whenever shipped plugin files change on a branch, and the live/template manifests SHALL stay aligned.

#### Scenario: Plugin edits require a version bump

- **GIVEN** a branch changes any shipped Active Agents extension files under `vscode/guardex-active-agents/**`, `templates/vscode/guardex-active-agents/**`, or `scripts/install-vscode-active-agents-extension.js`
- **WHEN** the focused extension regression suite runs
- **THEN** `vscode/guardex-active-agents/package.json` SHALL declare a version greater than the base branch version
- **AND** `templates/vscode/guardex-active-agents/package.json` SHALL match that same version exactly

### Requirement: Active Agents extension activates on VS Code startup

The Active Agents VS Code companion SHALL declare startup activation in both shipped manifests so the companion can initialize immediately after the VS Code window reloads.

#### Scenario: Startup activation stays in sync across shipped manifests

- **GIVEN** the live and template Active Agents extension manifests ship in the repo
- **WHEN** the extension install regression suite reads those manifests
- **THEN** both manifests SHALL include `onStartupFinished`
- **AND** the installed extension manifest SHALL preserve that same activation event list

### Requirement: Active Agents extension installs a newer workspace build and prompts reload

The Active Agents VS Code companion SHALL install a newer workspace-shipped build of itself when one is available and SHALL offer a reload action so the updated companion takes effect immediately.

#### Scenario: Running companion sees a newer workspace build

- **GIVEN** the running Active Agents extension version is older than `vscode/guardex-active-agents/package.json` in the open workspace
- **AND** the workspace contains `scripts/install-vscode-active-agents-extension.js`
- **WHEN** the companion activates
- **THEN** it SHALL run that install script with the workspace root as the current working directory
- **AND** it SHALL show a message offering `Reload Window`
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## 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 Finalize the scope around bumping the VS Code extension version whenever shipped plugin files change, activating the companion on VS Code startup, and auto-installing newer workspace builds.
- [x] 1.2 Define the normative version + startup + auto-update requirements in `specs/vscode-active-agents-extension/spec.md`.

## 2. Implementation

- [x] 2.1 Bump the live/template Active Agents extension manifests from `0.0.1` to `0.0.3`.
- [x] 2.2 Make the extension install regression read the current manifest version instead of hardcoding `0.0.1`.
- [x] 2.3 Add a focused regression that fails when extension-shipping files change without a higher version than the base branch.
- [x] 2.4 Add `onStartupFinished` to the live/template manifests and lock the installed-manifest/startup contract in the focused regression suite.
- [x] 2.5 Auto-install a newer workspace companion build on activate and offer a `Reload Window` action after the update lands.

## 3. Verification

- [x] 3.1 Run `node --test test/vscode-active-agents-session-state.test.js`.
- [x] 3.2 Run `openspec validate agent-codex-vscode-extension-version-bump-guard-2026-04-22-16-18 --type change --strict`.
- [x] 3.3 Run `openspec validate --specs`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run `gx branch finish --branch agent/codex/always-active-vscode-extension-2026-04-22-16-18 --base main --via-pr --wait-for-merge --cleanup`.
- [ ] 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).
119 changes: 119 additions & 0 deletions templates/vscode/guardex-active-agents/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.o
const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**';
const SESSION_SCAN_LIMIT = 200;
const REFRESH_DEBOUNCE_MS = 250;
const ACTIVE_AGENTS_MANIFEST_RELATIVE = path.join('vscode', 'guardex-active-agents', 'package.json');
const ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE = path.join('scripts', 'install-vscode-active-agents-extension.js');
const RELOAD_WINDOW_ACTION = 'Reload Window';
const UPDATE_LATER_ACTION = 'Later';
const SESSION_ACTIVITY_GROUPS = [
{ kind: 'blocked', label: 'BLOCKED' },
{ kind: 'working', label: 'WORKING NOW' },
Expand Down Expand Up @@ -565,6 +569,120 @@ function readCurrentBranch(repoRoot) {
}
}

function parseSimpleSemver(version) {
const parts = String(version || '')
.split('.')
.map((part) => Number.parseInt(part, 10));
if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
return null;
}
return parts;
}

function compareSimpleSemver(left, right) {
const leftParts = parseSimpleSemver(left);
const rightParts = parseSimpleSemver(right);
if (!leftParts || !rightParts) {
return 0;
}

for (let index = 0; index < leftParts.length; index += 1) {
if (leftParts[index] !== rightParts[index]) {
return leftParts[index] - rightParts[index];
}
}

return 0;
}

function readJsonFile(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (_error) {
return null;
}
}

function resolveActiveAgentsAutoUpdateCandidate(installedVersion) {
const candidates = [];

for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
const repoRoot = workspaceFolder?.uri?.fsPath;
if (!repoRoot) {
continue;
}

const manifestPath = path.join(repoRoot, ACTIVE_AGENTS_MANIFEST_RELATIVE);
const installScriptPath = path.join(repoRoot, ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE);
if (!fs.existsSync(manifestPath) || !fs.existsSync(installScriptPath)) {
continue;
}

const manifest = readJsonFile(manifestPath);
const nextVersion = typeof manifest?.version === 'string' ? manifest.version.trim() : '';
if (!nextVersion || compareSimpleSemver(nextVersion, installedVersion) <= 0) {
continue;
}

candidates.push({ repoRoot, installScriptPath, version: nextVersion });
}

candidates.sort((left, right) => compareSimpleSemver(right.version, left.version));
return candidates[0] || null;
}

function runActiveAgentsInstallScript(repoRoot, installScriptPath) {
return new Promise((resolve, reject) => {
cp.execFile(
process.execPath,
[installScriptPath],
{ cwd: repoRoot, encoding: 'utf8' },
(error, stdout, stderr) => {
if (error) {
reject(new Error(String(stderr || stdout || error.message || '').trim() || 'install failed'));
return;
}
resolve({ stdout, stderr });
},
);
});
}

async function maybeAutoUpdateActiveAgentsExtension(context) {
const installedVersion = typeof context?.extension?.packageJSON?.version === 'string'
? context.extension.packageJSON.version.trim()
: '';
if (!installedVersion) {
return;
}

const candidate = resolveActiveAgentsAutoUpdateCandidate(installedVersion);
if (!candidate) {
return;
}

try {
await runActiveAgentsInstallScript(candidate.repoRoot, candidate.installScriptPath);
} catch (error) {
const failure = typeof error?.message === 'string' && error.message.trim()
? error.message.trim()
: 'install failed';
vscode.window.showWarningMessage?.(
`GitGuardex Active Agents could not auto-update to ${candidate.version}: ${failure}`,
);
return;
}

const selection = await vscode.window.showInformationMessage?.(
`GitGuardex Active Agents updated to ${candidate.version}. Reload Window to use the newest companion.`,
RELOAD_WINDOW_ACTION,
UPDATE_LATER_ACTION,
);
if (selection === RELOAD_WINDOW_ACTION) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
}
}

function decorateSession(session, lockRegistry) {
return {
...session,
Expand Down Expand Up @@ -1349,6 +1467,7 @@ function activate(context) {
...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh),
);
void refreshController.refreshNow();
void maybeAutoUpdateActiveAgentsExtension(context);
}

function deactivate() {}
Expand Down
3 changes: 2 additions & 1 deletion templates/vscode/guardex-active-agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "GitGuardex Active Agents",
"description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.",
"publisher": "recodeee",
"version": "0.0.1",
"version": "0.0.3",
"license": "MIT",
"icon": "icon.png",
"engines": {
Expand All @@ -14,6 +14,7 @@
"Other"
],
"activationEvents": [
"onStartupFinished",
"workspaceContains:.omx/state/active-sessions",
"workspaceContains:.omx/agent-worktrees",
"workspaceContains:.omc/agent-worktrees",
Expand Down
Loading