Carry a routed question across the move, and let the Test tab actually test
TWO fixes, both found by using the product rather than reading it. A question asked on a page that cannot answer it was lost. resolveIntent routes to the page that can, and routing.js answered "That is on Candidates. Taking you there now. Ask again once the page loads." — but the panel is page-scoped, so it re-mounts on the new route and the navigation destroys the very message explaining why the reader moved. What the reader saw was a different page and a fresh greeting, with no trace of what they asked. The question now travels with the destination. AssistantPanelContext sits ABOVE the router and already has `ask` for exactly this — a page handing a question to the panel — so the panel that mounts on the other side asks it. That is what the reader wanted, and what "ask again once the page loads" was apologising for. It cannot loop: resolveIntent only routes when the destination differs from the page you are on, and answerableHere short-circuits before that. The Test tab did not test. "Simulation & Scope Diagnostics" reads where a question WOULD route — which skills are reachable, which tools are in scope, what the classifier makes of it — and never calls the model. That is genuinely useful and it is not what a tab called Test leads anyone to expect. A real test did exist, but under Skills, as "Test in Owliver" on a capability card. So the Simulated User Query the tab has always shown is now runnable, through the same path that card uses: the existing Owliver panel, scoped to the draft's own agent and disabled skills, so the answer comes from the agent being edited rather than the published one. The diagnostics stay — they answer a different and still useful question. The button is disabled when the agent does not cover the selected page, with the reason in its title, rather than offering a run that would decline. 924/924 checks pass and the production build is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJvibeSc1JYXjatankqM1g
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
</div>
|
||||
{/* 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 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRunLive({
|
||||
question,
|
||||
scope: { contextId, disabledSkills: result.disabled, agent: draftAgent },
|
||||
capability: null,
|
||||
trace: { agentName: fields.name || 'This agent', surface: contextId },
|
||||
})}
|
||||
disabled={!question.trim() || !result.covers}
|
||||
title={result.covers
|
||||
? 'Ask this for real, in the Owliver panel'
|
||||
: 'This agent does not cover the selected page'}
|
||||
className="mt-2 inline-flex items-center gap-1.5 rounded-lg border border-border
|
||||
bg-surface px-3 py-1.5 text-caption font-medium text-ink-2
|
||||
hover:bg-surface-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Play className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
Ask Owliver for real
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -452,6 +452,7 @@ export default function AdminAgentDetail() {
|
||||
)}
|
||||
{view === 'test' && (
|
||||
<AgentTestPanel
|
||||
onRunLive={onTestCapability}
|
||||
fields={fields}
|
||||
dirty={dirty}
|
||||
customSkills={customSkills}
|
||||
|
||||
Reference in New Issue
Block a user