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,26 @@
# agent-codex-vscode-active-agents-welcome-view-2026-04-22-10-58 (minimal / T1)

Branch: `agent/codex/vscode-active-agents-welcome-view-2026-04-22-10-58`

Replace the Active Agents empty-state placeholder with a native VS Code welcome view that exposes direct actions for starting a sandbox, opening the guide, and refreshing the panel.

Scope:
- Add `contributes.viewsWelcome` plus a `gitguardex.activeAgents.startAgent` command in both extension manifests.
- Remove the `InfoItem` empty-state fallback so the tree returns empty and VS Code renders the welcome content.
- Prompt for task + agent name, then send `gx branch start '<task>' '<agent>'` to an integrated terminal.
- Add a stable `README.md#quick-start` anchor for the guide link.
- Extend the active-agents regression suite to cover the new empty-state and command flow.

Verification:
- `node --test test/vscode-active-agents-session-state.test.js`

## Handoff

- Handoff: change=`agent-codex-vscode-active-agents-welcome-view-2026-04-22-10-58`; branch=`agent/codex/vscode-active-agents-welcome-view-2026-04-22-10-58`; scope=`templates/vscode/guardex-active-agents/*, vscode/guardex-active-agents/*, test/vscode-active-agents-session-state.test.js, openspec/changes/agent-codex-vscode-active-agents-welcome-view-2026-04-22-10-58/*`; action=`finish this sandbox via PR merge + cleanup after targeted verification`.
- Copy prompt: Continue `agent-codex-vscode-active-agents-welcome-view-2026-04-22-10-58` on branch `agent/codex/vscode-active-agents-welcome-view-2026-04-22-10-58`. Work inside the existing sandbox, review `openspec/changes/agent-codex-vscode-active-agents-welcome-view-2026-04-22-10-58/notes.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/codex/vscode-active-agents-welcome-view-2026-04-22-10-58 --base main --via-pr --wait-for-merge --cleanup`.

## Cleanup

- [ ] Run: `gx branch finish --branch agent/codex/vscode-active-agents-welcome-view-2026-04-22-10-58 --base main --via-pr --wait-for-merge --cleanup`
- [ ] Record PR URL + `MERGED` state in the completion handoff.
- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`).
21 changes: 13 additions & 8 deletions templates/vscode/guardex-active-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

Local VS Code companion for Guardex-managed repos.

## Quick Start

Use the welcome view in Source Control to create or inspect Guardex sandboxes quickly.

1. Install from a Guardex-wired repo:

```sh
node scripts/install-vscode-active-agents-extension.js
```

2. Reload the VS Code window.
3. In Source Control -> `Active Agents`, use `Start agent` to enter a task + agent name and run `gx branch start`.

What it does:

- Adds an `Active Agents` view to the Source Control container.
Expand All @@ -12,11 +25,3 @@ What it does:
- Derives `thinking` versus `working` from the live sandbox worktree, surfaces working counts in the repo/header summary, and shows changed-file counts for active edits.
- Uses VS Code's native animated `loading~spin` icon for the running-state affordance.
- Reads repo-local presence files from `.omx/state/active-sessions/`.

Install from a Guardex-wired repo:

```sh
node scripts/install-vscode-active-agents-extension.js
```

Then reload the VS Code window.
92 changes: 80 additions & 12 deletions templates/vscode/guardex-active-agents/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,6 @@ class SessionDecorationProvider {
}
}

class InfoItem extends vscode.TreeItem {
constructor(label, description = '') {
super(label, vscode.TreeItemCollapsibleState.None);
this.description = description;
this.iconPath = new vscode.ThemeIcon('info');
}
}

class RepoItem extends vscode.TreeItem {
constructor(repoRoot, sessions, changes) {
super(path.basename(repoRoot), vscode.TreeItemCollapsibleState.Expanded);
Expand Down Expand Up @@ -462,6 +454,83 @@ function countWorkingSessions(sessions) {
return sessions.filter((session) => session.activityKind === 'working').length;
}

function shellQuote(value) {
return `'${String(value).replace(/'/g, `'\\''`)}'`;
}

async function pickRepoRoot() {
const workspaceFolders = vscode.workspace.workspaceFolders || [];
if (workspaceFolders.length === 0) {
vscode.window.showInformationMessage?.('Open a Guardex workspace folder to start an agent.');
return null;
}

if (workspaceFolders.length === 1) {
return workspaceFolders[0].uri.fsPath;
}

const picks = workspaceFolders.map((folder) => ({
label: path.basename(folder.uri.fsPath),
description: folder.uri.fsPath,
repoRoot: folder.uri.fsPath,
}));
const selection = await vscode.window.showQuickPick?.(picks, {
placeHolder: 'Select the Guardex repo where gx branch start should run.',
});
return selection?.repoRoot || null;
}

async function promptStartAgentDetails() {
const taskName = await vscode.window.showInputBox?.({
prompt: 'Task for gx branch start',
placeHolder: 'vscode active agents welcome view',
ignoreFocusOut: true,
validateInput: (value) => value.trim() ? undefined : 'Task is required.',
});
if (!taskName) {
return null;
}

const agentName = await vscode.window.showInputBox?.({
prompt: 'Agent name for gx branch start',
placeHolder: 'codex',
value: 'codex',
ignoreFocusOut: true,
validateInput: (value) => value.trim() ? undefined : 'Agent name is required.',
});
if (!agentName) {
return null;
}

return {
taskName: taskName.trim(),
agentName: agentName.trim(),
};
}

async function startAgentFromPrompt(refresh) {
const repoRoot = await pickRepoRoot();
if (!repoRoot) {
return;
}

const details = await promptStartAgentDetails();
if (!details) {
return;
}

const terminal = vscode.window.createTerminal?.({
name: `GitGuardex: ${path.basename(repoRoot)}`,
cwd: repoRoot,
});
terminal?.show(true);
terminal?.sendText(
`gx branch start ${shellQuote(details.taskName)} ${shellQuote(details.agentName)}`,
true,
);
refresh();
}

function buildActiveAgentGroupNodes(sessions) {
const workingSessions = sessions
.filter((session) => session.activityKind === 'working')
Expand Down Expand Up @@ -511,9 +580,7 @@ class ActiveAgentsProvider {
+ (workingCount > 0 ? ` · ${workingCount} working now` : ''),
}
: undefined;
this.treeView.message = sessionCount > 0
? undefined
: 'Start a sandbox session to populate this view.';
this.treeView.message = undefined;
}

async syncRepoEntries() {
Expand Down Expand Up @@ -569,7 +636,7 @@ class ActiveAgentsProvider {
const repoEntries = await this.syncRepoEntries();

if (repoEntries.length === 0) {
return [new InfoItem('No active Guardex agents', 'Open or start a sandbox session.')];
return [];
}

return repoEntries.map((entry) => new RepoItem(entry.repoRoot, entry.sessions, entry.changes));
Expand Down Expand Up @@ -638,6 +705,7 @@ function activate(context) {
context.subscriptions.push(
treeView,
vscode.window.registerFileDecorationProvider(decorationProvider),
vscode.commands.registerCommand('gitguardex.activeAgents.startAgent', () => startAgentFromPrompt(refresh)),
vscode.commands.registerCommand('gitguardex.activeAgents.refresh', refresh),
vscode.commands.registerCommand('gitguardex.activeAgents.openWorktree', async (session) => {
if (!session?.worktreePath) {
Expand Down
10 changes: 10 additions & 0 deletions templates/vscode/guardex-active-agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "gitguardex.activeAgents.startAgent",
"title": "Start Guardex Agent"
},
{
"command": "gitguardex.activeAgents.refresh",
"title": "Refresh Active Agents"
Expand Down Expand Up @@ -57,6 +61,12 @@
}
]
},
"viewsWelcome": [
{
"view": "gitguardex.activeAgents",
"contents": "No active Guardex agents are visible in this workspace yet.\n\n[Start agent](command:gitguardex.activeAgents.startAgent)\n[Open guide](https://github.com/recodeee/gitguardex/blob/main/vscode/guardex-active-agents/README.md#quick-start)\n[Refresh](command:gitguardex.activeAgents.refresh)"
}
],
"menus": {
"view/title": [
{
Expand Down
46 changes: 42 additions & 4 deletions test/vscode-active-agents-session-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ function createMockVscode(tempRoot) {
openedDocuments: [],
shownDocuments: [],
infoMessages: [],
inputResponses: [],
quickPickCalls: [],
quickPickResponse: undefined,
warningMessages: [],
watchers: [],
};
Expand Down Expand Up @@ -206,6 +209,11 @@ function createMockVscode(tempRoot) {
registrations.warningMessages.push(args);
return undefined;
},
showInputBox: async () => registrations.inputResponses.shift(),
showQuickPick: async (items, options) => {
registrations.quickPickCalls.push({ items, options });
return registrations.quickPickResponse;
},
createTerminal: (options) => {
const terminal = {
options,
Expand Down Expand Up @@ -448,12 +456,42 @@ test('active-agents extension registers tree and decoration providers', async ()

const provider = registrations.providers[0].provider;
assert.equal(typeof provider.getTreeItem, 'function');
assert.equal(typeof registrations.commands.get('gitguardex.activeAgents.startAgent'), 'function');

const [rootItem] = await provider.getChildren();
assert.equal(rootItem.label, 'No active Guardex agents');
assert.equal(provider.getTreeItem(rootItem), rootItem);
const rootItems = await provider.getChildren();
assert.deepEqual(rootItems, []);
assert.equal(registrations.treeViews[0].badge, undefined);
assert.equal(registrations.treeViews[0].message, 'Start a sandbox session to populate this view.');
assert.equal(registrations.treeViews[0].message, undefined);

for (const subscription of context.subscriptions) {
subscription.dispose?.();
}
});

test('active-agents extension startAgent command prompts and runs gx branch start in a terminal', async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-vscode-start-agent-'));
const { registrations, vscode } = createMockVscode(tempRoot);
registrations.inputResponses.push('demo task', 'codex');
const extension = loadExtensionWithMockVscode(vscode);
const context = { subscriptions: [] };

extension.activate(context);

await registrations.commands.get('gitguardex.activeAgents.startAgent')();

assert.equal(registrations.terminals.length, 1);
assert.deepEqual(registrations.terminals[0].options, {
name: `GitGuardex: ${path.basename(tempRoot)}`,
cwd: tempRoot,
});
assert.equal(registrations.terminals[0].shown, true);
assert.deepEqual(registrations.terminals[0].sentTexts, [
{
text: "gx branch start 'demo task' 'codex'",
addNewLine: true,
},
]);
assert.deepEqual(registrations.quickPickCalls, []);

for (const subscription of context.subscriptions) {
subscription.dispose?.();
Expand Down
21 changes: 13 additions & 8 deletions vscode/guardex-active-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

Local VS Code companion for Guardex-managed repos.

## Quick Start

Use the welcome view in Source Control to create or inspect Guardex sandboxes quickly.

1. Install from a Guardex-wired repo:

```sh
node scripts/install-vscode-active-agents-extension.js
```

2. Reload the VS Code window.
3. In Source Control -> `Active Agents`, use `Start agent` to enter a task + agent name and run `gx branch start`.

What it does:

- Adds an `Active Agents` view to the Source Control container.
Expand All @@ -12,11 +25,3 @@ What it does:
- Derives `thinking` versus `working` from the live sandbox worktree, surfaces working counts in the repo/header summary, and shows changed-file counts for active edits.
- Uses VS Code's native animated `loading~spin` icon for the running-state affordance.
- Reads repo-local presence files from `.omx/state/active-sessions/`.

Install from a Guardex-wired repo:

```sh
node scripts/install-vscode-active-agents-extension.js
```

Then reload the VS Code window.
Loading