Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src-node/claude-code-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src-node/mcp-editor-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. " +
Expand Down
32 changes: 26 additions & 6 deletions src/core-ai/AIChatPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -267,19 +273,29 @@ 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
}
});
}

/**
* Render the login prompt UI (user not signed in).
*/
function _renderLoginUI() {
_currentEntitlementState = "login";
const html =
'<div class="ai-chat-panel">' +
'<div class="ai-unavailable">' +
Expand All @@ -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 =
'<div class="ai-chat-panel">' +
'<div class="ai-unavailable">' +
Expand All @@ -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 =
'<div class="ai-chat-panel">' +
'<div class="ai-unavailable">' +
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();
Expand Down
26 changes: 20 additions & 6 deletions src/core-ai/aiPhoenixConnectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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" });
});
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/styles/Extn-AIChatPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@
padding: 8px 10px;
min-height: 0;
min-width: 0;
-webkit-user-select: text;
user-select: text;
cursor: default;
}

/* ── Individual messages ────────────────────────────────────────────── */
Expand All @@ -265,6 +268,7 @@
word-wrap: break-word;
overflow-wrap: anywhere;
min-width: 0;
-webkit-user-select: text;
user-select: text;
cursor: text;
}
Expand Down Expand Up @@ -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 {
Expand Down
Loading