diff --git a/src-node/ai-cli-connector.js b/src-node/ai-cli-connector.js index 2576b33212..e247bf099f 100644 --- a/src-node/ai-cli-connector.js +++ b/src-node/ai-cli-connector.js @@ -249,7 +249,10 @@ class CliConnector { try { return await deadline(spec.handler(args), timeoutMs); } finally { - if (spec.name === "askInLivePreview") { this.cancelCall(session, frame.id); } + // A question still showing when its call ends, by error or budget, must not outlive it. + if (spec.name === "askInLivePreview" || spec.name === "askRichQuestion") { + this.cancelCall(session, frame.id); + } } } diff --git a/src-node/ai-editor-tool-specs.js b/src-node/ai-editor-tool-specs.js index a2eba6a729..6d1ac9948d 100644 --- a/src-node/ai-editor-tool-specs.js +++ b/src-node/ai-editor-tool-specs.js @@ -23,6 +23,7 @@ const path = require("path"); const fs = require("fs"); const { z } = require("zod"); +const {RICH_UI_STYLE_GUIDANCE} = require("./ai-system-prompt"); // Absolute path to the bundled API reference, mirrored from // docs/API-Reference/ at build time by build/api-docs-generator.js. @@ -52,6 +53,10 @@ const EXEC_PEER_TIMEOUT_MS = { useImage: 90000 }; +// A rich question waits until the user answers or its owner cancels it. The CLI transport needs a finite +// budget; this one is a day, and ending it cancels the question in the editor like any CLI cancellation. +const RICH_QUESTION_FAILSAFE_MS = 24 * 60 * 60 * 1000; + // Floor for caller-provided timeouts (e.g. execJsInLivePreview's // timeoutMs). 5s minimum stops the model from spamming impatient retries // on a preview that's just taking a beat to settle. No ceiling — the @@ -97,6 +102,9 @@ function getToolTimeout(name, args = {}) { if (name === "askInLivePreview") { return (args.timeoutS || 300) * 1000 + 15000; } + if (name === "askRichQuestion") { + return RICH_QUESTION_FAILSAFE_MS; + } if (name === "execJsInEditor" || name === "execJsInLivePreview") { return _resolveCallerTimeout(args.timeoutMs, 10000); } @@ -842,6 +850,64 @@ function getEditorToolSpecs(peerCall, options = {}) { } ); + addTool( + "askRichQuestion", + "Ask the user a question shown as rich interactive UI " + + (options.cli ? "in your session's pane in Phoenix" : "in the chat") + ", with no live preview needed, and " + + "wait for the answer: a visual choice, a short form, or something to look at and confirm. Prefer choices: " + + "each has id, label and optional description/color; Phoenix renders the cards and a pick returns " + + "{choice:id,label}. For custom content pass html or uiFile with its own