Skip to content

Commit 55ebbea

Browse files
committed
feat(ai-chat): expand safe-bash list, scoped sidebar dark overrides, plan overlay css
* Safe-bash classifier in claude-code-agent.js now splits commands on `;`, `&&`, `||` and accepts the chain if every segment matches a safe pattern, so `git status && git log -5` and `sleep 1; echo done` no longer prompt. `sleep` (numeric durations) joins the allowlist. Process substitution (`$(...)`, backticks), redirection (`<`, `>`) and pipes (`|`) still fall through to a user prompt — chained destructive ops can't piggy-back on a safe prefix. * New scoped overrides at the bottom of Extn-AIChatPanel.less neutralise Bootstrap's .btn / .btn-primary / .btn-secondary skins and add dark-theme defaults for inputs/textareas/selects inside .ai-chat-panel, so the always-dark sidebar renders identically in light and dark editor themes. * Plan-card maximize: .ai-plan-maximize-btn (muted button flush right of the header) plus .ai-plan-fullscreen-overlay (fixed, z-index 10001, dark backdrop, 960px card with the same plan chrome). Strings AI_CHAT_PLAN_MAXIMIZE and AI_CHAT_PLAN_CLOSE_FULLSCREEN. * First-time Full Auto warning strings: AI_CHAT_FULL_AUTO_WARNING_TITLE / _BODY / _PROCEED.
1 parent e9e03b8 commit 55ebbea

3 files changed

Lines changed: 172 additions & 10 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ function _isToolResponseError(toolResponse) {
117117
// Bash commands the agent can run without prompting the user in Edit
118118
// Mode. Mirrors the CLI's default "permissions.allow" set
119119
// (cli.js:2925) plus a small handful of universally read-only shell
120-
// utilities. Shell-composition characters (`;`, `&&`, `||`, backticks,
121-
// pipes, redirection, command substitution) trip the safety belt
122-
// below — without that check `git status; rm -rf /` would slip through
123-
// since the prefix matches.
120+
// utilities. The safety belt in _isSafeReadOnlyBash splits on
121+
// `;` / `&&` / `||` and checks every segment, so chaining safe
122+
// commands (e.g. `git status && git log`, `sleep 1; echo done`)
123+
// works while `git status; rm -rf /` correctly falls through.
124124
const _SAFE_BASH_PATTERNS = [
125125
// git read-only
126126
/^git\s+status(\s|$)/,
@@ -143,6 +143,9 @@ const _SAFE_BASH_PATTERNS = [
143143
/^wc(\s|$)/,
144144
/^file\s/,
145145
/^stat\s/,
146+
// numeric-only sleep — no `sleep $(...)` since process substitution
147+
// is rejected separately, but be explicit so `sleep $VAR` also fails.
148+
/^sleep\s+\d+(\.\d+)?$/,
146149
// version probes
147150
/^node\s+--version$/,
148151
/^npm\s+--version$/,
@@ -153,12 +156,19 @@ const _SAFE_BASH_PATTERNS = [
153156
function _isSafeReadOnlyBash(rawCmd) {
154157
const cmd = (rawCmd || "").trim();
155158
if (!cmd) { return false; }
156-
// Reject anything that could chain a destructive op via shell
157-
// composition: `;` `&&` `||` `|` backticks `$(...)` `<` `>`.
158-
// The CLI's parser handles these; we keep matching simple by
159-
// refusing to bypass the prompt if any of them are present.
160-
if (/[;&|`$()<>]/.test(cmd)) { return false; }
161-
return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(cmd); });
159+
// Reject command/process substitution, redirection, and pipes —
160+
// these can hide arbitrary commands or send output to dangerous
161+
// places. Backticks, `$(...)`, `<`, `>`, `|`. Plain `$VAR` is
162+
// allowed (substitution-without-command).
163+
if (/[`<>|]|\$\(/.test(cmd)) { return false; }
164+
// Split on `;`, `&&`, `||` and verify EVERY segment matches a safe
165+
// pattern. Quotes around delimiters are not handled — a command
166+
// like `echo "a; b"` will split mid-string and fail safe-check
167+
// (which is fine: false negatives are OK, false positives are not).
168+
const segments = cmd.split(/\s*(?:;|&&|\|\|)\s*/).filter(Boolean);
169+
return segments.every(function (seg) {
170+
return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(seg); });
171+
});
162172
}
163173

164174
/**

src/nls/root/strings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,6 +2223,9 @@ define({
22232223
"AI_CHAT_FILE_NOT_FOUND_MSG": "Could not open <span class=\"dialog-filename\">{0}</span>. The file may have been moved or deleted.",
22242224
"AI_CHAT_UNDO_RESTORE_WARNING_TITLE": "AI Undo & Restore",
22252225
"AI_CHAT_UNDO_RESTORE_WARNING_BODY": "This will only undo changes made by the AI. Changes made outside the AI won’t be restored and may be lost. For full version history, use version control like Git.",
2226+
"AI_CHAT_FULL_AUTO_WARNING_TITLE": "Switch to Full Auto Mode?",
2227+
"AI_CHAT_FULL_AUTO_WARNING_BODY": "Full Auto mode lets the AI run any tool — Bash commands, file edits, file deletions, web fetches — without asking you first.<br><br>This is convenient for trusted scratch projects, but can be risky: a misjudged step could overwrite or delete files, run a destructive shell command, or push unintended changes. Use version control (Git) so you can recover if something goes wrong.<br><br>Only enable Full Auto in projects you trust. You can switch back to Edit Mode at any time using <kbd>Shift+Tab</kbd> or by clicking the mode bar.",
2228+
"AI_CHAT_FULL_AUTO_WARNING_PROCEED": "Enable Full Auto",
22262229
"AI_CHAT_SHOW_DIFF": "Show diff",
22272230
"AI_CHAT_HIDE_DIFF": "Hide diff",
22282231
"AI_CHAT_DIFF_MORE_TITLE": "Diff options",
@@ -2249,6 +2252,8 @@ define({
22492252
"AI_CHAT_TOOL_TASK_NAME": "Subagent: {0}",
22502253
"AI_CHAT_TOOL_PLANNING": "Planning",
22512254
"AI_CHAT_PLAN_TITLE": "Proposed Plan",
2255+
"AI_CHAT_PLAN_MAXIMIZE": "Open plan in full screen",
2256+
"AI_CHAT_PLAN_CLOSE_FULLSCREEN": "Close (Esc)",
22522257
"AI_CHAT_PLAN_APPROVE": "Approve",
22532258
"AI_CHAT_PLAN_REVISE": "Revise",
22542259
"AI_CHAT_PLAN_FEEDBACK_PLACEHOLDER": "What would you like changed?",

src/styles/Extn-AIChatPanel.less

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,94 @@
13651365
font-weight: 600;
13661366
}
13671367

1368+
// Maximize button on the plan header — sits flush right via auto
1369+
// margin so the title and icon stay left-aligned. Same muted look as
1370+
// the diff toggle / more menu so it reads as a quiet affordance.
1371+
.ai-plan-maximize-btn {
1372+
margin-left: auto;
1373+
background: none;
1374+
border: none;
1375+
color: #6b9eff;
1376+
font-size: @ai-text-secondary;
1377+
line-height: 1;
1378+
padding: 2px 6px;
1379+
border-radius: 4px;
1380+
cursor: pointer;
1381+
opacity: 0.65;
1382+
transition: opacity 0.15s ease, background-color 0.15s ease;
1383+
1384+
&:hover {
1385+
opacity: 1;
1386+
background-color: rgba(107, 158, 255, 0.15);
1387+
}
1388+
}
1389+
1390+
// Fullscreen plan overlay — covers the whole window (over the editor,
1391+
// not just the AI panel). z-index above modal dialogs so a long plan
1392+
// is readable in detail without horizontal cramping.
1393+
.ai-plan-fullscreen-overlay {
1394+
position: fixed;
1395+
top: 0;
1396+
left: 0;
1397+
right: 0;
1398+
bottom: 0;
1399+
z-index: 10001;
1400+
background: rgba(0, 0, 0, 0.72);
1401+
display: flex;
1402+
align-items: center;
1403+
justify-content: center;
1404+
padding: 24px;
1405+
-webkit-user-select: text;
1406+
-moz-user-select: text;
1407+
user-select: text;
1408+
1409+
.ai-plan-fullscreen-card {
1410+
// Same chrome as the inline plan card so the maximized view is
1411+
// a faithful magnification, not a different surface.
1412+
width: min(960px, 96vw);
1413+
max-height: 92vh;
1414+
display: flex;
1415+
flex-direction: column;
1416+
background-color: rgba(40, 44, 52, 0.98);
1417+
border: 1px solid rgba(107, 158, 255, 0.35);
1418+
border-radius: 8px;
1419+
overflow: hidden;
1420+
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
1421+
1422+
.ai-plan-header {
1423+
flex-shrink: 0;
1424+
}
1425+
1426+
.ai-plan-body {
1427+
// Override the inline card's max-height — in fullscreen we
1428+
// want the body to consume the remaining card height.
1429+
max-height: none;
1430+
flex: 1 1 auto;
1431+
overflow-y: auto;
1432+
padding: 22px 28px;
1433+
}
1434+
}
1435+
1436+
.ai-plan-fullscreen-close {
1437+
margin-left: auto;
1438+
background: none;
1439+
border: none;
1440+
color: #6b9eff;
1441+
font-size: @ai-text-body;
1442+
line-height: 1;
1443+
padding: 2px 8px;
1444+
border-radius: 4px;
1445+
cursor: pointer;
1446+
opacity: 0.7;
1447+
transition: opacity 0.15s ease, background-color 0.15s ease;
1448+
1449+
&:hover {
1450+
opacity: 1;
1451+
background-color: rgba(107, 158, 255, 0.15);
1452+
}
1453+
}
1454+
}
1455+
13681456
.ai-plan-body {
13691457
padding: 14px 16px;
13701458
font-size: @ai-text-body;
@@ -2745,3 +2833,62 @@
27452833
text-align: center;
27462834
}
27472835

2836+
/* ── Sidebar-is-always-dark overrides ───────────────────────────────── */
2837+
/* The AI panel renders against a fixed dark sidebar background. Both
2838+
Bootstrap's stock .btn skins AND Phoenix's light-theme overrides of
2839+
them paint badly here in light editor themes. Neutralise the skins
2840+
inside the panel so the AI panel's own per-element rules
2841+
(.ai-plan-approve-btn, .ai-plan-revise-btn, .ai-plan-feedback-send,
2842+
etc.) own the look — and that look is identical in both themes. */
2843+
.ai-chat-panel {
2844+
.btn,
2845+
.btn:hover,
2846+
.btn:focus,
2847+
.btn:active,
2848+
.btn-primary,
2849+
.btn-primary:hover,
2850+
.btn-primary:focus,
2851+
.btn-primary:active,
2852+
.btn-secondary,
2853+
.btn-secondary:hover,
2854+
.btn-secondary:focus,
2855+
.btn-secondary:active {
2856+
background: none;
2857+
background-color: transparent;
2858+
background-image: none;
2859+
border-color: transparent;
2860+
box-shadow: none;
2861+
text-shadow: none;
2862+
color: inherit;
2863+
}
2864+
2865+
/* Inputs and textareas — same story. The chat textarea has its own
2866+
per-class style; this is the safety net for any other input we
2867+
drop in (plan feedback textarea today, future ones tomorrow). */
2868+
input[type="text"],
2869+
input[type="search"],
2870+
input[type="email"],
2871+
input[type="password"],
2872+
input[type="number"],
2873+
select,
2874+
textarea {
2875+
background-color: rgba(255, 255, 255, 0.04);
2876+
color: @project-panel-text-1;
2877+
border: 1px solid rgba(255, 255, 255, 0.12);
2878+
border-radius: 4px;
2879+
box-shadow: none;
2880+
2881+
&:focus {
2882+
background-color: rgba(255, 255, 255, 0.06);
2883+
border-color: rgba(107, 158, 255, 0.4);
2884+
outline: none;
2885+
box-shadow: none;
2886+
}
2887+
2888+
&::placeholder {
2889+
color: @project-panel-text-2;
2890+
opacity: 0.6;
2891+
}
2892+
}
2893+
}
2894+

0 commit comments

Comments
 (0)