feat(sagi): spawn a real claude session for Sagi's Ask handoff

Unmatched Ask questions now adopt a dedicated "Sagi" lane (POST
/api/lanes/ensure, cwd defaults to $HOME) and spawn a tmux-backed claude
run there instead of just prefilling the Run page composer.
This commit is contained in:
2026-08-24 09:39:28 +07:00
parent 510f015fdf
commit b1bca07c47
6 changed files with 99 additions and 26 deletions
+26 -5
View File
@@ -78,6 +78,7 @@ import { useSagiBrain } from "./useSagiBrain";
import { useSagiPosition, SAGI_SIZE } from "./useSagiPosition";
import { matchIntent } from "./intents";
import { sagiPrefs } from "./prefs";
import { api } from "../../lib/api";
import "./sagi.css";
const FLYOUT_GAP = 10; // px between avatar and flyout
@@ -196,11 +197,31 @@ export function Sagi() {
(query: string): string | null => {
const result = matchIntent(query, brain.status);
if (result.kind === "answer") return result.text;
// Handoff: spawn a real claude via the existing Run page. `autostart=1`
// tells Run to fire the prompt automatically once it's prefilled, so the
// question is actually sent instead of just dropped into the composer.
navigate(`/run?prompt=${encodeURIComponent(result.prompt)}&autostart=1`);
setOpen(false);
// Handoff: ensure the "Sagi" lane exists for this dashboard, spawn a
// fresh claude session in it carrying the user's prompt as its first
// message, then jump to the Run page so the live pane is observable.
// Fire-and-forget — the panel already clears its composer on null and
// closes; errors surface as an answer instead of silently swallowing.
const prompt = result.prompt;
brain.setThinking(true);
(async () => {
try {
const { lane } = await api.lanes.ensure({ title: "Sagi" });
await api.run.start({
laneId: lane.id,
cwd: lane.cwd,
initialPrompt: prompt,
permissionMode: "acceptEdits",
});
navigate(`/run?laneId=${lane.id}&autostart=1`);
setOpen(false);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
brain.setBubble(`couldn't start that session: ${msg}`);
} finally {
brain.setThinking(false);
}
})();
return null;
},
[brain, navigate]