diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index d863e1201..921c286a0 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -363,6 +363,8 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "multiple Edit calls to make targeted changes rather than rewriting the entire " + "file with Write. This is critical because Write replaces the entire file content " + "which is slow and loses undo history." + + "\n\nAlways use full absolute paths for all file operations (Read, Edit, Write, " + + "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." + (locale && !locale.startsWith("en") diff --git a/src-node/mcp-editor-tools.js b/src-node/mcp-editor-tools.js index 962456d68..d04b698ae 100644 --- a/src-node/mcp-editor-tools.js +++ b/src-node/mcp-editor-tools.js @@ -61,7 +61,7 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors) const hasClarification = clarificationAccessors && clarificationAccessors.hasClarification; const getEditorStateTool = sdkModule.tool( "getEditorState", - "Get the current Phoenix editor state: active file, working set (open files), live preview file, " + + "Get the current Phoenix editor state: active file, working set (open files with isDirty flag), live preview file, " + "cursor/selection info (current line text with surrounding context, or selected text), " + "and the currently selected element in the live preview (tag, selector, text preview) if any. " + "The live preview selected element may differ from the editor cursor — use execJsInLivePreview to inspect it further. " + diff --git a/src/core-ai/AIChatPanel.js b/src/core-ai/AIChatPanel.js index b6a6a616d..1d542c8ad 100644 --- a/src/core-ai/AIChatPanel.js +++ b/src/core-ai/AIChatPanel.js @@ -45,6 +45,7 @@ define(function (require, exports, module) { const _KernalModeTrust = window.KernalModeTrust; let _nodeConnector = null; + let _currentEntitlementState = null; // "chat" | "login" | "upsell" | "adminDisabled" | null let _isStreaming = false; let _queuedMessage = null; // text queued by user while AI is streaming let _currentRequestId = null; @@ -240,15 +241,20 @@ define(function (require, exports, module) { * upsell screen if no AI plan, or proceeds to CLI availability check if entitled. */ function _checkEntitlementAndInit() { - _removeCurrentPanel(); const EntitlementsManager = _KernalModeTrust && _KernalModeTrust.EntitlementsManager; if (!EntitlementsManager) { // No entitlement system (test env or dev) — skip straight to CLI check - _checkAvailability(); + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); + } return; } if (!EntitlementsManager.isLoggedIn()) { - _renderLoginUI(); + if (_currentEntitlementState !== "login") { + _removeCurrentPanel(); + _renderLoginUI(); + } return; } // TODO: Switch to EntitlementsManager.getAIEntitlement() once AI entitlement is @@ -267,12 +273,21 @@ define(function (require, exports, module) { // }); EntitlementsManager.getLiveEditEntitlement().then(function (entitlement) { if (entitlement.activated) { - _checkAvailability(); + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); + } } else { - _renderUpsellUI(entitlement); + if (_currentEntitlementState !== "upsell") { + _removeCurrentPanel(); + _renderUpsellUI(entitlement); + } } }).catch(function () { - _checkAvailability(); // fallback on error + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); // fallback on error + } }); } @@ -280,6 +295,7 @@ define(function (require, exports, module) { * Render the login prompt UI (user not signed in). */ function _renderLoginUI() { + _currentEntitlementState = "login"; const html = '
' + '
' + @@ -302,6 +318,7 @@ define(function (require, exports, module) { * Render the upsell UI (user logged in but no AI plan). */ function _renderUpsellUI(entitlement) { + _currentEntitlementState = "upsell"; const html = '
' + '
' + @@ -325,6 +342,7 @@ define(function (require, exports, module) { * Render the admin-disabled UI (AI turned off by system administrator). */ function _renderAdminDisabledUI() { + _currentEntitlementState = "adminDisabled"; const html = '
' + '
' + @@ -359,6 +377,7 @@ define(function (require, exports, module) { * Render the full chat UI. */ function _renderChatUI() { + _currentEntitlementState = "chat"; $panel = $(PANEL_HTML); $messages = $panel.find(".ai-chat-messages"); $status = $panel.find(".ai-chat-status"); @@ -567,6 +586,7 @@ define(function (require, exports, module) { * Render the unavailable UI (CLI not found). */ function _renderUnavailableUI(error) { + _currentEntitlementState = "chat"; const $unavailable = $(UNAVAILABLE_HTML); $unavailable.find(".ai-retry-btn").on("click", function () { _checkAvailability(); diff --git a/src/core-ai/aiPhoenixConnectors.js b/src/core-ai/aiPhoenixConnectors.js index d29430f19..bc89b8a56 100644 --- a/src/core-ai/aiPhoenixConnectors.js +++ b/src/core-ai/aiPhoenixConnectors.js @@ -213,7 +213,9 @@ define(function (require, exports, module) { if (p.startsWith("/tauri/")) { p = p.replace("/tauri", ""); } - return p; + const doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + const isDirty = doc ? doc.isDirty : false; + return { path: p, isDirty: isDirty }; }); let livePreviewFile = null; @@ -338,6 +340,7 @@ define(function (require, exports, module) { deferred.resolve({ base64: base64 }); }) .catch(function (err) { + console.error("[AI Control] Screenshot failed:", err); deferred.resolve({ base64: null, error: err.message || String(err) }); }); return deferred.promise(); @@ -350,6 +353,14 @@ define(function (require, exports, module) { return deferred.promise(); } + function _onResult(result) { + if (result.base64) { + console.log("[AI Control] Screenshot taken:", params.selector || "full window", + params.purePreview ? "(pure preview)" : ""); + } + deferred.resolve(result); + } + if (params.purePreview) { const previousMode = LiveDevMain.getCurrentMode(); LiveDevMain.setMode(LivePreviewConstants.LIVE_PREVIEW_MODE); @@ -358,12 +369,12 @@ define(function (require, exports, module) { _captureScreenshot(params.selector) .done(function (result) { LiveDevMain.setMode(previousMode); - deferred.resolve(result); + _onResult(result); }); }, 150); } else { _captureScreenshot(params.selector) - .done(function (result) { deferred.resolve(result); }); + .done(function (result) { _onResult(result); }); } return deferred.promise(); @@ -490,6 +501,7 @@ define(function (require, exports, module) { }); }) .fail(function (err) { + console.error("[AI Control] applyEditToBuffer failed:", err); deferred.resolve({ applied: false, error: err.message || String(err) }); }); return deferred.promise(); @@ -544,6 +556,7 @@ define(function (require, exports, module) { }) .fail(function (err) { _onExecJsDone(); + console.error("[AI Control] execJsInLivePreview failed:", err); deferred.resolve({ error: (err && err.message) || String(err) || "evaluate() failed" }); }); } @@ -581,6 +594,7 @@ define(function (require, exports, module) { if (settled) { return; } cleanup(); _onExecJsDone(); + console.error("[AI Control] Timed out waiting for live preview connection (30s)"); deferred.resolve({ error: "Timed out waiting for live preview connection (30s)" }); }, TIMEOUT); @@ -618,18 +632,18 @@ define(function (require, exports, module) { function controlEditor(params) { const deferred = new $.Deferred(); const vfsPath = SnapshotStore.realToVfsPath(params.filePath); - console.log("controlEditor:", params.operation, params.filePath, "-> vfs:", vfsPath); + console.log("[AI Control] controlEditor:", params.operation, params.filePath, "-> vfs:", vfsPath); function _resolve(result) { if (!result.success) { - console.error("controlEditor failed:", params.operation, vfsPath, result.error); + console.error("[AI Control] controlEditor failed:", params.operation, vfsPath, result.error); } deferred.resolve(result); } switch (params.operation) { case "open": - CommandManager.execute(Commands.CMD_OPEN, { fullPath: vfsPath }) + CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, { fullPath: vfsPath }) .done(function () { _resolve({ success: true }); }) .fail(function (err) { _resolve({ success: false, error: String(err) }); }); break; diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index ef56a4c16..915bd0c60 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -243,6 +243,9 @@ padding: 8px 10px; min-height: 0; min-width: 0; + -webkit-user-select: text; + user-select: text; + cursor: default; } /* ── Individual messages ────────────────────────────────────────────── */ @@ -265,6 +268,7 @@ word-wrap: break-word; overflow-wrap: anywhere; min-width: 0; + -webkit-user-select: text; user-select: text; cursor: text; } @@ -475,6 +479,7 @@ margin-bottom: 3px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.025); + border: 1px solid rgba(255, 255, 255, 0.04); border-left: 2px solid var(--tool-color, @project-panel-text-2); .ai-tool-header {