Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/routes/tangent/hooks/useRunAutomatedResearch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { useMutation } from "@tanstack/react-query";

import useToastNotification from "@/hooks/useToastNotification";
import type { ScenarioEntry } from "@/routes/tangent/idb/tangentDb";
import {
saveScenario,
type ScenarioEntry,
} from "@/routes/tangent/idb/tangentDb";
import {
buildOpencodeSessionUrl,
createOpencodeSession,
resolveInstanceId,
sendAutoresearchMessage,
Expand All @@ -25,6 +29,13 @@ export function useRunAutomatedResearch() {
);
const prompt = buildAutoresearchPrompt(scenario);
await sendAutoresearchMessage(instanceId, sessionId, prompt);

const url = buildOpencodeSessionUrl(instanceId, sessionId);
await saveScenario({
...scenario,
research: { instanceId, sessionId, url, startedAt: Date.now() },
updatedAt: Date.now(),
});
},
onSuccess: () => {
notify("Automated research started", "success");
Expand Down
12 changes: 12 additions & 0 deletions src/routes/tangent/idb/tangentDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ interface ScenarioPlan {
failure_playbook?: unknown[];
}

/**
* Reference to the OpenCode agent session created when automated research is
* started for a scenario. Persisted so the UI can surface a follow link.
*/
interface ScenarioResearch {
instanceId: string;
sessionId: string;
url: string;
startedAt: number;
}

export interface ScenarioEntry {
id: string;
run: ScenarioRunRef;
Expand All @@ -50,6 +61,7 @@ export interface ScenarioEntry {
/** Only the ideas the user selected when building the scenario. */
ideas: ScenarioIdea[];
plan: ScenarioPlan;
research?: ScenarioResearch;
createdAt: number;
updatedAt: number;
}
Expand Down
14 changes: 14 additions & 0 deletions src/routes/tangent/services/autoresearchOpencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
createInstanceApiTangentInstancesPost,
listInstancesApiTangentInstancesGet,
} from "@/api/sdk.gen";
import { API_URL } from "@/utils/constants";

/**
* Workspace directory the OpenCode agent runs in. OpenCode scopes sessions to
Expand Down Expand Up @@ -96,6 +97,19 @@ export async function createOpencodeSession(
return data.id;
}

/**
* Build the OpenCode web UI URL for following a created session, mirroring the
* structure produced by the backend redirect. The base resolves to the
* configured backend (or the current origin in relative-path mode).
*/
export function buildOpencodeSessionUrl(
instanceId: string,
sessionId: string,
): string {
const base = API_URL || window.location.origin;
return `${base}/api/tangent/instances/${instanceId}/opencode/app/default/Lw/session/${sessionId}`;
}

/**
* Send a prompt to an OpenCode session without waiting for the agent to finish
* (fire-and-forget via `prompt_async`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Link } from "@/components/ui/link";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Table,
Expand Down Expand Up @@ -123,17 +124,28 @@ function ScenarioRow({
</Text>
</TableCell>
<TableCell>
<Button
size="xs"
variant="outline"
disabled={isResearchPending}
onClick={(event) => {
event.stopPropagation();
onRunResearch();
}}
>
Run automated research
</Button>
{scenario.research ? (
<Link
external
href={scenario.research.url}
size="sm"
onClick={(event) => event.stopPropagation()}
>
Open research session
</Link>
) : (
<Button
size="xs"
variant="outline"
disabled={isResearchPending}
onClick={(event) => {
event.stopPropagation();
onRunResearch();
}}
>
Run automated research
</Button>
)}
</TableCell>
</TableRow>
);
Expand Down Expand Up @@ -180,13 +192,19 @@ function ScenarioDetail({
{scenario.plan.name}
</Text>
</InlineStack>
<Button
size="sm"
disabled={isResearchPending}
onClick={onRunResearch}
>
Run automated research
</Button>
{scenario.research ? (
<Link external href={scenario.research.url} size="sm">
Open research session
</Link>
) : (
<Button
size="sm"
disabled={isResearchPending}
onClick={onRunResearch}
>
Run automated research
</Button>
)}
<Text as="p" size="sm" tone="subdued">
{scenario.rationale}
</Text>
Expand Down Expand Up @@ -219,8 +237,11 @@ export function MlExperimentPlannerContent({
selectedScenarioId,
}: MlExperimentPlannerContentProps) {
const { scenarios } = useRunScenarios(runId);
const { mutate: runResearch, isPending: isResearchPending } =
useRunAutomatedResearch();
const {
mutate: runResearch,
isPending: isResearchPending,
variables: researchVariables,
} = useRunAutomatedResearch();
const [selectedId, setSelectedId] = useState<string | null>(
selectedScenarioId ?? null,
);
Expand Down Expand Up @@ -250,7 +271,9 @@ export function MlExperimentPlannerContent({
scenario={selectedScenario}
onBack={() => setSelectedId(null)}
onRunResearch={() => runResearch(selectedScenario)}
isResearchPending={isResearchPending}
isResearchPending={
isResearchPending && researchVariables?.id === selectedScenario.id
}
/>
</BlockStack>
);
Expand All @@ -276,7 +299,9 @@ export function MlExperimentPlannerContent({
scenario={scenario}
onSelect={() => setSelectedId(scenario.id)}
onRunResearch={() => runResearch(scenario)}
isResearchPending={isResearchPending}
isResearchPending={
isResearchPending && researchVariables?.id === scenario.id
}
/>
))}
</TableBody>
Expand Down
Loading