diff --git a/src/agent/agents/subagents/tangentResearcher.ts b/src/agent/agents/subagents/tangentResearcher.ts
index 43c902c6b..08ec7a963 100644
--- a/src/agent/agents/subagents/tangentResearcher.ts
+++ b/src/agent/agents/subagents/tangentResearcher.ts
@@ -14,7 +14,7 @@
*/
import { Agent } from "@openai/agents";
-import { requireOrchestratorModel } from "../../config";
+import { getAgentModelConfig } from "../../config";
import { attachObservabilityHooks } from "../../middleware/observability";
import tangentResearcherPrompt from "../../prompts/tangentResearcher.md?raw";
import type { AgentSession, RecentPipelineRun } from "../../session";
@@ -44,7 +44,7 @@ export function createTangentResearcherAgent(session: AgentSession): Agent {
hyperparameter-tuning and experiment ideas. Read-only — cannot edit the pipeline or submit runs.`,
instructions,
tools: [runTools.getRunStatus, csom.getPipelineState],
- model: requireOrchestratorModel(),
+ ...getAgentModelConfig(session.aiConfig),
modelSettings: { reasoning: { effort: "high" } },
});
attachObservabilityHooks(agent, session.emitStatus);
diff --git a/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx b/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx
index 0532b7f05..71a89824e 100644
--- a/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx
+++ b/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx
@@ -13,14 +13,18 @@ import { CopyText } from "@/components/shared/CopyText/CopyText";
import PipelineIO from "@/components/shared/Execution/PipelineIO";
import { InfoBox } from "@/components/shared/InfoBox";
import { LoadingScreen } from "@/components/shared/LoadingScreen";
+import { useFlagValue } from "@/components/shared/Settings/useFlags";
import { StatusBar } from "@/components/shared/Status";
import { TagList } from "@/components/shared/Tags/TagList";
+import { Button } from "@/components/ui/button";
+import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Paragraph, Text } from "@/components/ui/typography";
import { useUserDetails } from "@/hooks/useUserDetails";
import type { ComponentSpec } from "@/models/componentSpec";
import { useBackend } from "@/providers/BackendProvider";
import { useExecutionData } from "@/providers/ExecutionDataProvider";
+import { useStartOptimizationChat } from "@/routes/v2/pages/RunView/hooks/useStartOptimizationChat";
import { useSpec } from "@/routes/v2/shared/providers/SpecContext";
import {
PIPELINE_NOTES_ANNOTATION,
@@ -121,6 +125,8 @@ function RunDetailsContentLoaded({
{spec.name ?? "Unnamed Pipeline"}
+
+
{metadata && }
{spec.description && (
@@ -155,6 +161,20 @@ function RunDetailsContentLoaded({
);
}
+function OptimizationButton() {
+ const aiEnabled = useFlagValue("ai-assistant");
+ const startOptimizationChat = useStartOptimizationChat();
+
+ if (!aiEnabled) return null;
+
+ return (
+
+ );
+}
+
function RunInfoSection({ metadata }: { metadata: PipelineRunResponse }) {
return (
{
+ if (!pendingPrompt || !thread) return;
+ const prompt = aiChat.consumePendingPrompt();
+ if (prompt) handleSend(prompt);
+ }, [pendingPrompt, thread, aiChat]);
+
if (!isAiConfigured) {
return ;
}
diff --git a/src/routes/v2/shared/components/AiChat/aiChatStore.ts b/src/routes/v2/shared/components/AiChat/aiChatStore.ts
index b84be416a..69f5936ac 100644
--- a/src/routes/v2/shared/components/AiChat/aiChatStore.ts
+++ b/src/routes/v2/shared/components/AiChat/aiChatStore.ts
@@ -26,6 +26,12 @@ export interface AiChatStoreConfig {
export class AiChatStore {
@observable.shallow accessor threads: AgentThread[] = [];
@observable accessor activeThreadId: string | null = null;
+ /**
+ * Prompt queued by a consumer (e.g. an external "ask the assistant"
+ * button) to be sent on the active thread. {@link AiChatContent} owns
+ * the tool bridge, so it observes this and dispatches the send.
+ */
+ @observable accessor pendingPrompt: string | null = null;
constructor(private readonly config: AiChatStoreConfig) {
makeObservable(this);
@@ -62,11 +68,30 @@ export class AiChatStore {
return thread;
}
+ /**
+ * Starts a fresh thread and queues a prompt to be sent on it. The
+ * dispatch happens in {@link AiChatContent}, which holds the tool
+ * bridge required to fulfil tool calls.
+ */
+ @action startThreadWithPrompt(prompt: string): AgentThread {
+ const thread = this.newThread();
+ this.pendingPrompt = prompt;
+ return thread;
+ }
+
+ /** Returns and clears the queued prompt, guarding against double-sends. */
+ @action consumePendingPrompt(): string | null {
+ const prompt = this.pendingPrompt;
+ this.pendingPrompt = null;
+ return prompt;
+ }
+
@action disposeAll() {
for (const thread of this.threads) {
thread.dispose();
}
this.threads = [];
this.activeThreadId = null;
+ this.pendingPrompt = null;
}
}