Skip to content

Commit e1e7b69

Browse files
committed
feat: add wait MCP tool, extend live preview banner duration, fix table cursor
- Add `wait` MCP tool (0.1–60s) with countdown UI in AI chat panel - Auto-approve `wait` tool in allowedTools list - Show "Waiting Xs" countdown that becomes "Done waiting Xs" on completion - Keep live preview banner visible for 5s after exec/resize (was instant/3s) - Cancel pending auto-hide timer when new exec batch starts - Fix text cursor on table cells (th/td) in AI chat messages
1 parent ebea385 commit e1e7b69

6 files changed

Lines changed: 59 additions & 4 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
237237
"mcp__phoenix-editor__takeScreenshot",
238238
"mcp__phoenix-editor__execJsInLivePreview",
239239
"mcp__phoenix-editor__controlEditor",
240-
"mcp__phoenix-editor__resizeLivePreview"
240+
"mcp__phoenix-editor__resizeLivePreview",
241+
"mcp__phoenix-editor__wait"
241242
],
242243
mcpServers: { "phoenix-editor": editorMcpServer },
243244
permissionMode: "acceptEdits",

src-node/mcp-editor-tools.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,27 @@ function createEditorMcpServer(sdkModule, nodeConnector) {
200200
}
201201
);
202202

203+
const waitTool = sdkModule.tool(
204+
"wait",
205+
"Wait for a specified number of seconds before continuing. " +
206+
"Useful for waiting after DOM changes, animations, live preview updates, or resize operations " +
207+
"before taking a screenshot or inspecting state. Maximum 60 seconds.",
208+
{
209+
seconds: z.number().min(0.1).max(60).describe("Number of seconds to wait (0.1–60)")
210+
},
211+
async function (args) {
212+
const ms = Math.round(args.seconds * 1000);
213+
await new Promise(function (resolve) { setTimeout(resolve, ms); });
214+
return {
215+
content: [{ type: "text", text: "Waited " + args.seconds + " seconds." }]
216+
};
217+
}
218+
);
219+
203220
return sdkModule.createSdkMcpServer({
204221
name: "phoenix-editor",
205-
tools: [getEditorStateTool, takeScreenshotTool, execJsInLivePreviewTool, controlEditorTool, resizeLivePreviewTool]
222+
tools: [getEditorStateTool, takeScreenshotTool, execJsInLivePreviewTool, controlEditorTool,
223+
resizeLivePreviewTool, waitTool]
206224
});
207225
}
208226

src/core-ai/AIChatPanel.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ define(function (require, exports, module) {
640640
"mcp__phoenix-editor__execJsInLivePreview": { icon: "fa-solid fa-eye", color: "#66bb6a", label: Strings.AI_CHAT_TOOL_LIVE_PREVIEW_JS },
641641
"mcp__phoenix-editor__controlEditor": { icon: "fa-solid fa-code", color: "#6bc76b", label: Strings.AI_CHAT_TOOL_CONTROL_EDITOR },
642642
"mcp__phoenix-editor__resizeLivePreview": { icon: "fa-solid fa-arrows-left-right", color: "#66bb6a", label: Strings.AI_CHAT_TOOL_RESIZE_PREVIEW },
643+
"mcp__phoenix-editor__wait": { icon: "fa-solid fa-hourglass-half", color: "#adb9bd", label: Strings.AI_CHAT_TOOL_WAIT },
643644
TodoWrite: { icon: "fa-solid fa-list-check", color: "#66bb6a", label: Strings.AI_CHAT_TOOL_TASKS }
644645
};
645646

@@ -1376,6 +1377,20 @@ define(function (require, exports, module) {
13761377
$tool.find(".ai-tool-header").on("click", function () {
13771378
$tool.toggleClass("ai-tool-expanded");
13781379
}).css("cursor", "pointer");
1380+
} else if (toolName === "mcp__phoenix-editor__wait" && toolInput && toolInput.seconds) {
1381+
// Countdown timer: update label every second
1382+
const totalSec = Math.ceil(toolInput.seconds);
1383+
let remaining = totalSec;
1384+
const $label = $tool.find(".ai-tool-label");
1385+
const countdownId = setInterval(function () {
1386+
remaining--;
1387+
if (remaining <= 0) {
1388+
clearInterval(countdownId);
1389+
$label.text(StringUtils.format(Strings.AI_CHAT_TOOL_WAITED, totalSec));
1390+
} else {
1391+
$label.text(StringUtils.format(Strings.AI_CHAT_TOOL_WAITING, remaining));
1392+
}
1393+
}, 1000);
13791394
} else if (toolName === "mcp__phoenix-editor__takeScreenshot") {
13801395
const $detail = $('<div class="ai-tool-detail"></div>');
13811396
$tool.append($detail);
@@ -1503,6 +1518,11 @@ define(function (require, exports, module) {
15031518
summary: Strings.AI_CHAT_TOOL_RESIZE_PREVIEW,
15041519
lines: input.width ? [input.width + "px"] : []
15051520
};
1521+
case "mcp__phoenix-editor__wait":
1522+
return {
1523+
summary: StringUtils.format(Strings.AI_CHAT_TOOL_WAITING, input.seconds || "?"),
1524+
lines: []
1525+
};
15061526
case "TodoWrite": {
15071527
const todos = input.todos || [];
15081528
const completed = todos.filter(function (t) { return t.status === "completed"; }).length;

src/core-ai/aiPhoenixConnectors.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ define(function (require, exports, module) {
153153
function _onExecJsStart() {
154154
_activeExecJsCount++;
155155
if (_activeExecJsCount === 1) {
156+
// Cancel any pending auto-hide from a previous exec batch
157+
if (_bannerAutoHideTimer) {
158+
clearTimeout(_bannerAutoHideTimer);
159+
_bannerAutoHideTimer = null;
160+
}
156161
_savedLivePreviewMode = LiveDevMain.getCurrentMode();
157162
if (_savedLivePreviewMode !== LivePreviewConstants.LIVE_PREVIEW_MODE) {
158163
LiveDevMain.setMode(LivePreviewConstants.LIVE_PREVIEW_MODE);
@@ -173,7 +178,14 @@ define(function (require, exports, module) {
173178
LiveDevMain.setMode(_savedLivePreviewMode);
174179
}
175180
_savedLivePreviewMode = null;
176-
_hideBanner();
181+
// Keep the banner visible briefly so the user can read it
182+
if (_bannerAutoHideTimer) {
183+
clearTimeout(_bannerAutoHideTimer);
184+
}
185+
_bannerAutoHideTimer = setTimeout(function () {
186+
_hideBanner();
187+
_bannerAutoHideTimer = null;
188+
}, 5000);
177189
}
178190
}
179191

@@ -670,7 +682,7 @@ define(function (require, exports, module) {
670682
_bannerAutoHideTimer = setTimeout(function () {
671683
_hideBanner();
672684
_bannerAutoHideTimer = null;
673-
}, 3000);
685+
}, 5000);
674686

675687
const result = { actualWidth: actualWidth };
676688
if (actualWidth !== targetWidth) {

src/nls/root/strings.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,9 @@ define({
18621862
"AI_CHAT_CONTEXT_SELECTION": "Selection L{0}-L{1}",
18631863
"AI_CHAT_CONTEXT_CURSOR": "Line {0}",
18641864
"AI_CHAT_CONTEXT_LIVE_PREVIEW": "Live Preview",
1865+
"AI_CHAT_TOOL_WAIT": "Wait",
1866+
"AI_CHAT_TOOL_WAITING": "Waiting {0}s",
1867+
"AI_CHAT_TOOL_WAITED": "Done waiting {0}s",
18651868
"AI_CHAT_COPY_CODE": "Copy",
18661869
"AI_CHAT_COPIED_CODE": "Copied!",
18671870

src/styles/Extn-AIChatPanel.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
max-width: 50%;
243243
white-space: nowrap;
244244
overflow-wrap: normal;
245+
cursor: text;
245246
}
246247

247248
th {

0 commit comments

Comments
 (0)