fix(agent): context-aware task input, auto-select active session
- Running/pending: input locked with "agent is working" message - Done: shows "+ Follow up" and "New task" buttons instead of open input - No session: normal new-task input (unchanged UX) - On mount: auto-selects the most recent running/pending session, falls back to latest session — so navigating away and back doesn't lose context and doesn't require manual re-selection Made-with: Cursor
This commit is contained in:
@@ -285,15 +285,26 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
}, []);
|
||||
|
||||
// Load session list
|
||||
// Load session list — auto-select the most recent active or last session
|
||||
useEffect(() => {
|
||||
if (!appName) return;
|
||||
setLoadingSessions(true);
|
||||
fetch(`/api/projects/${projectId}/agent/sessions`)
|
||||
.then(r => r.json())
|
||||
.then(d => { setSessions(d.sessions ?? []); setLoadingSessions(false); })
|
||||
.then(d => {
|
||||
const list: AgentSession[] = d.sessions ?? [];
|
||||
setSessions(list);
|
||||
setLoadingSessions(false);
|
||||
// Auto-select: prefer running/pending, then the most recent
|
||||
if (list.length > 0 && !activeSessionId) {
|
||||
const live = list.find(s => s.status === "running" || s.status === "pending");
|
||||
const pick = live ?? list[0];
|
||||
setActiveSessionId(pick.id);
|
||||
setActiveSession(pick);
|
||||
}
|
||||
})
|
||||
.catch(() => setLoadingSessions(false));
|
||||
}, [projectId, appName]);
|
||||
}, [projectId, appName]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Adaptive polling — 500ms while running, 5s when idle
|
||||
useEffect(() => {
|
||||
@@ -619,14 +630,94 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Task input */}
|
||||
{/* Task input — context-aware */}
|
||||
{(() => {
|
||||
const st = activeSession?.status;
|
||||
|
||||
// Agent is actively working — lock input
|
||||
if (st === "running" || st === "pending") {
|
||||
return (
|
||||
<div style={{ borderTop: "1px solid #e8e4dc", background: "#fff", padding: "12px 16px", flexShrink: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<span style={{ width: 7, height: 7, borderRadius: "50%", background: "#3d5afe", flexShrink: 0, display: "inline-block" }} />
|
||||
<span style={{ fontSize: "0.78rem", color: "#6b6560", fontFamily: "Outfit, sans-serif", flex: 1 }}>
|
||||
Agent is working — wait for it to finish, or stop it above.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Session completed successfully — offer follow-up or new task
|
||||
if (st === "done") {
|
||||
return (
|
||||
<div style={{ borderTop: "1px solid #e8e4dc", background: "#fff", padding: "12px 16px", flexShrink: 0 }}>
|
||||
{showFollowUp ? (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<div style={{ fontSize: "0.72rem", color: "#6b6560", fontFamily: "Outfit, sans-serif" }}>
|
||||
Describe the next thing to build (continues from this session's context):
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
|
||||
<textarea
|
||||
autoFocus
|
||||
value={task}
|
||||
onChange={e => setTask(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { handleRun(); setShowFollowUp(false); }
|
||||
if (e.key === "Escape") { setShowFollowUp(false); setTask(""); }
|
||||
}}
|
||||
placeholder="e.g. Now add email notifications for the same feature…"
|
||||
rows={2}
|
||||
style={{
|
||||
flex: 1, resize: "none", border: "1px solid #e8e4dc", borderRadius: 7,
|
||||
padding: "8px 11px", fontSize: "0.78rem", fontFamily: "Outfit, sans-serif",
|
||||
outline: "none", background: "#faf8f5",
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
<button onClick={() => { handleRun(); setShowFollowUp(false); }} disabled={submitting || !task.trim()}
|
||||
style={{ padding: "8px 14px", background: task.trim() ? "#1a1a1a" : "#e8e4dc", color: task.trim() ? "#fff" : "#a09a90", border: "none", borderRadius: 7, fontSize: "0.75rem", fontWeight: 600, cursor: task.trim() ? "pointer" : "default", fontFamily: "Outfit, sans-serif", whiteSpace: "nowrap" }}>
|
||||
{submitting ? "Starting…" : "Run"}
|
||||
</button>
|
||||
<button onClick={() => { setShowFollowUp(false); setTask(""); }}
|
||||
style={{ padding: "6px 14px", background: "transparent", color: "#a09a90", border: "1px solid #e8e4dc", borderRadius: 7, fontSize: "0.73rem", cursor: "pointer", fontFamily: "Outfit, sans-serif" }}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: "0.63rem", color: "#b5b0a6", fontFamily: "Outfit, sans-serif" }}>
|
||||
⌘↵ to run · Esc to cancel · Starts a new session for this task
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<span style={{ fontSize: "0.75rem", color: "#2e7d32", fontFamily: "Outfit, sans-serif", flex: 1 }}>
|
||||
✓ Done — approve the changes above, or continue building.
|
||||
</span>
|
||||
<button onClick={() => setShowFollowUp(true)}
|
||||
style={{ padding: "7px 14px", background: "#f0ece4", color: "#1a1a1a", border: "1px solid #e8e4dc", borderRadius: 7, fontSize: "0.75rem", fontWeight: 600, cursor: "pointer", fontFamily: "Outfit, sans-serif", whiteSpace: "nowrap" }}>
|
||||
+ Follow up
|
||||
</button>
|
||||
<button onClick={() => { setActiveSession(null); setActiveSessionId(null); setTask(""); }}
|
||||
style={{ padding: "7px 14px", background: "transparent", color: "#a09a90", border: "1px solid #e8e4dc", borderRadius: 7, fontSize: "0.75rem", cursor: "pointer", fontFamily: "Outfit, sans-serif", whiteSpace: "nowrap" }}>
|
||||
New task
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// No active session (or failed/stopped handled above) — new task input
|
||||
if (!activeSession) {
|
||||
return (
|
||||
<div style={{ borderTop: "1px solid #e8e4dc", background: "#fff", padding: "12px 16px", flexShrink: 0 }}>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
|
||||
<textarea
|
||||
value={task}
|
||||
onChange={e => setTask(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleRun(); }}
|
||||
placeholder={`Tell the agent what to build in ${appName || "this app"}…`}
|
||||
placeholder={`Describe what to build in ${appName || "this app"}…`}
|
||||
rows={2}
|
||||
style={{
|
||||
flex: 1, resize: "none", border: "1px solid #e8e4dc", borderRadius: 8,
|
||||
@@ -648,9 +739,14 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ fontSize: "0.65rem", color: "#b5b0a6", marginTop: 5, fontFamily: "Outfit, sans-serif" }}>
|
||||
⌘↵ to start · Each task is a new session · Use "Follow up" on a failed session to continue it
|
||||
⌘↵ to start · The agent works autonomously until done · You approve before anything is committed
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user