diff --git a/src/components/agents/AgentTestPanel.jsx b/src/components/agents/AgentTestPanel.jsx index 6ca5613..9c0867f 100644 --- a/src/components/agents/AgentTestPanel.jsx +++ b/src/components/agents/AgentTestPanel.jsx @@ -47,7 +47,7 @@ const CLASSIFICATION_COPY = { }; /** @param {any} props */ -export function AgentTestPanel({ fields, dirty, customSkills = [] }) { +export function AgentTestPanel({ fields, dirty, customSkills = [], onRunLive }) { const [contextId, setContextId] = React.useState( () => CONTEXT_OPTIONS.find((o) => fields.pages.includes(pageKeyForContext(o.contextId)))?.contextId || CONTEXT_OPTIONS[0].contextId @@ -79,6 +79,7 @@ export function AgentTestPanel({ fields, dirty, customSkills = [] }) { return { covers, + disabled, pageOnly, scoped, matched, @@ -152,6 +153,33 @@ export function AgentTestPanel({ fields, dirty, customSkills = [] }) { className="pl-9 w-full bg-surface" /> + {/* Everything else on this tab is a STATIC read of where a + question would route. Useful, and not the same as asking. The + tab is called Test, so a reader reasonably expects to be able + to run the thing — this is that, through the existing Owliver + panel and the draft's own scope, so the answer comes from the + agent being edited rather than the published one. */} + {onRunLive && ( + + )} diff --git a/src/components/ai-assistant/KrowAssistant.jsx b/src/components/ai-assistant/KrowAssistant.jsx index 4a17a0f..0a46248 100644 --- a/src/components/ai-assistant/KrowAssistant.jsx +++ b/src/components/ai-assistant/KrowAssistant.jsx @@ -281,13 +281,21 @@ export default function KrowAssistant({ request: panelRequest, consumeRequest, notice: panelNotice, consumeNotice, test: panelTest, reportTest, clearTest, + ask: askAssistant, } = useAssistantPanel(); /* The app's own router, not a location assignment: a full page load would discard the thread and the panel state along with it. */ const goToPage = React.useCallback( - (destination) => navigate(destination.route), - [navigate] + (destination, question) => { + navigate(destination.route); + /* Hand the question to the panel that mounts on the other side. The + request lives in AssistantPanelContext, which sits ABOVE the router, so + it survives the navigation that discards this panel's thread. Without + this the reader has to retype what they just typed. */ + if (question) askAssistant({ question }); + }, + [navigate, askAssistant] ); /* Skills available here, and the roles they can act on — both read from what diff --git a/src/components/ai-assistant/routing.js b/src/components/ai-assistant/routing.js index f6382e4..a82fa00 100644 --- a/src/components/ai-assistant/routing.js +++ b/src/components/ai-assistant/routing.js @@ -159,7 +159,7 @@ export function answerableHere(context, question) { export function navigationAnswer(destination) { return doc( text(`That is on **${destination.page}**. Taking you there now.`), - note('Ask again once the page loads and I will answer from it.') + note('Bringing your question with me — I will answer it from that page.') ); } diff --git a/src/components/ai-assistant/useAssistant.js b/src/components/ai-assistant/useAssistant.js index 6701278..a2c1df9 100644 --- a/src/components/ai-assistant/useAssistant.js +++ b/src/components/ai-assistant/useAssistant.js @@ -723,12 +723,20 @@ export function useConversation({ abortRef.current = null; } - /* Navigate after the reply is on screen, so the user reads why they moved. - The panel re-resolves its context from the new route, which is what - makes the next question answer from the page they land on. */ + /* Navigate after the reply is on screen — except the reply does not + survive the move. The panel is page-scoped, so it re-mounts on the new + route and the message explaining why the reader moved is destroyed by + the navigation that message was explaining. The reader landed somewhere + else with a fresh greeting and no trace of what they asked. + + So the question travels with the destination. The panel on the other + side asks it, which is what the reader wanted in the first place and + what the old copy ("ask again once the page loads") was apologising + for. Re-asking cannot loop: resolveIntent only navigates when the + destination differs from the current page. */ if (controller.signal.aborted) return; - if (intent.kind === 'navigate') onNavigate?.(intent.destination); + if (intent.kind === 'navigate') onNavigate?.(intent.destination, text); /* A skill's action runs after its reply, for the same reason: the user should read why the form opened before it opens. */ if (intent.kind === 'skill' && intent.action) onAction?.(intent.action, intent.skill); diff --git a/src/pages/admin/AgentDetail.jsx b/src/pages/admin/AgentDetail.jsx index 3625525..444d5b0 100644 --- a/src/pages/admin/AgentDetail.jsx +++ b/src/pages/admin/AgentDetail.jsx @@ -452,6 +452,7 @@ export default function AdminAgentDetail() { )} {view === 'test' && (