Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
84d4408
feat(workflow): unify Agent panel launch for all workflow actions
cursoragent Oct 8, 2026
852636d
fix(workflow): clean up webview handler imports after agent launch re…
cursoragent Oct 8, 2026
c0142f8
fix(workflow): resolve nearest-root binding gate and launch UX
cursoragent Oct 8, 2026
4502021
fix(workflow): migrate legacy Cursor launch settings and launch UX
cursoragent Oct 8, 2026
321be3c
fix(workflow): audit 4502021 — non-blocking launch UX and B2 activate
cursoragent Oct 8, 2026
94e08a6
fix(workflow): QA regression fixes for 0.2.4-agent-panel.5
cursoragent Oct 8, 2026
8a91275
test(workflow): assert launch failures suppress NEEDS ATTENTION
cursoragent Oct 8, 2026
59ced19
fix(workflow): address PR #24 retest failures for agent-panel.6
cursoragent Oct 8, 2026
8e93202
ci(test): fix vitest OOM and launch retry test recursion
cursoragent Oct 8, 2026
a117776
fix(workflow): agent-panel.7 dedupe after failure and V&A task refresh
cursoragent Oct 8, 2026
743574d
fix(webview): stable tasks fetch keys and V&A visibility loading (.8)
cursoragent Oct 8, 2026
a6935e3
fix(webview): V&A tasks refetch without stale Archive Now (.9)
cursoragent Oct 8, 2026
fdec4db
fix(workflow): agent-panel.10 dedupe pending, V&A copy, CLI version c…
cursoragent Oct 8, 2026
5404ebe
fix(webview): empty tasks loading, stale cache gating, fetch forceRel…
cursoragent Oct 8, 2026
e61fa8c
fix(webview): tasks read errors and scoped invalidation loading (.12)
cursoragent Oct 8, 2026
2a39813
fix(webview): restore receipt on deduped launch and dedupe initial ar…
cursoragent Oct 8, 2026
88f787f
fix(launch): await composer.createNew and guard concurrent Cursor pan…
cursoragent Oct 8, 2026
b70be56
fix(launch): do not await composer.createNew; quick-fail fallback onl…
cursoragent Oct 8, 2026
1be4a80
fix(workflow): copy-only skip dedupe and Launching; i18n + paint anch…
cursoragent Oct 8, 2026
a4fbae3
fix(lint): use window.requestAnimationFrame in launch pending hook
cursoragent Oct 8, 2026
6df0c99
fix(workflow): align webview copy-only with host settings (.16)
cursoragent Oct 8, 2026
44213aa
fix: copy-only respects cursorLaunchMode only on Cursor adapter (.17)
cursoragent Oct 8, 2026
bdeab81
fix: preserve isCursorHost on ChangeDetail presentation refresh (.18)
cursoragent Oct 8, 2026
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ jobs:

- name: Test
run: pnpm test
env:
NODE_OPTIONS: --max-old-space-size=8192

- name: Typecheck
run: ./scripts/check-typecheck-baseline.sh

- name: Lint
run: npx eslint src/
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Workflow actions (Apply, Verify, Archive, Continue, etc.) now launch through a unified IDE Agent panel path on Cursor and VS Code instead of opening an editor-area `agent` terminal by default.
- Cursor hosts use runtime command detection with layered fallback: `composer.createNew` → `workbench.action.chat.open` + `composerMode.agent` → deeplink → clipboard.
- Added `openspec.agentAutoSubmit` (`never` / `readOnly` / `always`, default `readOnly`). Auto-send applies only when `composer.createNew` succeeds.
- Legacy `openspec.cursorLaunchMode` values `deeplink` and `chatCommand` migrate to `agentPanel`. Dashboard Ready to Verify CTA label is now **Run Verify** (action matches label).

## [0.2.3] - 2026-09-22

### Added
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ OpenSpec brings change planning and execution into the editor. Review what needs
- **Stores and Worksets**: Use registered Stores as planning roots, then browse, inspect, create, and open trusted multi-folder Worksets without leaving the sidebar.
- **Recommended actions**: A compact, resolver-backed action rail surfaces up to three next steps by priority: Needs Attention, Ready to Verify, then Recommended.
- **Change detail**: Proposal, Specs, Design, Tasks, and Verify & Archive tabs with markdown rendering, task progress, and workflow controls.
- **Safe workflow routing**: Review and verify actions open the appropriate detail or interactive terminal flow; high-impact archive actions stay confirmation-protected.
- **Safe workflow routing**: Workflow buttons open the IDE Agent panel with `/opsx-*` commands; high-impact archive actions stay confirmation-protected.
- **CLI integration**: OpenSpec CLI list, status, new, archive, Store, and Workset flows with retry, timeout, and `openspec.cliPath` fallback.
- **Editor-native**: Uses VS Code theme tokens, Codicons, keyboard-accessible controls, and narrow-sidebar-friendly layouts.

Expand Down Expand Up @@ -105,7 +105,8 @@ Open Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`):
| `openspec.taskExecutionMode` | `fillChat` | When clicking task execute: `auto` = run via adapter; `fillChat` = fill chat or copy to clipboard |
| `openspec.workflowLaunchMode` | `clipboard` (VS Code) / smart launch in Cursor | **Launch with adapter (recommended)** or **Copy command only (safe)**. In Cursor with unset settings, defaults to launch mode. |
| `openspec.preferredAgentAdapter` | `clipboard` (VS Code) / `cursor` in Cursor launch mode | Preferred agent executor when launch mode is adapter: `clipboard`, `cursor`, `vscode-copilot`, `claude-code`, or `opencode` |
| `openspec.cursorLaunchMode` | `clipboard` (stored default) / `deeplink` in Cursor launch mode | Cursor launch behavior: `deeplink`, `chatCommand`, `clipboard`, or explicit `agentCli` (**may modify workspace files**; never auto-selected) |
| `openspec.agentAutoSubmit` | `readOnly` | Auto-send in Cursor Agent panel: `never`, `readOnly` (explore/verify only), or `always` (composer layer only) |
| `openspec.cursorLaunchMode` | `clipboard` (stored default) / `agentPanel` in Cursor launch mode | `agentPanel` (IDE Agent with fallback), `clipboard`, or explicit `agentCli` (**may modify workspace files**) |
| `openspec.taskDependencyPolicy` | `block` | When preceding tasks are incomplete: `block` = prevent execution; `warn` = show warning and allow proceed |
| `openspec.cursorAgentModel` | `auto` | Cursor Agent CLI model for explicit Agent CLI execution. Use `auto` or a specific model name |
| `openspec.agentModel` | `auto` | Legacy Cursor Agent CLI model setting; prefer `openspec.cursorAgentModel` |
Expand All @@ -114,11 +115,10 @@ Open Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`):
**Task execution & Adapters**

- **Clipboard** (`clipboard`): Always available. Select **Copy command only (safe)** when you only want `/opsx:*` commands copied.
- **Cursor** (`cursor`): In Cursor, defaults to opening the prompt deeplink (`deeplink`) or Chat (`chatCommand`). `agentCli` is available only when explicitly selected and may modify workspace files.
- **Cursor** (`cursor`): In Cursor, defaults to the Agent panel (`agentPanel`) with runtime fallback (composer → chat → deeplink → clipboard). `agentCli` is opt-in and may modify workspace files.
- **OpenCode** (`opencode`): Uses `/opsx-<action>` command format when routed through the adapter.
- In Cursor, unset workflow settings smart-default to launch mode with the Cursor adapter. Choose **Copy command only (safe)** in settings to restore copy-only behavior. If launch fails, the extension falls back to clipboard with a visible notice.
- Verify and Archive are intentionally different: they open the dedicated `Verify & Archive` tab and run inside a real VS Code terminal editor instead of the headless `agentCli` path.
- Within that tab, `Review & Archive` keeps Agent review interactive, while `Archive Now` is the explicit direct-archive escape path and is disabled until the bound workflow is complete.
- Verify and Archive use the same Agent panel launch path as Apply and Continue. The **Verify & Archive** tab also offers **Archive Now** (direct filesystem archive) when the workflow is complete.

### Dashboard

Expand Down
33 changes: 25 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "openspec-workflow",
"displayName": "OpenSpec Workflow",
"description": "Project-first OpenSpec dashboard for changes, specs, stores, worksets, and safe workflow actions",
"version": "0.2.3",
"version": "0.2.4-agent-panel.18",
"publisher": "randysss",
"license": "MIT",
"engines": {
Expand Down Expand Up @@ -101,23 +101,40 @@
"default": "clipboard",
"description": "Preferred Agent executor adapter used when workflowLaunchMode is adapter."
},
"openspec.agentAutoSubmit": {
"type": "string",
"enum": [
"never",
"readOnly",
"always"
],
"default": "readOnly",
"description": "When launching in the Cursor Agent panel via composer.createNew, auto-send the workflow command. readOnly auto-sends explore and verify only; other layers always prefill.",
"enumDescriptions": [
"Never auto-send; always prefill and wait for Enter.",
"Auto-send read-only workflow commands (explore, verify).",
"Auto-send every workflow command (including apply and archive)."
]
},
"openspec.cursorLaunchMode": {
"type": "string",
"enum": [
"clipboard",
"agentPanel",
"deeplink",
"chatCommand",
"agentCli"
],
"default": "clipboard",
"description": "Cursor adapter launch behavior for non-interactive workflow actions. Verify and Archive use the dedicated Verify & Archive tab; `agentCli` here means headless Agent CLI.",
"description": "Cursor launch behavior. agentPanel opens the IDE Agent panel with runtime fallback (composer → chat → deeplink → clipboard). agentCli runs headless Agent CLI and may modify files.",
"enumDescriptions": [
"Only copy the generated command to the clipboard. Does not open Cursor or start Agent CLI.",
"Open Cursor through the prompt deeplink with the generated command prefilled. The command is also copied as fallback.",
"Open Cursor Chat through the VS Code/Cursor chat command query. The command is also copied as fallback.",
"Run the generated command through Cursor Agent CLI in headless mode. May modify workspace files. Requires explicit selection."
"Only copy the generated command to the clipboard.",
"Open the Cursor Agent panel with `/opsx-*` prefilled (recommended).",
"Legacy alias for agentPanel.",
"Legacy alias for agentPanel.",
"Headless Cursor Agent CLI in the Output panel. May modify workspace files."
],
"markdownDescription": "Cursor adapter launch behavior for workflow actions.\\n\\n- `clipboard`: only copy the generated `/opsx:*` command.\\n- `deeplink`: open Cursor prompt deeplink with `/opsx-*` prefilled, while keeping clipboard fallback.\\n- `chatCommand`: try Cursor Chat query prefill, while keeping clipboard fallback.\\n- `agentCli`: **May modify workspace files.** Run Cursor Agent CLI directly in headless mode; never used unless you explicitly select it."
"markdownDescription": "Cursor workflow launch behavior.\\n\\n- `agentPanel` (default on Cursor): IDE Agent panel with layered fallback.\\n- `clipboard`: copy only.\\n- `agentCli`: **May modify workspace files.** Headless CLI opt-in.\\n- `deeplink` / `chatCommand`: migrated to `agentPanel`."
},
"openspec.taskDependencyPolicy": {
"type": "string",
Expand Down Expand Up @@ -245,7 +262,7 @@
"build:webview": "vite build",
"dev:webview": "vite",
"postinstall": "node scripts/link-vitest-pnpm.js",
"test": "vitest run",
"test": "NODE_OPTIONS=--max-old-space-size=8192 vitest run",
"test:watch": "vitest",
"package": "node scripts/package-with-marketplace-readme.js",
"verify:vsix-webview": "node scripts/verify-vsix-webview.js",
Expand Down
19 changes: 19 additions & 0 deletions scripts/check-typecheck-baseline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail

BASELINE="${TSC_BASELINE_ERROR_COUNT:-13}"
LOG="$(mktemp)"
trap 'rm -f "$LOG"' EXIT

# Must match local: npx tsc --noEmit -p tsconfig.typecheck.json
npx tsc --noEmit -p tsconfig.typecheck.json >"$LOG" 2>&1 || true
COUNT="$(rg -c 'error TS' "$LOG" || true)"
COUNT="${COUNT:-0}"

if [ "$COUNT" -gt "$BASELINE" ]; then
echo "TypeScript error count $COUNT exceeds baseline $BASELINE"
cat "$LOG"
exit 1
fi

echo "TypeScript check OK ($COUNT errors, baseline $BASELINE)"
4 changes: 3 additions & 1 deletion scripts/verify-vsix-webview.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const path = require('path');
const { execSync } = require('child_process');

const rootDir = path.resolve(__dirname, '..');
const inputPath = path.resolve(rootDir, process.argv[2] ?? 'openspec-workflow-0.2.2.vsix');
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
const defaultVsixName = `${pkg.name}-${pkg.version}.vsix`;
const inputPath = path.resolve(rootDir, process.argv[2] ?? defaultVsixName);

const REQUIRED_MARKERS = [
'openspec-webview-executor-ui-v1',
Expand Down
5 changes: 1 addition & 4 deletions src/extension/adapters/adapterRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as vscode from 'vscode';

Check warning on line 1 in src/extension/adapters/adapterRegistry.ts

View workflow job for this annotation

GitHub Actions / build-test-lint

'vscode' is defined but never used
import type { AgentAdapterOption } from '../../shared/executorLaunchPresentation';
import { clipboardAdapter } from './clipboard-adapter';
import { claudeCodeAdapter } from './claude-code-adapter';
Expand All @@ -7,6 +7,7 @@
import { vscodeChatAdapter } from './vscode-chat-adapter';
import { vscodeCopilotAdapter } from './vscode-copilot-adapter';
import type { IAgentExecutorAdapter } from '../services/agentExecutor.types';
import { isCursorHost } from '../utils/isCursorHost';

const registeredAdapters: IAgentExecutorAdapter[] = [
vscodeCopilotAdapter,
Expand All @@ -17,10 +18,6 @@
clipboardAdapter,
];

function isCursorHost(): boolean {
return (vscode.env.appName ?? '').toLowerCase().includes('cursor');
}

/** Runtime launch adapters (must pass isAvailable). */
export async function getAvailableAdapters(): Promise<IAgentExecutorAdapter[]> {
const results = await Promise.all(
Expand Down
45 changes: 21 additions & 24 deletions src/extension/adapters/cursor-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import type {
import { logger } from '../utils/logger';
import { t } from '../../i18n';
import { buildWorkflowCommand } from '../../shared/workflowCommand';
import { buildCursorPromptDeeplink } from '../services/cursorDeeplink';
import { getCursorAgentModel, getWorkflowLaunchConfig } from '../services/workflowLaunchConfig';
import { launchAgentPanelPrompt } from '../services/agentPanelLauncher';
import { notifyWorkflowCommandCopied } from '../services/workflowClipboardNotify';

const ADAPTER_ID = 'cursor';
const DISPLAY_NAME = 'Cursor (agent CLI)';
const DISPLAY_NAME = 'Cursor (Agent)';
const OUTPUT_CHANNEL_NAME = 'OpenSpec (Agent)';
const NO_OUTPUT_NOTICE_MS = 10000;
const STILL_RUNNING_NOTICE_MS = 30000;
Expand Down Expand Up @@ -160,31 +161,27 @@ export const cursorAdapter: IAgentExecutorAdapter = {
}

if (cursorLaunchMode === 'clipboard') {
vscode.window.showInformationMessage(t('workflow.copiedCommand', { command: text }));
notifyWorkflowCommandCopied(text);
return { success: true, adapterId: ADAPTER_ID, message: 'Copied to clipboard' };
}

if (cursorLaunchMode === 'chatCommand') {
try {
await vscode.commands.executeCommand('workbench.action.chat.open', {
query: text,
isPartialQuery: true,
});
vscode.window.showInformationMessage(t('cursor.chatOpenedCopied', { command: text }));
return { success: true, adapterId: ADAPTER_ID, message: 'Chat opened with prompt' };
} catch {
vscode.window.showInformationMessage(t('cursor.chatOpenFailedCopied', { command: text }));
return { success: true, adapterId: ADAPTER_ID, message: 'Copied to clipboard' };
}
}

try {
await vscode.env.openExternal(vscode.Uri.parse(buildCursorPromptDeeplink(text)));
vscode.window.showInformationMessage(t('cursor.promptOpenedCopied', { command: text }));
return { success: true, adapterId: ADAPTER_ID, message: 'Cursor prompt opened' };
} catch {
vscode.window.showInformationMessage(t('cursor.openFailedCopied', { command: text }));
return { success: true, adapterId: ADAPTER_ID, message: 'Copied to clipboard' };
const panelResult = await launchAgentPanelPrompt({
text,
autoSubmit: false,
});
if (panelResult.outcome === 'copied') {
vscode.window.showInformationMessage(t('agentLaunch.copied', { command: text }));
} else if (panelResult.outcome === 'started') {
vscode.window.showInformationMessage(t('agentLaunch.started', { command: text }));
} else if (panelResult.layer === 'deeplink') {
vscode.window.showInformationMessage(t('agentLaunch.deeplinkPrefilled', { command: text }));
} else {
vscode.window.showInformationMessage(t('agentLaunch.openedPanel', { command: text }));
}
return {
success: panelResult.success,
adapterId: ADAPTER_ID,
message: panelResult.layer,
};
},
};
22 changes: 12 additions & 10 deletions src/extension/adapters/vscode-chat-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode';
import type { IAgentExecutorAdapter, TaskExecuteRequest, TaskExecuteResult } from '../services/agentExecutor.types';
import { t } from '../../i18n';
import { launchAgentPanelPrompt } from '../services/agentPanelLauncher';

const ADAPTER_ID = 'vscode-chat';
const DISPLAY_NAME = 'VS Code Chat';
Expand All @@ -23,16 +24,17 @@ export const vscodeChatAdapter: IAgentExecutorAdapter = {

async fillChat(request: TaskExecuteRequest): Promise<TaskExecuteResult> {
const prompt = request.promptOverride ?? `/opsx:apply ${request.changeName}`;
try {
await vscode.commands.executeCommand('workbench.action.chat.open', {
query: prompt,
isPartialQuery: true,
});
return { success: true, adapterId: ADAPTER_ID, message: 'Chat opened with prompt' };
} catch {
await vscode.env.clipboard.writeText(prompt);
vscode.window.showInformationMessage(t('clipboard.copiedChat'));
return { success: true, adapterId: ADAPTER_ID, message: 'Copied to clipboard (fallback)' };
await vscode.env.clipboard.writeText(prompt);
const panelResult = await launchAgentPanelPrompt({ text: prompt, autoSubmit: false });
if (panelResult.outcome === 'copied') {
vscode.window.showInformationMessage(t('agentLaunch.copied', { command: prompt }));
} else {
vscode.window.showInformationMessage(t('agentLaunch.prefilledChat', { command: prompt }));
}
return {
success: panelResult.success,
adapterId: ADAPTER_ID,
message: panelResult.layer,
};
},
};
22 changes: 12 additions & 10 deletions src/extension/adapters/vscode-copilot-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode';
import type { IAgentExecutorAdapter, TaskExecuteRequest, TaskExecuteResult } from '../services/agentExecutor.types';
import { t } from '../../i18n';
import { launchAgentPanelPrompt } from '../services/agentPanelLauncher';

const ADAPTER_ID = 'vscode-copilot';
const DISPLAY_NAME = 'VS Code Copilot Chat';
Expand All @@ -20,16 +21,17 @@ export const vscodeCopilotAdapter: IAgentExecutorAdapter = {

async fillChat(request: TaskExecuteRequest): Promise<TaskExecuteResult> {
const prompt = request.promptOverride ?? `/opsx:apply ${request.changeName}`;
try {
await vscode.commands.executeCommand('workbench.action.chat.open', {
query: prompt,
isPartialQuery: true,
});
return { success: true, adapterId: ADAPTER_ID, message: 'Chat opened with prompt' };
} catch {
await vscode.env.clipboard.writeText(prompt);
vscode.window.showInformationMessage(t('clipboard.copiedChat'));
return { success: true, adapterId: ADAPTER_ID, message: 'Copied to clipboard (fallback)' };
await vscode.env.clipboard.writeText(prompt);
const panelResult = await launchAgentPanelPrompt({ text: prompt, autoSubmit: false });
if (panelResult.outcome === 'copied') {
vscode.window.showInformationMessage(t('agentLaunch.copied', { command: prompt }));
} else {
vscode.window.showInformationMessage(t('agentLaunch.prefilled', { command: prompt }));
}
return {
success: panelResult.success,
adapterId: ADAPTER_ID,
message: panelResult.layer,
};
},
};
18 changes: 11 additions & 7 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.workspace.onDidChangeWorkspaceFolders(() => {
void dashboardViewProvider.syncOpenSpecRootAvailability();
void dashboardViewProvider.syncProjectContextAfterWorkspaceChange();
}),
);

Expand All @@ -162,7 +163,9 @@ export async function activate(context: vscode.ExtensionContext) {

logger.info('OpenSpec extension activated successfully');
console.log('OpenSpec extension is now active!');
await promptReloadAfterInstallOrUpdate(context);
setTimeout(() => {
void promptReloadAfterInstallOrUpdate(context);
}, 4000);
} catch (error) {
logger.error('Failed to activate OpenSpec extension', error as Error);
vscode.window.showErrorMessage(
Expand All @@ -181,13 +184,14 @@ async function promptReloadAfterInstallOrUpdate(context: vscode.ExtensionContext
await context.globalState.update(key, marker);
logger.info(`OpenSpec installation marker changed: previous=${previous ?? 'none'}, current=${marker}`);
const reload = t('extension.reloadWindow');
const selected = await vscode.window.showInformationMessage(
void vscode.window.showInformationMessage(
t('extension.reloadRecommended'),
reload
);
if (selected === reload) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
}
reload,
).then((selected) => {
if (selected === reload) {
void vscode.commands.executeCommand('workbench.action.reloadWindow');
}
});
}

export function deactivate() {
Expand Down
Loading
Loading