Skip to content
Merged

Ai #2841

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
30 changes: 30 additions & 0 deletions src-node/claude-code-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,36 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
}
}
}

// Tool results come back as user-typed messages with content blocks
// of type tool_result. Log isError + content size so we can correlate
// a "Tool done" (input stream) with what Claude actually saw as the reply.
if (message.type === "user" && message.message && Array.isArray(message.message.content)) {
for (const block of message.message.content) {
if (block && block.type === "tool_result") {
let len = 0;
let preview = "";
if (typeof block.content === "string") {
len = block.content.length;
preview = block.content.slice(0, 120);
} else if (Array.isArray(block.content)) {
for (const c of block.content) {
if (c && c.type === "text" && typeof c.text === "string") {
len += c.text.length;
if (!preview) { preview = c.text.slice(0, 120); }
} else if (c && c.type === "image" && typeof c.data === "string") {
len += c.data.length;
if (!preview) { preview = "[image " + c.data.length + "ch]"; }
}
}
}
_log("Tool result:", block.tool_use_id || "?",
"isError=" + !!block.is_error,
"len=" + len + "ch",
preview ? ("preview=" + JSON.stringify(preview)) : "");
}
}
}
}

// Flush any remaining accumulated text
Expand Down
46 changes: 38 additions & 8 deletions src-node/mcp-editor-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ const CLARIFICATION_HINT =
"IMPORTANT: The user has typed a follow-up clarification while you were working." +
" Call the getUserClarification tool to read it before proceeding.";

// Per-tool safety-net budgets for the browser round-trip. The node connector
// is reliable in practice, so these should never fire during normal use —
// they exist so a stalled promise chain (live preview wedged, etc.) surfaces
// a deterministic error to Claude instead of the handler hanging forever.
// Tools whose runtime is bounded by user-supplied code (execJsInLivePreview)
// intentionally have no timeout — the code is allowed to run as long as it takes.
const EXEC_PEER_TIMEOUT_MS = {
getEditorState: 5000,
takeScreenshot: 15000,
controlEditor: 5000,
resizeLivePreview: 5000
};

function _execPeerWithTimeout(nodeConnector, fn, args, label) {
const ms = EXEC_PEER_TIMEOUT_MS[fn];
const call = nodeConnector.execPeer(fn, args);
if (!ms) {
return call; // no timeout configured for this tool
}
let timer;
const timeout = new Promise(function (_resolve, reject) {
timer = setTimeout(function () {
reject(new Error(label + " timed out after " + ms + "ms"));
}, ms);
});
return Promise.race([call, timeout]).finally(function () {
clearTimeout(timer);
});
}

/**
* Append a clarification hint to an MCP tool result if the user has queued a message.
*/
Expand Down Expand Up @@ -70,7 +100,7 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
async function () {
let result;
try {
const state = await nodeConnector.execPeer("getEditorState", {});
const state = await _execPeerWithTimeout(nodeConnector, "getEditorState", {}, "getEditorState");
result = {
content: [{ type: "text", text: JSON.stringify(state) }]
};
Expand Down Expand Up @@ -107,11 +137,11 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
async function (args) {
let toolResult;
try {
const result = await nodeConnector.execPeer("takeScreenshot", {
const result = await _execPeerWithTimeout(nodeConnector, "takeScreenshot", {
selector: args.selector || undefined,
purePreview: args.purePreview || false,
filePath: args.filePath || undefined
});
}, "takeScreenshot");
if (result.filePath) {
toolResult = {
content: [{ type: "text", text: "Screenshot saved to: " + result.filePath }]
Expand Down Expand Up @@ -148,9 +178,9 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
async function (args) {
let toolResult;
try {
const result = await nodeConnector.execPeer("execJsInLivePreview", {
const result = await _execPeerWithTimeout(nodeConnector, "execJsInLivePreview", {
code: args.code
});
}, "execJsInLivePreview");
if (result.error) {
toolResult = {
content: [{ type: "text", text: "Error: " + result.error }],
Expand Down Expand Up @@ -202,7 +232,7 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
for (const op of args.operations) {
console.log("[Phoenix AI] controlEditor:", op.operation, op.filePath);
try {
const result = await nodeConnector.execPeer("controlEditor", op);
const result = await _execPeerWithTimeout(nodeConnector, "controlEditor", op, "controlEditor:" + op.operation);
results.push(result);
if (!result.success) {
hasError = true;
Expand Down Expand Up @@ -234,9 +264,9 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
async function (args) {
let toolResult;
try {
const result = await nodeConnector.execPeer("resizeLivePreview", {
const result = await _execPeerWithTimeout(nodeConnector, "resizeLivePreview", {
width: args.width
});
}, "resizeLivePreview");
if (result.error) {
toolResult = {
content: [{ type: "text", text: "Error: " + result.error }],
Expand Down
15 changes: 12 additions & 3 deletions src/styles/Extn-AIChatPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@
line-height: 19px;
}

.ai-chat-header-actions {
.ai-chat-header-actions,
.ai-chat-header-left-actions {
position: absolute;
right: 10px;
display: flex;
align-items: center;
gap: 2px;
Expand All @@ -88,6 +88,14 @@
transition: opacity 0.5s ease;
}

.ai-chat-header-actions {
right: 10px;
}

.ai-chat-header-left-actions {
left: 10px;
}

.ai-history-btn,
.ai-settings-btn {
display: flex;
Expand All @@ -113,7 +121,8 @@
}

/* Show header actions on tab container hover */
.ai-tab-container:hover .ai-chat-header-actions {
.ai-tab-container:hover .ai-chat-header-actions,
.ai-tab-container:hover .ai-chat-header-left-actions {
opacity: 1;
pointer-events: auto;
transition: opacity 0.15s ease;
Expand Down
Loading