Agent Companion
A VS Code extension that notifies you when Claude Code needs your attention — a permission
request, a question, a finished task, or a failure — and optionally lets you answer with your
voice. Built with an architecture designed to extend to other coding agents (Codex CLI, Gemini
CLI, …) later without touching notifications, voice, or the status bar.
The problem
You kick off a Claude Code task and switch away — another window, another room. Claude then:
- asks for permission to run something,
- asks you a question,
- finishes,
- or fails.
You don't notice until you wander back. Agent Companion watches for those moments and tells you.
How it works
Claude Code supports hooks — shell commands it invokes on events like PermissionRequest,
Notification, Stop, and more, with structured JSON in and out. Agent Companion installs a
small script as a hook command; that script talks to a localhost-only HTTP server the extension
runs while VS Code is open. No terminal-output scraping, no polling, no guessing — see
docs/ARCHITECTURE.md for the full research and design writeup, including
what was verified against Claude Code's docs versus what still needs a live spot-check.
Installation
- Install the extension (from a
.vsix — see Development — or the Marketplace
once published).
- Run Agent Companion: Setup Claude Code Integration from the Command Palette.
- Pick Global (
~/.claude/settings.json, every project) or Project
(.claude/settings.json, this workspace only).
- Review the summary and confirm. Your existing settings file is backed up first
(
settings.json.bak-<timestamp>) and only Agent Companion's own hook entries are added —
nothing else in the file is touched.
Run Agent Companion: Remove Claude Code Integration to reverse this at any time; it only
removes the entries it added.
If your settings.json is malformed or already has a hand-edited entry that looks like ours,
setup refuses to guess — it shows the exact JSON block to paste in yourself, in the
"Agent Companion" output channel.
Features
| Event |
Notification |
Sound |
| Permission requested |
"Claude needs permission — project" + command + risk tier |
configurable |
| Input required |
"Claude needs you — project" |
configurable |
| Completed |
"Claude finished — project" |
configurable |
| Failed |
"Claude failed — project" |
configurable |
- Status bar —
$(robot) Claude: Working / $(bell) Claude: Needs You / etc. Click it for a
list of active sessions and any pending permission requests, with Allow/Deny actions.
- Risk classification — every permission request is tagged LOW / MEDIUM / HIGH / CRITICAL by
deterministic rules (no model calls) before you ever see it. See
src/permissions/RiskClassifier.ts.
- Cross-platform sound — macOS (
afplay), Windows (PowerShell SoundPlayer), Linux
(paplay → aplay → ffplay, whichever is installed), isolated behind one interface. No
sound files are bundled; defaults come from your OS (macOS system sounds, Windows
SystemSounds, or the freedesktop sound theme on Linux if present). Set a custom path in
settings for anything else.
- Voice commands (optional, off by default) — see below.
Voice control — what's real and what isn't
Voice control is implemented end to end except the actual speech recognition engine, and
this README says so plainly rather than pretending otherwise.
Working today:
- A listening window opens only in direct response to a real pending permission request, for a
configurable number of seconds, then closes. The microphone is never engaged on a timer or at
startup.
- A visible
$(mic) Listening… status bar indicator shows exactly when it's active.
VoiceCommandParser recognizes Yes/Allow, No/Deny, Repeat, and "Open Claude" from a transcript
and is fully unit tested.
- The decision-routing logic is real: for LOW/MEDIUM risk, a recognized "yes" resolves the
permission request immediately. For HIGH/CRITICAL risk, voice alone is never sufficient —
it only surfaces a typed VS Code modal confirmation. This is enforced in
PermissionManager
itself, not just in the UI layer that calls it.
Not implemented: the microphone-to-text step. VS Code has no built-in speech-to-text API, and
the realistic options — a native macOS SFSpeechRecognizer addon, or a local model like a small
Whisper build run as a child process — both need a focused engineering spike and a dependency
decision that this build deliberately didn't make for you. Enabling voice control today turns on
the listening window and the mic indicator, honestly, with nothing behind them yet: no command
will be recognized until a real SpeechRecognitionProvider replaces the built-in
NullSpeechRecognitionProvider. The Agent Companion: Enable Voice Control command says this
explicitly when you run it.
Swapping in a real engine is isolated entirely behind
src/voice/SpeechRecognitionProvider.ts — nothing else
in VoiceManager or VoiceCommandParser needs to change.
Privacy & security
- No network calls except loopback IPC between the extension and its own hook script. Nothing is
ever sent to an external server.
- The microphone is never active outside a bounded listening window tied to a real pending
permission request, and voice control defaults to off.
- Raw command text is held in memory only for the lifetime of a pending permission decision —
never written to the log output channel or to disk. Session status (shown in the status bar)
stores state and risk tier, not command text.
- No command is ever auto-approved for HIGH or CRITICAL risk. A casual "yes" is never sufficient
by itself for those tiers — see
PermissionManager.resolve().
- Hook installation never overwrites or removes settings it didn't add, always backs up first,
and is fully reversible.
Settings
All under agentCompanion.*:
| Setting |
Default |
Purpose |
notifications.enabled |
true |
Desktop notifications on/off |
notifications.onlyWhenUnfocused |
false |
Only notify when VS Code isn't focused (see limitation below) |
sounds.enabled |
true |
Sounds on/off |
sounds.volume |
1 |
0.0–1.0, honored where the OS player supports it |
sounds.permissionRequired / inputRequired / completed / failed |
"" |
Custom sound file path (empty = OS default) |
voice.enabled |
false |
See Voice control |
voice.listeningTimeoutSeconds |
12 |
How long the mic window stays open |
risk.warningsEnabled |
true |
Show the risk tier on permission notifications |
hookScope |
global |
Default scope offered by Setup/Remove commands |
Commands
- Agent Companion: Setup Claude Code Integration
- Agent Companion: Remove Claude Code Integration
- Agent Companion: Enable Voice Control / Disable Voice Control
- Agent Companion: Test Notification
- Agent Companion: Test Permission Sound / Test Completion Sound
- Agent Companion: Show Agent Status
Troubleshooting
- No notifications at all — check the "Agent Companion" output channel; the IPC server logs
the port it started on and every hook event it receives. If setup succeeded but nothing
arrives, confirm
claude --version actually reads the settings file you installed into (see
the caveat in docs/ARCHITECTURE.md §1 — this was
written without a local Claude Code CLI to verify against).
- No sound on Linux — install
pulseaudio-utils (for paplay) or ffmpeg (for ffplay),
or set a custom sound path.
- Setup refuses to run — your settings.json is malformed or has a hand-edited entry that
looks like an old install. The output channel shows the exact JSON to paste in by hand.
Architecture overview
See docs/ARCHITECTURE.md for the full writeup. In short:
Claude Code hook → agent-companion-hook.cjs → localhost HTTP → ClaudeAdapter
↓
AgentEventBus (agent-agnostic)
↓ ↓ ↓ ↓
NotificationManager StatusBar VoiceManager …
Nothing outside agents/claude/ knows Claude Code's payload shapes. A future
agents/codex/CodexAdapter.ts implementing the same AgentAdapter interface plugs into the same
bus without changing notifications, voice, status, or UI.
Development
npm install
npm run compile # bundle with esbuild
npm run typecheck
npm run lint
npm test # node:test — pure logic, no VS Code host required
npm run test:e2e # downloads a real VS Code build and activates the extension in it
test:e2e is the real check: it launches an actual Extension Development Host and asserts the
extension activates without throwing and all its commands register — not just that it typechecks.
Press F5 in VS Code (with this folder open) to launch an Extension Development Host interactively
with the extension loaded.
Roadmap
V1 (this release): Claude Code, notifications, custom sounds, permission/completion/failure
alerts, voice command parsing + risk-gated routing, deterministic risk classification.
V2: Codex CLI and Gemini CLI adapters, multiple simultaneous agent sessions, a richer
dashboard, session summaries, stuck-agent detection, and a real speech-recognition backend.
V3: Mobile notifications, remote approval where it can be done safely, cross-device
monitoring.