From 581e29f70404a7bbf07526ae692068ebe3947bdd Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 16 Mar 2026 17:27:03 +0530 Subject: [PATCH 1/8] feat(ai): add Phoenix Code context and improve system prompt Add context that this is Phoenix Code with live preview for HTML/CSS/JS. Prefer vanilla web tech for mockups/prototypes, build responsive layouts by default. Soften plan mode hint to "use your best judgement". --- src-node/claude-code-agent.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index fac1e11ac..d6f60e0fd 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -470,13 +470,18 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "controlEditor). Never use relative paths." + "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + + "\n\nYou are running inside Phoenix Code, a web-focused code editor with a built-in " + + "live preview for HTML/CSS/JS. When the user asks to create mockups, prototypes, " + + "or web pages, prefer vanilla HTML/CSS/JS so the live preview can render and " + + "edit them — unless the user specifically requests a framework. " + + "Build responsive layouts by default for web content." + "\n\nWhen planning, consider if verification is needed. takeScreenshot can " + "capture the full editor, specific panels, the code area, or the live preview. " + "For HTML/CSS/JS with live preview, execJsInLivePreview can run JS in the " + "browser to confirm behavior." + - "\n\nFor tasks that involve creating new applications, extensive modifications, " + - "or architectural changes, enter plan mode first to propose a plan " + - "for user approval before writing code." + + "\n\nUse your best judgement for when to enter plan mode. Use it when the task " + + "involves creating new applications, extensive modifications, or architectural " + + "changes — propose a plan for user approval before writing code." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language." From feee30ede0427ac8711098c88e18674a79404875 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 11:23:49 +0530 Subject: [PATCH 2/8] feat: plan mode and auto edit mode --- src-node/claude-code-agent.js | 41 ++++++++++++++++++++++++++++---- src/nls/root/strings.js | 2 ++ src/styles/Extn-AIChatPanel.less | 39 ++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index d6f60e0fd..ddf0d427e 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -209,7 +209,7 @@ exports.checkAvailability = async function () { * aiProgress, aiTextStream, aiToolEdit, aiError, aiComplete */ exports.sendPrompt = async function (params) { - const { prompt, projectPath, sessionAction, model, locale, selectionContext, images, envOverrides } = params; + const { prompt, projectPath, sessionAction, model, locale, selectionContext, images, envOverrides, permissionMode } = params; const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2, 7); // Handle session @@ -252,7 +252,7 @@ exports.sendPrompt = async function (params) { } // Run the query asynchronously — don't await here so we return requestId immediately - _runQuery(requestId, enrichedPrompt, projectPath, model, currentAbortController.signal, locale, images, envOverrides) + _runQuery(requestId, enrichedPrompt, projectPath, model, currentAbortController.signal, locale, images, envOverrides, permissionMode) .catch(err => { console.error("[Phoenix AI] Query error:", err); }); @@ -370,7 +370,7 @@ exports.clearClarification = async function () { /** * Internal: run a Claude SDK query and stream results back to the browser. */ -async function _runQuery(requestId, prompt, projectPath, model, signal, locale, images, envOverrides) { +async function _runQuery(requestId, prompt, projectPath, model, signal, locale, images, envOverrides, permissionMode) { let editCount = 0; let toolCounter = 0; let queryFn; @@ -455,7 +455,7 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, } }, mcpServers: { "phoenix-editor": editorMcpServer }, - permissionMode: "acceptEdits", + permissionMode: permissionMode || "acceptEdits", appendSystemPrompt: "When modifying an existing file, always prefer the Edit tool " + "(find-and-replace) instead of the Write tool. The Write tool should ONLY be used " + @@ -496,6 +496,39 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, hooks: [ async (input) => { console.log("[Phoenix AI] Intercepted Edit tool"); + // Plan file edits: capture content, write to disk, skip editor + const editPath = (input.tool_input.file_path || "").replace(/\\/g, "/"); + if (editPath.includes("/.claude/plans/")) { + try { + let content = ""; + if (fs.existsSync(input.tool_input.file_path)) { + content = fs.readFileSync(input.tool_input.file_path, "utf8"); + } + if (input.tool_input.old_string && input.tool_input.new_string) { + content = content.replace(input.tool_input.old_string, input.tool_input.new_string); + } + const dir = path.dirname(input.tool_input.file_path); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(input.tool_input.file_path, content, "utf8"); + _lastPlanContent = content; + console.log("[Phoenix AI] Captured plan edit content:", content.length + "ch"); + } catch (err) { + console.warn("[Phoenix AI] Failed to edit plan file:", err.message); + } + let planReason = "Plan file updated."; + if (_queuedClarification) { + planReason += CLARIFICATION_HINT; + } + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: planReason + } + }; + } const myToolId = toolCounter; // capture before any await const edit = { file: input.tool_input.file_path, diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 113f66fa6..9c56f7ab5 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1926,6 +1926,8 @@ define({ "AI_CHAT_PLAN_REVISE": "Revise", "AI_CHAT_PLAN_FEEDBACK_PLACEHOLDER": "What would you like changed?", "AI_CHAT_PLAN_REVISE_DEFAULT": "Please revise the plan.", + "AI_CHAT_MODE_PLAN": "Plan Mode", + "AI_CHAT_MODE_FULL_AUTO": "Full Auto", "AI_CHAT_CODE_DEFAULT_LANG": "text", "AI_CHAT_CODE_COLLAPSE": "Collapse", "AI_CHAT_CODE_EXPAND": "Expand", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 0aee24059..4d041e854 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1620,11 +1620,46 @@ } } + .ai-permission-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + cursor: pointer; + user-select: none; + + &:hover { + background: rgba(255, 255, 255, 0.04); + border-radius: 4px; + } + + .ai-permission-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + + &.mode-auto { + background-color: #e74c3c; + } + + &.mode-plan { + background-color: #3498db; + } + } + + .ai-permission-label { + font-size: @sidebar-xs-font-size; + color: @project-panel-text-2; + line-height: 1; + } + } + .ai-chat-context-bar { display: none; flex-wrap: wrap; - gap: 4px; - padding: 0 4px 4px 4px; + gap: 6px; + padding: 0 4px 6px 4px; &.has-chips { display: flex; From 99475a65122db1c351f582428081041fd54f4398 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 11:40:03 +0530 Subject: [PATCH 3/8] feat: plan mode and auto edit mode --- src/nls/root/strings.js | 1 + src/styles/Extn-AIChatPanel.less | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 9c56f7ab5..c0587755d 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1936,6 +1936,7 @@ define({ "AI_CHAT_PREVIEW_OPEN": "Preview", "AI_CHAT_PREVIEW_VIEWING": "Previewing", "AI_CHAT_QUESTION_OTHER": "Type a custom answer\u2026", + "AI_CHAT_QUESTION_SUBMIT": "Submit", "AI_CHAT_IMAGE_LIMIT": "Maximum {0} images allowed", "AI_CHAT_IMAGE_REMOVE": "Remove image", "AI_CHAT_ATTACH_FILE": "Attach files", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 4d041e854..8af8a8470 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1022,7 +1022,10 @@ } .ai-question-submit { - align-self: flex-end; + display: flex; + align-items: center; + gap: 5px; + margin-left: auto; background: none; border: 1px solid rgba(76, 175, 80, 0.3); color: rgba(76, 175, 80, 0.85); @@ -1088,6 +1091,7 @@ display: flex; align-items: center; justify-content: center; + gap: 5px; transition: background-color 0.15s ease, color 0.15s ease; &:hover:not(:disabled) { From 3863a0f35be4c2fbf22b2abc2979e03d5a754ae3 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 11:42:11 +0530 Subject: [PATCH 4/8] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 66abb0da1..635adcd7a 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "21f226b54906adc5bd2cb0dd0d68d587dc9d22fe" + "commitID": "3b18f2068ee7fc6836a2352c6047da66a896becb" } } From b982b4bff3e854455f3c69e111fc6f01ac542557 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 13:03:17 +0530 Subject: [PATCH 5/8] fix: remove deprecation warning from EditorManager.focusEditor --- src/editor/EditorManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index 4dd00f280..36e613070 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -495,7 +495,6 @@ define(function (require, exports, module) { * removed. For example, after a dialog with editable text is closed. */ function focusEditor() { - DeprecationWarning.deprecationWarning("Use MainViewManager.focusActivePane() instead of EditorManager.focusEditor().", true); MainViewManager.focusActivePane(); } From f3cae0d35cb3e68bb502330b864f33c83f6cbf75 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 13:15:10 +0530 Subject: [PATCH 6/8] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 635adcd7a..87d4376e1 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "3b18f2068ee7fc6836a2352c6047da66a896becb" + "commitID": "32bd241a5376648ecc09bf2ae19d50ea229cd305" } } From 2223590c7f913b660dc8b78d4b58cabc03e1b2b8 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 14:12:00 +0530 Subject: [PATCH 7/8] feat: three-tier permission modes with bash confirmation in Edit Mode - Edit Mode (default): auto-allows edits, prompts for bash commands - Full Auto (bypassPermissions): allows everything without prompts - Plan Mode: plans first as before - Bash PreToolUse hook sends confirmation to browser in Edit Mode - Confirmation card collapses to status line after Allow/Deny --- src-node/claude-code-agent.js | 59 +++++++++++++++++++++ src/nls/root/strings.js | 6 +++ src/styles/Extn-AIChatPanel.less | 90 ++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index ddf0d427e..ace042841 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -60,6 +60,9 @@ let _questionResolve = null; // Pending plan resolver — used by ExitPlanMode stream interception let _planResolve = null; +// Pending bash confirmation resolver — used by Bash PreToolUse hook (Edit Mode) +let _bashConfirmResolve = null; + // Stores rejection feedback when user rejects a plan let _planRejectionFeedback = null; @@ -272,6 +275,7 @@ exports.cancelQuery = async function () { // Clear any pending question or plan _questionResolve = null; _planResolve = null; + _bashConfirmResolve = null; _queuedClarification = null; return { success: true }; } @@ -302,6 +306,18 @@ exports.answerPlan = async function (params) { return { success: true }; }; +/** + * Receive the user's response to a bash confirmation prompt (Edit Mode). + * Called from browser via execPeer("answerBashConfirm", {allowed}). + */ +exports.answerBashConfirm = async function (params) { + if (_bashConfirmResolve) { + _bashConfirmResolve(params); + _bashConfirmResolve = null; + } + return { success: true }; +}; + /** * Resume a previous session by setting the session ID. * The next sendPrompt call will use queryOptions.resume with this session ID. @@ -313,6 +329,7 @@ exports.resumeSession = async function (params) { } _questionResolve = null; _planResolve = null; + _bashConfirmResolve = null; _queuedClarification = null; currentSessionId = params.sessionId; return { success: true }; @@ -696,6 +713,48 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, } ] }, + { + matcher: "Bash", + hooks: [ + async (input) => { + if (permissionMode !== "acceptEdits") { + // Plan mode: SDK handles. Full Auto: allow freely. + return {}; + } + // Edit Mode: ask user confirmation before running bash + const command = input.tool_input.command || ""; + console.log("[Phoenix AI] Bash confirmation requested:", command.slice(0, 80)); + nodeConnector.triggerPeer("aiBashConfirm", { + requestId: requestId, + command: command, + toolId: toolCounter + }); + const response = await new Promise((resolve, reject) => { + _bashConfirmResolve = resolve; + if (signal.aborted) { + _bashConfirmResolve = null; + reject(new Error("Aborted")); + return; + } + const onAbort = () => { + _bashConfirmResolve = null; + reject(new Error("Aborted")); + }; + signal.addEventListener("abort", onAbort, { once: true }); + }); + if (response.allowed) { + return {}; + } + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: "User denied this command." + } + }; + } + ] + }, { matcher: "AskUserQuestion", hooks: [ diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c0587755d..078fae238 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1927,7 +1927,13 @@ define({ "AI_CHAT_PLAN_FEEDBACK_PLACEHOLDER": "What would you like changed?", "AI_CHAT_PLAN_REVISE_DEFAULT": "Please revise the plan.", "AI_CHAT_MODE_PLAN": "Plan Mode", + "AI_CHAT_MODE_EDIT": "Edit Mode", "AI_CHAT_MODE_FULL_AUTO": "Full Auto", + "AI_CHAT_BASH_CONFIRM_TITLE": "Allow command?", + "AI_CHAT_BASH_ALLOW": "Allow", + "AI_CHAT_BASH_DENY": "Deny", + "AI_CHAT_BASH_ALLOWED": "Command allowed", + "AI_CHAT_BASH_DENIED": "Command denied", "AI_CHAT_CODE_DEFAULT_LANG": "text", "AI_CHAT_CODE_COLLAPSE": "Collapse", "AI_CHAT_CODE_EXPAND": "Expand", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 8af8a8470..35ff4c8f5 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1412,6 +1412,92 @@ } } +/* ── Bash confirmation card ─────────────────────────────────────────── */ +.ai-bash-confirm { + border: 1px solid rgba(243, 156, 18, 0.3); + border-radius: 8px; + background: rgba(243, 156, 18, 0.06); + margin: 8px 0; + overflow: hidden; + + .ai-bash-confirm-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + font-weight: 600; + font-size: @sidebar-content-font-size; + color: #f39c12; + border-bottom: 1px solid rgba(243, 156, 18, 0.15); + } + + .ai-bash-confirm-body { + padding: 8px 12px; + + pre { + margin: 0; + padding: 8px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.2); + color: #e0e0e0; + font-size: @sidebar-content-font-size; + white-space: pre-wrap; + word-break: break-all; + } + } + + .ai-bash-confirm-actions { + display: flex; + gap: 8px; + padding: 8px 12px; + border-top: 1px solid rgba(243, 156, 18, 0.15); + } + + .ai-bash-allow-btn { + background: rgba(76, 175, 80, 0.15); + border: 1px solid rgba(76, 175, 80, 0.35); + color: #81c784; + padding: 4px 14px; + border-radius: 4px; + cursor: pointer; + font-size: @sidebar-xs-font-size; + + &:hover { + background: rgba(76, 175, 80, 0.25); + } + } + + .ai-bash-deny-btn { + background: rgba(231, 76, 60, 0.15); + border: 1px solid rgba(231, 76, 60, 0.35); + color: #e57373; + padding: 4px 14px; + border-radius: 4px; + cursor: pointer; + font-size: @sidebar-xs-font-size; + + &:hover { + background: rgba(231, 76, 60, 0.25); + } + } + +} + +.ai-bash-result { + padding: 4px 10px; + border-radius: 4px; + font-size: @sidebar-xs-font-size; + margin: 4px 0; + + &.ai-bash-result-allowed { + color: #81c784; + } + + &.ai-bash-result-denied { + color: #e57373; + } +} + /* ── Queued clarification bubble (static, above input) ─────────────── */ .ai-queued-msg { border: 1px dashed rgba(255, 255, 255, 0.15); @@ -1647,6 +1733,10 @@ background-color: #e74c3c; } + &.mode-edit { + background-color: #f39c12; + } + &.mode-plan { background-color: #3498db; } From bd021f166df9277b8e6f5cd1114cdf5786b9fa9f Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 17 Mar 2026 14:13:38 +0530 Subject: [PATCH 8/8] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 87d4376e1..f26946e06 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "32bd241a5376648ecc09bf2ae19d50ea229cd305" + "commitID": "4ca7bc6eea7ad4c94fbf321ae2c854f533fb8bd2" } }