From daa7ba32ca575f19f8c59cb0d3c545d24d04f783 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 8 Mar 2026 17:55:46 +0530 Subject: [PATCH 1/3] feat(ai): add filePath param to takeScreenshot MCP tool When filePath is specified, saves the screenshot to disk and returns the path instead of inline base64 image data. --- src-node/mcp-editor-tools.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src-node/mcp-editor-tools.js b/src-node/mcp-editor-tools.js index df5866897..febf0cf59 100644 --- a/src-node/mcp-editor-tools.js +++ b/src-node/mcp-editor-tools.js @@ -86,7 +86,9 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors) const takeScreenshotTool = sdkModule.tool( "takeScreenshot", - "Take a screenshot of the Phoenix Code editor window. Returns a PNG image. " + + "Take a screenshot of the Phoenix Code editor window. " + + "By default returns the screenshot as a PNG image inline. " + + "If filePath is specified, saves the screenshot to that file and returns the file path instead. " + "Prefer capturing specific regions instead of the full page: " + "use selector '#panel-live-preview-frame' for the live preview content, " + "or '.editor-holder' for the code editor area. " + @@ -95,16 +97,22 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors) "and other editor UI elements. Use purePreview=true to temporarily hide these overlays.", { selector: z.string().optional().describe("CSS selector to capture a specific element. Use '#panel-live-preview-frame' for the live preview, '.editor-holder' for the code editor."), - purePreview: z.boolean().optional().describe("When true, temporarily switches to preview mode to hide element highlight overlays and toolboxes before capturing, then restores the previous mode.") + purePreview: z.boolean().optional().describe("When true, temporarily switches to preview mode to hide element highlight overlays and toolboxes before capturing, then restores the previous mode."), + filePath: z.string().optional().describe("Absolute path to save the screenshot as a PNG file. If specified, returns the file path instead of inline image data.") }, async function (args) { let toolResult; try { const result = await nodeConnector.execPeer("takeScreenshot", { selector: args.selector || undefined, - purePreview: args.purePreview || false + purePreview: args.purePreview || false, + filePath: args.filePath || undefined }); - if (result.base64) { + if (result.filePath) { + toolResult = { + content: [{ type: "text", text: "Screenshot saved to: " + result.filePath }] + }; + } else if (result.base64) { toolResult = { content: [{ type: "image", data: result.base64, mimeType: "image/png" }] }; From 1853301d42d18330056849d2d43441f5d9bae493 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 8 Mar 2026 19:00:12 +0530 Subject: [PATCH 2/3] feat(ai): add attach file button styles and i18n strings --- src/nls/root/strings.js | 2 + src/styles/Extn-AIChatPanel.less | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 4e6821518..d5b318785 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1914,6 +1914,8 @@ define({ "AI_CHAT_QUESTION_OTHER": "Type a custom answer\u2026", "AI_CHAT_IMAGE_LIMIT": "Maximum {0} images allowed", "AI_CHAT_IMAGE_REMOVE": "Remove image", + "AI_CHAT_ATTACH_FILE": "Attach files", + "AI_CHAT_FILE_REMOVE": "Remove file", "AI_CHAT_QUEUED": "Queued", "AI_CHAT_QUEUED_EDIT": "Edit", "AI_CHAT_TOOL_CLARIFICATION": "Reading your follow-up", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index f1acab839..68d0423e6 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -293,6 +293,34 @@ } } +/* ── User message file chips ────────────────────────────────────────── */ +.ai-user-files { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 6px; + justify-content: flex-end; + + .ai-user-file-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + font-size: 11px; + color: @project-panel-text-2; + max-width: 180px; + + .ai-file-chip-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} + /* ── User message image thumbnails ──────────────────────────────────── */ .ai-user-images { display: flex; @@ -1352,6 +1380,73 @@ display: flex; } } + + .ai-file-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 6px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 4px; + font-size: 11px; + color: @project-panel-text-2; + max-width: 150px; + position: relative; + + .ai-file-chip-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .ai-file-remove { + position: absolute; + top: -6px; + right: -6px; + width: 16px; + height: 16px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.7); + border: 1px solid rgba(255, 255, 255, 0.2); + color: @project-panel-text-2; + font-size: 11px; + padding: 0; + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + } + + &:hover .ai-file-remove { + display: flex; + } + } + } + + .ai-attach-btn { + background: none; + border: none; + color: @project-panel-text-2; + width: 28px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + align-self: stretch; + border-radius: 7px 0 0 7px; + opacity: 0.5; + transition: opacity 0.15s ease, color 0.15s ease; + + &:hover { + opacity: 1; + color: @project-panel-text-1; + } + + i { + font-size: @sidebar-content-font-size; + } } .ai-chat-textarea { From 0c75dbebde0b9255e4aac57daa6025bb4edf5647 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 8 Mar 2026 20:03:14 +0530 Subject: [PATCH 3/3] feat: expose __kernalModeTrust in exec_js for dev/test access --- phoenix-builder-mcp/mcp-tools.js | 10 ++++++---- src/phoenix-builder/phoenix-builder-boot.js | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/phoenix-builder-mcp/mcp-tools.js b/phoenix-builder-mcp/mcp-tools.js index 0f040f2d8..070ea24a9 100644 --- a/phoenix-builder-mcp/mcp-tools.js +++ b/phoenix-builder-mcp/mcp-tools.js @@ -322,10 +322,12 @@ export function registerTools(server, processManager, wsControlServer, phoenixDe "Execute JavaScript in the Phoenix Code browser runtime and return the result. " + "Code runs async in the page context with access to: " + "$ (jQuery) for DOM queries/clicks, " + - "brackets.test.CommandManager, brackets.test.EditorManager, brackets.test.ProjectManager, " + - "brackets.test.DocumentManager, brackets.test.FileSystem, brackets.test.FileUtils, " + - "and 50+ other modules on brackets.test.* — " + - "supports await.", + "brackets.test.CommandManager, brackets.test.EditorManager, " + + "brackets.test.ProjectManager, brackets.test.DocumentManager, " + + "brackets.test.FileSystem, brackets.test.FileUtils, " + + "and 50+ other modules on brackets.test.* — supports await. " + + "__kernalModeTrust is available as a parameter " + + "(deleted from window after boot, preserved here for dev/test).", { code: z.string().describe("JavaScript code to execute in Phoenix"), instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.") diff --git a/src/phoenix-builder/phoenix-builder-boot.js b/src/phoenix-builder/phoenix-builder-boot.js index 91eb38b2d..f1ff826a7 100644 --- a/src/phoenix-builder/phoenix-builder-boot.js +++ b/src/phoenix-builder/phoenix-builder-boot.js @@ -387,10 +387,11 @@ // --- Register built-in handler for exec_js_request --- // Evaluates arbitrary JS in the page context and returns the result. + // `__kernalModeTrust` is available inside exec_js code for dev/test access. registerHandler("exec_js_request", function (msg) { const AsyncFunction = (async function () {}).constructor; - const fn = new AsyncFunction(msg.code); - fn().then(function (result) { + const fn = new AsyncFunction("__kernalModeTrust", msg.code); + fn(_kernalModeTrust).then(function (result) { _sendMessage({ type: "exec_js_response", id: msg.id,