Skip to content
Open
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
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
- Run every GitHub CLI (`gh`) command outside the sandbox. The sandbox cannot
access the host keyring and can incorrectly report that authentication is
invalid.
- Keep user-facing documentation aligned with behavior changes. Update the
Unreleased section of `CHANGELOG.md` for user-visible changes, update
`README.md` when commands, settings, requirements, or workflows change, and
extend `docs/TESTPLAN.txt` when new behavior needs interactive release
verification. Record changes to durable design decisions in
`docs/DECISIONS.md`.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 1.0.0 — Unreleased

- Add a configurable permission mode for Claude sessions launched by Lookout,
defaulting to automatic permission classification while preserving explicit
launch-command overrides and an option to inherit Claude's resolved default.
- Clear stale attention indicators as soon as updates are read, prefer unread
activity during navigation, synchronize the newest Claude usage observation
across windows, and discard quota windows after their reset time.
Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ Authoritative sources only, never estimated: Codex via `codex app-server` JSON-R
- `docs/ROADMAP.md`, `docs/RESEARCH.md` — roadmap and research record
- `docs/plans/`, `docs/research/` — Workshop-convention artifacts
- `docs/sessions/` — dated session checkpoints; the interactive smoke matrix (`docs/sessions/2026-07-10-smoke.md`) is the gating milestone before Marketplace release

Keep documentation in the same change as the behavior it describes. Add
user-visible changes to the Unreleased section of `CHANGELOG.md`; update
`README.md` for changed commands, settings, requirements, or workflows; extend
`docs/TESTPLAN.txt` when interactive release verification is needed; and update
`docs/DECISIONS.md` whenever a durable design decision changes.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ All shortcuts can be changed in Keyboard Shortcuts.
The most common settings are:

- `lookout.codex.command` and `lookout.claude.command` — provider launch commands;
- `lookout.claude.permissionMode` — permission mode for Lookout-launched Claude
sessions (`auto` by default); an explicit command flag takes precedence;
- `lookout.codex.enabled` and `lookout.claude.enabled` — entries shown in the
new-agent picker;
- `lookout.codex.lifecycleIntegration` and
Expand Down
7 changes: 7 additions & 0 deletions docs/TESTPLAN.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ B. LAUNCH AND LAYOUT (~10 min)
leaving the editor area free for code and review. The Agents toolbar has
no permanent isolated-worktree action, and Launch from Template is absent
until at least one template is configured.
5a. Claude permission mode: with lookout.claude.permissionMode at its default,
launch Claude and confirm /permissions reports auto mode. Set the setting
to manual, launch a new Claude session, and confirm manual mode. Restore
the setting to auto, set lookout.claude.command to
"claude --permission-mode plan", and confirm the explicit plan mode wins.
Finally set permissionMode to inherit, restore the command to "claude",
launch again, and confirm Lookout does not force a mode. Restore defaults.
6. Terminal tab names read "Lookout: <label> [<id>]"; tree rows show stable
directory - branch - state labels. Drag the Current Workspace rows into a
new order and reload;
Expand Down
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,27 @@
"default": "claude",
"description": "Command used to launch a Claude Code session."
},
"lookout.claude.permissionMode": {
"type": "string",
"enum": [
"auto",
"acceptEdits",
"manual",
"dontAsk",
"plan",
"inherit"
],
"enumDescriptions": [
"Let Claude automatically classify whether each action needs approval.",
"Automatically approve file edits while prompting for other protected actions.",
"Ask before protected actions.",
"Deny actions that require permission instead of asking.",
"Restrict Claude to planning without making changes.",
"Do not pass a permission mode; use Claude's resolved default."
],
"default": "auto",
"description": "Permission mode for Claude Code sessions launched by Lookout. An explicit --permission-mode in the launch command takes precedence."
},
"lookout.claude.enabled": {
"type": "boolean",
"default": true,
Expand Down
22 changes: 22 additions & 0 deletions src/agentCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export type LaunchShell =
| 'argv'
| 'unknown';

export type ClaudePermissionMode =
| 'inherit'
| 'auto'
| 'acceptEdits'
| 'manual'
| 'dontAsk'
| 'plan';

export function classifyShell(
shellPath: string | undefined,
_platform: NodeJS.Platform = process.platform
Expand Down Expand Up @@ -191,6 +199,20 @@ export function isDirectAgentCommand(
);
}

export function withClaudePermissionMode(
command: string,
mode: ClaudePermissionMode
): string {
if (
mode === 'inherit' ||
!isDirectAgentCommand(command, 'claude') ||
/(^|\s)--permission-mode(?:\s|=)/.test(command)
) {
return command;
}
return `${command} --permission-mode ${mode}`;
}

export function shellQuote(value: string, shell: LaunchShell): string {
switch (shell) {
case 'argv':
Expand Down
19 changes: 14 additions & 5 deletions src/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import {
isDirectAgentCommand,
PROVIDER_ACTIVITY_TOOL_MATCHER,
shellQuote,
withClaudePermissionMode,
withCodexLifecycleIntegration,
withCodexTokenBudget,
type ClaudePermissionMode,
type LaunchShell
} from './agentCommand';
import { captureGitBaseline, listUncommittedChanges } from './gitReview';
Expand Down Expand Up @@ -1335,6 +1337,13 @@ export class SessionManager implements vscode.Disposable {
tokenBudget.limitTokens,
launchShell
);
} else if (request.kind === 'claude') {
command = withClaudePermissionMode(
command,
vscode.workspace
.getConfiguration('lookout.claude')
.get<ClaudePermissionMode>('permissionMode', 'auto')
);
}
const notifyHelperPath = path.join(
this.context.extensionPath,
Expand Down Expand Up @@ -1366,8 +1375,8 @@ export class SessionManager implements vscode.Disposable {
}
if (
request.kind !== 'claude' ||
/(^|\s)--settings(?:\s|=)/.test(request.command) ||
!isDirectClaudeCommand(request.command)
/(^|\s)--settings(?:\s|=)/.test(command) ||
!isDirectClaudeCommand(command)
) {
return {
command,
Expand All @@ -1385,11 +1394,11 @@ export class SessionManager implements vscode.Disposable {
.getConfiguration('lookout.claude')
.get('lifecycleIntegration', true);
if (!statusLineIntegration && !lifecycleIntegration) {
return { command: request.command, integrationsSkipped: false };
return { command, integrationsSkipped: false };
}
if (launchShell === 'unknown') {
// No known-safe way to quote the settings path for this shell.
return { command: request.command, integrationsSkipped: true };
return { command, integrationsSkipped: true };
}
await vscode.workspace.fs.createDirectory(this.context.globalStorageUri);
const helperPath = path.join(
Expand Down Expand Up @@ -1476,7 +1485,7 @@ export class SessionManager implements vscode.Disposable {
Buffer.from(JSON.stringify(settings), 'utf8')
);
return {
command: `${request.command} --settings ${shellQuote(
command: `${command} --settings ${shellQuote(
settingsUri.fsPath,
launchShell
)}`,
Expand Down
17 changes: 17 additions & 0 deletions test/agentCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
classifyShell,
isDirectAgentCommand,
shellQuote,
withClaudePermissionMode,
withCodexLifecycleIntegration,
withCodexTokenBudget
} from '../src/agentCommand';
Expand Down Expand Up @@ -104,6 +105,22 @@ test('recognizes direct provider commands without accepting shell expressions',
assert.equal(shellQuote("it's ready", 'posix'), "'it'\\''s ready'");
});

test('starts direct Claude sessions in the configured permission mode', () => {
assert.equal(
withClaudePermissionMode('claude --model opus', 'auto'),
'claude --model opus --permission-mode auto'
);
assert.equal(
withClaudePermissionMode('claude --permission-mode manual', 'auto'),
'claude --permission-mode manual'
);
assert.equal(withClaudePermissionMode('claude', 'inherit'), 'claude');
assert.equal(
withClaudePermissionMode('wrapper claude', 'auto'),
'wrapper claude'
);
});

test('classifies terminal shells from their executable paths', () => {
assert.equal(classifyShell('/bin/bash', 'linux'), 'posix');
assert.equal(classifyShell('/usr/local/bin/fish', 'darwin'), 'posix');
Expand Down