一键导入
system-context
Architectural map and development guidelines for the Virgo extension. Mandatory reference for all agent modifications.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Architectural map and development guidelines for the Virgo extension. Mandatory reference for all agent modifications.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
# Dev Cycle Protocol: CDP Shell Sovereignty (v2.5.1)
Protocol for high-density, symmetrical shorthand logging in VS Code extensions.
Protocol for local session metadata persistence in the Virgo extension.
Protocol for high-integrity conversational AI injections and sensory parity in the Virgo extension.
Governance protocol and automated scripts for packaging and publishing the Virgo MCP standalone server to the public NPM registry. Enforces safety gates and clean extraction logic.
Unified Release Authority — Single Source of Truth for all release lifecycle actions. Merges release_prestige and version_sentinel into one sovereign skill covering: semantic versioning rules, authorization gates, pipeline execution, artifact verification, git history auditing, SemVer enforcement, and post-release validation.
| name | system_context |
| description | Architectural map and development guidelines for the Virgo extension. Mandatory reference for all agent modifications. |
[!IMPORTANT] This skill is the Source of Truth (SSOT) for the Virgo extension's internal systems. AGENT MANDATE: If you modify, refactor, or introduce a new system that contradicts or extends the content below, you MUST update this skill in the same turn.
[!IMPORTANT] All system skills are bidirectional. Before any significant action, read the relevant skill(s) that govern the affected subsystem. After any significant action, follow the skill_coherence_loop protocol to propose updates. A
skill pass [on <context>]command triggers an explicit coherence audit at any time.
| Skill | Governs | Last Updated |
|---|---|---|
system_context | Architectural map, subsystem ownership, synchronization protocols | 2026-04-23 (v2.5.3) |
loom_meta_governance | Tier-0 Universal Agent OS / Medium Tier Management | 2026-04-12 |
startup_orchestration | Boot sequence, Pulse graph, DPG, Phase 1–3 dependencies | 2026-04-10 |
autoplay_orchestration | Playback pipeline, pre-fetch, neural laws, intent baton | 2026-04-09 |
state_coherence_v4 | State sovereignty, 1-based consensus, split-brain detection | 2026-04-11 (v2.4.2) |
state_auditor | Multi-layer state conflict detection and audit methodology | 2026-04-09 |
virgo_injection_guard | MCP injection, dedup guards, sensory parity, verbatim rules | 2026-04-09 |
session_persistence | extension_state.json, turn indexing, session metadata, snippet paths | 2026-04-10 |
lifecycle_guard | Memory leaks, event listener cleanup, webview disposal | 2026-04-09 |
log_sanitization_v3 | Log density, shorthand formats, symmetrical prefixes | 2026-04-09 |
release_prestige | Extension packaging, installation testing, VSIX integrity | 2026-04-09 |
version_sentinel | Semantic versioning, changelog, release protocol | 2026-04-09 |
startup_orchestration | Boot sequence, Pulse graph, DPG, Phase 1–3, Persistence Yield | 2026-04-10 |
skill_coherence_loop | Skill lifecycle, bidirectional read/write protocol, harvest gate | 2026-04-10 |
dev_cycle | Build, package, VSIX install, process kill/restart for Antigravity editor | 2026-04-10 |
StateStore.ts: The central, reactive EventEmitter storing all document and playback status. Tracks isSelectingVoice for sampling mode.SyncManager.ts: The Observer service. It listens to StateStore, applies 100ms throttling, session parity checks, and visibility-aware flushing to the UI.PlaybackEngine.ts: Orchestrates synthesis. Handles the transition between Local (SAPI/macOS) and Neural (Edge TTS) audio streams. Implements Authoritative Stop via unified AbortController hierarchy.DocController.ts: Document intelligence. Manages chunking, metadata extraction, and position tracking.SettingsManager.ts: The authority on configuration and persistence. Bridges settings.json, globalState, and the Agent's extension_state.json.DashboardRelay.ts: The IPC "Switchboard" for post-message communication with the Webview.McpBridge.ts: SSE + REST HTTP server (port 7413+) that receives say_this_loud tool calls from the AI agent and writes .md snippets to sessions/<id>/. Emits an 'injected' event on each write, which is wired to SpeechProvider.refreshView() for real-time sidebar updates. The legacy McpWatcher.ts (filesystem polling) has been superseded by this event-driven relay.WebviewStore.ts: Global reactive store (Redux-lite). Maintained by UI_SYNC packets from SyncManager. Includes isRefreshing flag.CommandDispatcher.ts: Entry point for all incoming VS Code messages. Dispatches actions to the store or local services.MessageClient.ts: Outbound IPC wrapper. Used to send user commands (Play, Pause, Stop, Refresh) back to VS Code.WebviewAudioEngine.ts: The "Dumb Player" (Stateless worker). Executes single-threaded audio playback using a single HTMLAudioElement for all synthesis modes (Neural/Local).window.__debug (dev builds only): When __BOOTSTRAP_CONFIG__.debugMode === true, the bootstrap function in src/webview/index.ts exposes internal singletons as a global for live CDP inspection: { store, audioEngine, playback, dispatcher }. This global is never present in production builds (gated by debugMode).engineMode property and executed by the WebviewAudioEngine.CacheManager.ts: The Tier-1 (Persistent) storage (SSOT). Uses IndexedDB (ReadAloudAudioCache) with a 100MB cap and 7-day TTL.cachePolicy.ts: The centralized authority for key generation across both environments.Located in extension.ts, the syncSelection() function tracks document focus across tab changes, editor switches, and sidebar interactions. If no active editor exists (e.g., the webview has focus), it falls back to the last active tab or visible editor.
[!IMPORTANT] Law F.1 — Focused/Loaded Duality (BINDING)
These are two legally separate concepts. Any code that conflates them is a violation:
Slot State Field Updated By Update Trigger FOCUSED FILE (passive) focusedDocumentUri/focusedFileNamesyncSelection()→setActiveEditor()Every tab/editor change LOADED FILE (explicit) activeDocumentUri/activeDocumentFileNameloadCurrentDocument()→setActiveDocument()Explicit "Load File" button only Invariants:
syncSelection()MUST NEVER callloadCurrentDocument()orsetActiveDocument().DocControllerchapter loading MUST ONLY be invoked from theLOAD_DOCUMENTIPC path.- Webview UI components showing "Loaded File" MUST bind to
activeDocumentFileNameexclusively.focusedVersionSaltMUST be rendered as HTML (nottextContent) to show the<span class="version-badge">in the FOCUSED FILE slot. UsingtextContentsilently strips the badge.Status: ✅ Issue #26 RESOLVED (2026-04-10)
- DPG persistence yield guard added to
_tryInitialDocumentLoad()inspeechProvider.ts.- Focused file version salt now renders correctly via
innerHTML.Status: ✅ Focused File frozen on tab switch RESOLVED (2026-04-23 / v2.5.3)
SyncManager._calculateStateHash()was missingfocusedFileNameandfocusedVersionSalt.- Tab switches mutated focused file state but produced an identical dedup hash →
_flush()returned early → noUI_SYNCsent → Focused File area never updated.- Fix: both fields added to the hash array in
SyncManager.ts.
The extension uses a FileSystemWatcher on ~/.gemini/antigravity/brain. When a new directory is created, it automatically pivots its internal session context to maintain parity with the active agent session.
Keys: All cache keys are generated via cachePolicy.ts using [Text + VoiceID + Rate + EngineVersion].
Sovereign Manifest: The Webview emits a CacheManifest (Set of IDs) to the Extension Host.
Zero-Target Synthesis: The Extension Host MUST check the CacheManifest before synthesis. If a key is present, synthesis is skipped, and the Webview is instructed to play from local disk.
Tiering:
ObjectURL window (Active + Next 2).Lifecycle: Prefetch tasks are aborted immediately on IntentId increments (e.g., user skips forward).
Known Event Mirroring (Not a Bug): CACHE_STATS_UPDATE is emitted by two separate paths
for the same cache event: (1) the Extension bridge on write, and (2) the Webview CacheManager
on read/hydration acknowledgment. This produces two [HOST->WEBVIEW] [CACHE_STATS_UPDATE]
log lines per cache operation. Do NOT suppress either side without verifying which path owns
cache authority for the current operation (synthesis write vs. disk hydration).
playbackIntentId. Components MUST immediately eject tasks that do not match the current global intent. Intent IDs are initialized to 1 as the authoritative baseline across all environments (Extension Host, Webview, PlaybackEngine) to ensure total synchronization upon first handshake.WebviewStore is the exclusive owner of active intent and synchronization state in the UI. PlaybackController MUST NOT maintain private copies of isPlaying, isAwaitingSync, or playbackIntentId. All logic must read from and write to the store to prevent "Split-Brain" behavior.WebviewAudioEngine manages an internal AbortController linked to the active intent. New intents call abort() on the previous controller, which immediately releases locks and cancels async playback/fetch tasks.stop() command is universal. It triggers a cascade of aborts across all active segments, pre-fetch batches, and the primary synthesis lock in the WebviewAudioEngine.isHandshakeComplete flag is true in the store. This ensures the intent baseline has synced.McpBridge uses an internal _isHandshaking lock. Parallel probes arriving during an active handshake are absorbed and return 204 No Content. The lock is released upon successful connection or a 10s fallback timeout. The multi-instance Hub model (_activeServers Set) is present but effectively single-instance in practice — Gemini opens one SSE connection at a time.WebviewStore.patchState method emits a specific ASCII marker [STORE-SYNC-COMPLETE] after every non-identical state update. This signal is the authoritative "Done" marker for automation scripts to stop polling and verify state.availableVoices is excluded from the high-frequency DEFAULT_SYNC_PACKET. It is updated exclusively via dedicated VOICES commands during initialization or explicit voice scans.playbackIntentId is updated in the store (Date.now()).isAwaitingSync is set to true in the store.UI_SYNC packets with lower intentIds are ignored to prevent "UI Flickering".McpBridge enforces a 200ms eviction delay before force-purging stale sessions.batchId tracks manual vs auto-advance sequences.
batchId.batchId to maintain sequence continuity.batchId is 1. Synthesis requests with 0-value IDs auto-adopt current authoritative IDs.[!IMPORTANT] MP-001 Canonical Path Change: All session data now lives under
virgo/sessions/<sessionId>/. The legacy flatvirgo/<sessionId>/layout is decommissioned.
~/.gemini/antigravity/virgo/ is the extension's data root.virgo/sessions/<sessionId>/):
.md files)extension_state.json (turn index + session title)virgo/ root, not session-scoped):
active_servers.json — MCP bridge global registryprotocols/ — aspirational boot protocol files (see injection guard skill)tempmediaStorage/ — ephemeral audio scratch space~/.gemini/antigravity/brain/<sessionId>/):
task.md, implementation_plan.md, loom.json, etc. — agent governance artifacts_getSnippetHistory() scans sessions/ only — strict .md/.markdown filterextension_state.json is excluded from sidebar resultstask.md) is the active VS Code editor, the system MUST allow loading and synthesis of it — focus overrides discovery filtering.The extension emphasizes high-quality Neural voices but maintains Local voices as a reliable baseline.
VOICE_SCAN protocol. This is used to resolve transient Edge TTS connectivity issues or to force a fresh scan after network recovery.OutgoingAction.REFRESH_VOICES, which calls VoiceManager.scanAndSync() in the Extension Host.The Sovereign Cache Key (_activePlayKey) in PlaybackController is the ground-truth key for HEAD_MATCH resolution. It is set by the extension via setActivePlayKey(cacheKey) called from the CommandDispatcher.
[!IMPORTANT] IPC Ordering Invariant:
DATA_PUSHmessages can arrive in the webview BEFORE theplayAudiomessage, despite being emitted at the same time in the Bridge. This is a structural IPC ordering race. To compensate:
setActivePlayKey()MUST be called in theSYNTHESIS_STARTINGhandler (earliest possible signal), not only in theplayAudiohandler.DATA_PUSHhandlers MUST NOT assume the sovereign key is set when they arrive.FETCH_AUDIOrecovery (triggered on HEAD_MATCH fail) is a symptom of this race — suppressing it without fixing the ordering is incorrect.
_activePlayKey Lifecycle:
| Event | Action |
|---|---|
SYNTHESIS_STARTING received | setActivePlayKey(cacheKey) — EARLY set |
playAudio received | setActivePlayKey(cacheKey) — confirmation/override |
stop() / reset() called | _activePlayKey = null — cleared |
| New intent starts | Previous key is overwritten atomically |
Rate Sovereignty: The key includes the baked rate (e.g., _1.00_). Prefetch segments synthesized at the display rate (e.g., _1.40_) will never match the sovereign key for a direct-play session baked at 1.00. This is expected — prefetch segments are stored for cache hits only, not for immediate HEAD play.
Confirmed race conditions and invariants discovered during v2.5.x debugging:
{ once: true } MandateThe canplay event on the <audio> element MUST be registered with { once: true }. Without it, reassigning audio.src for a new segment re-fires canplay, causing:
Sovereign Event: PLAYING emissions per sentenceengineStatus: playing signalsThe Bridge's playAudio emission MUST be gated behind an explicit user-initiated play flag (_isUserInitiatedPlay). The synthesisReady callback path MUST NOT emit playAudio without this gate. Loading a file (LOAD_DOCUMENT) MUST NOT trigger playback.
When a new play intent starts, all in-flight prefetch tasks whose bakedRate diverges from the current synthesis rate MUST be aborted. Orphaned prefetch DATA_PUSH messages with mismatched rates cause ingestion failures and FETCH_AUDIO storms.
When simulating command palette interaction via CDP:
type to the VS Code command palette MUST be prefixed with >.> prefix forces it into "Command Mode".exec command in cdp-controller.mjs.SpeechProvider or DocController if it needs to be reflected in the UI.StateStore interface and StateStore.init().DashboardRelay.sync() method to include the new data in the standard sync packet.package.json (under contributes.configuration). Update SettingsManager.ts to map the configuration value into the StateStore.MessageClient.postMessage({ command: 'myCommand', ... }).CommandDispatcher on the backend (usually delegated to SpeechProvider.onDidReceiveMessage) to handle the incoming request.[!WARNING] These are structural jsdom/vitest limitations — not production bugs. They affect test file setup only.
HTMLAudioElement Stub (canplay Contract)WebviewAudioEngine.playBlob() sets audio.src, calls audio.load(), then awaits either canplay, ended, or error events before calling audio.play(). In jsdom, audio.load() is a no-op stub — media events never fire automatically.
Any test that calls playBlob() directly MUST mock load() to dispatch canplay synchronously:
// Instance-level (RaceCondition.test.ts — spy on specific engine.audioElement):
vi.spyOn(audio, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
// Prototype-level (WebviewAudioEngine.test.ts — affects all HTMLAudioElement instances):
vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
Without this, the inner Promise in playBlob() hangs permanently, causing a 5000ms watchdog timeout and a Vitest global timeout failure.
Known affected tests (pre-fix):
tests/webview/core/RaceCondition.test.ts:47 — "SHOULD allow audio packets that match the current intent"tests/webview/core/WebviewAudioEngine.test.ts:49 — "should acquire lock for playBlob and release it on completion"vscode.window.tabGroups Mock RequirementAny test file that triggers SpeechProvider._sendInitialState() (via resolveWebviewView() + a ready message) must include tabGroups.activeTabGroup.activeTab in the vscode.window mock — otherwise a TypeError crashes the test:
window: {
createStatusBarItem: vi.fn(...),
activeTextEditor: undefined,
tabGroups: { activeTabGroup: { activeTab: undefined } }
}
Known affected test files: speechProvider.voices.test.ts, speechProvider.sync.test.ts.
MessageClient.postMessage({ command: 'play' }).SpeechProvider.play() -> PlaybackEngine.start().
3.5. SYNTHESIS_STARTING emitted → CommandDispatcher calls setActivePlayKey(cacheKey) immediately (sovereign key early-set, before DATA_PUSH arrives).StateStore emits change.SyncManager throttles and sends UI_SYNC message to Webview via DashboardRelay.WebviewStore updates and React components re-render.[!WARNING]
DATA_PUSHandplayAudioare emitted simultaneously by the Bridge butDATA_PUSHarrives in the webview first. The sovereign key set in step 3.5 (viaSYNTHESIS_STARTING) is what prevents the HEAD_MATCH failure inDATA_PUSHingestion. Removing step 3.5 reintroduces the silent playback regression (RC-1).
graph TD
subgraph "Extension Host (Synthesis Authority)"
A[DocController: Chunking] --> B[CacheCheck: Manifest Look-up]
B -- Miss --> E[EdgeTTS: Synthesis]
B -- Hit --> F[Bridge: Signal Cache-Hit]
end
subgraph "IPC Bridge"
E --> G[DATA_PUSH / SYNTH_READY]
F --> H[PLAY_FROM_CACHE]
end
subgraph "Webview (Playback & Persistence SSOT)"
G --> I[WebviewAudioEngine: Ingest]
H --> J[CacheManager: Load Blob]
I --> K[Tier-2: Memory Window Objects]
J --> K
K --> L[WebviewAudioEngine: Play]
I -.-> M[Tier-1: IndexedDB Sync]
M -- "Periodic Update" --> N[CacheManifest]
N -- "Sync Out" --> B
end
[!NOTE] This section documents the live inspection and mutation toolchain available to the agent during development. All capabilities below are dev-only and are never active in production.
cdp-controller.mjs Persistent REPL (npm run cdp:shell)A long-running Node.js process that maintains a single CDP connection to the running Antigravity editor (localhost:9222). It provides a REPL with the following command registry:
| Command | Action |
|---|---|
launch | Triggers F5 in the main editor, waits for VOICE_SCAN activation signal |
exec <cmd> | Dispatches a VS Code command palette action (prefixed with >) to the Dev Host workbench |
frames | Lists all CDP frames in the dev host (diagnostic — identifies webview targets) |
targets / pages | Lists all active CDP targets and pages across the browser instance |
eval [target] <expr> | Executes JS in the webview. [target] can be an index (eval 1 ...) or a URL fragment (eval @fragment ...) |
close | 3-tier graceful shutdown: Polite → window.close() → Surgical PID kill |
exit | Terminates the REPL process itself |
VS Code Two-Frame Architecture: VS Code webviews are sandboxed iframes inside the workbench renderer. They never appear as separate CDP top-level targets. The controller uses Parallel Sovereign Probing (scanning Store, Config, and URL markers) to traverse child frames of the dev host page, preferring the vscode-webview://.../fake.html frame.
window.__debug — Live Singleton InspectorExposed in src/webview/index.ts inside the bootstrap() function, gated by:
if (__BOOTSTRAP_CONFIG__.debugMode) {
(window as any).__debug = { store, audioEngine, playback, dispatcher };
}
Verified live snapshot (2026-04-10):
{ "native": true, "extensionVersion": "2.2.2", "debugMode": true, "logLevel": 2 }
The store is a custom class. Key methods available via window.__debug.store:
| Method | Purpose |
|---|---|
getState() | Returns full state snapshot |
patchState(patch) | Shallow-merges patch into state. Mutations are immediate and confirmed. |
updateState(patch) | Deep state update (triggers listeners) |
subscribe(fn) | Registers a state change listener |
resetPlaybackIntent() | Atomically resets intent + isAwaitingSync |
setIntentIds(ids) | Updates playbackIntentId + batchIntentId |
[!WARNING]
store.dispatch()does NOT exist. This is a custom store, not Redux. UsepatchState()for direct mutations in debug sessions.
Critical state fields for playback diagnosis:
rate, volume, engineMode, playbackIntent, isPlaying, isPaused,
playbackStalled, isBuffering, neuralBuffer, lastLoadType,
playbackIntentId, batchIntentId, activeQueue
Key methods available via window.__debug.audioEngine:
| Method | Behavior |
|---|---|
setVolume(0–100) | Normalizes to audioElement.volume (divides by 100). ✅ Confirmed working. |
setRate(n) | Sets playback rate. Does NOT write back to store — rate change is engine-local only. |
pause() / resume() / stop() | Direct playback control |
ingestData(data) | Feeds a synthesized audio blob into the engine buffer |
scanVoices() | Re-queries available TTS voices |
purgeMemory() | Wipes in-memory audio objects |
wipeCache() | Clears IndexedDB cache |
window.__debug.dispatcher.dispatch({ command, ... }) handles inbound message commands from the VS Code extension side. It is not a client-side mutation API — calling dispatch() from the webview console will return {} without state changes.
| Observation | Verdict |
|---|---|
isBuffering: true while playbackIntent: "STOPPED" | ✅ Remediated — Filtered via Segmented Sovereignty |
audioEngine.setRate() adjustments | ✅ Seamless — Applied via Neural Rate Guard ($target / baked$) |
audioElement.volume = setVolume(n) / 100 | ✅ Correct normalization |
store.patchState() mutations are instant and reflected in next getState() | ✅ Confirmed |
Dev session baseline: rate: 7.2, volume: 84, engineMode: "neural" | 📊 Reference snapshot |
v2.5.0 RC-1: DATA_PUSH arrives before playAudio → HEAD_MATCH fails → FETCH_AUDIO storm | ✅ Resolved (v2.5.2) — SYNTHESIS_STARTING early key-set + { once: true } canplay fix |
| v2.5.0 RC-2: Prefetch at rate 1.40 collides with play at rate 1.00 key space | ✅ Resolved (v2.5.2) — Prefetch rate fence + text-payload decoupling in REQUEST_SYNTHESIS |
v2.5.0 RC-3: canplay fires 3× per sentence → 3× Sovereign Event: PLAYING signals | ✅ Resolved (v2.5.2) — { once: true } enforced in WebviewAudioEngine |
| v2.5.0 UM-1: Load-file triggers autoplay without user intent gate | ✅ Resolved (v2.5.2) — _isUserInitiatedPlay gate active in audioBridge |
MP-001: MCP injections wrote to brain/<id>/, sidebar scanned sessions/<id>/ — snippets disappeared | ✅ Resolved (v2.5.3) — Unified canonical root: virgo/sessions/<id>/ |
| Focused File frozen on tab switch | ✅ Resolved (v2.5.3) — focusedFileName + focusedVersionSalt added to SyncManager hash |
To maintain high-integrity state and prevent "Split-Brain" bugs, agents MUST adhere to these three pillars:
STRICT PROHIBITION: Components (Controllers, Engines, UI Classes) are FORBIDDEN from maintaining local private properties that duplicate or shadow data stored in the WebviewStore or StateStore.
store.getState().isPlaying inside your method.private isPlaying: boolean in your class and trying to keep it synced.state sub-object or vice-versa. If a property exists in both, one MUST be the authority.Never scatter Date.now() or manual ID increments across components.
store.resetPlaybackIntent()). This ensures that the intentId update and the isAwaitingSync lock happen in a single atomic state transition.undefined properties (CundefinedSundefined logging) from infecting the state and ensure valid defaults.MessageClient implements a Sanitizer that validates UI_SYNC packets before they reach the Store.playbackIntentId, batchIntentId) MUST initialize to 1.brain, antigravity) are filtered ONLY from the Snippet Discovery Sidebar to maintain history hygiene. They are FULLY permitted for loading and synthesis if they are the actively focused editor in VS Code.UI_SYNC, the Extension's nested state object is the definitive authority. The WebviewStore's patchState must perform a strictly mirrors the sub-object onto flat properties to prevent local optimistic UI from permanently diverging from the Extension's reality.Before adding any new property, method, or IPC command:
grep_search or search_code for the functional concept (e.g., "sync", "lock", "intent").If a new requirement "looks like" something the Store already handles (e.g., "I need a way to wait for a response"), use the existing mechanism (e.g., isAwaitingSync) instead of creating a new one (e.g., this.isWaitingForDoc).
To ensure playback reliability despite the inherent instability of internet-based Neural TTS, the system adheres to the Autoradiant philosophy: "Neural by choice, Local by necessity, Healing by design."
lastNeuralSuccessTime is checked. Fallback to Local only occurs if:
Date.now() - lastNeuralSuccessTime > 120,000ms.readyState or TypeError in the TTS client are treated as Authoritative Corruptions.MsEdgeTTS instance MUST be immediately destroyed and re-initialized. Retries MUST NOT proceed using a corrupted client.synthesisLock with intent-based early ejection.for loop (3 attempts) with exponential backoff.DEGRADED, signal the Bridge to pivot.show virgo (dispatches virgo.show-dashboard) to ensure the panel is active before performing eval or state audits.find virgo to locate the fake.html frame within the vscode-webview:// sandbox.cdp-controller.mjs uses .cdp_shell.lock (in project root). ONLY ONE instance can be active.window.__debug.store.__BOOTSTRAP_CONFIG__.vscode-webview:// fragments.exit or quit command via send_command_input to ensure the lock file is removed.list_dir.429 (Too Many Requests) or target detachment..cdp_shell.lock and kill the Node process.