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;
|
if (el) el.scrollTop = el.scrollHeight;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Load session list
|
// Load session list — auto-select the most recent active or last session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appName) return;
|
if (!appName) return;
|
||||||
setLoadingSessions(true);
|
setLoadingSessions(true);
|
||||||
fetch(`/api/projects/${projectId}/agent/sessions`)
|
fetch(`/api/projects/${projectId}/agent/sessions`)
|
||||||
.then(r => r.json())
|
.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));
|
.catch(() => setLoadingSessions(false));
|
||||||
}, [projectId, appName]);
|
}, [projectId, appName]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
// Adaptive polling — 500ms while running, 5s when idle
|
// Adaptive polling — 500ms while running, 5s when idle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -619,14 +630,94 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName
|
|||||||
</div>
|
</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={{ borderTop: "1px solid #e8e4dc", background: "#fff", padding: "12px 16px", flexShrink: 0 }}>
|
||||||
<div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
|
<div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
|
||||||
<textarea
|
<textarea
|
||||||
value={task}
|
value={task}
|
||||||
onChange={e => setTask(e.target.value)}
|
onChange={e => setTask(e.target.value)}
|
||||||
onKeyDown={e => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleRun(); }}
|
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}
|
rows={2}
|
||||||
style={{
|
style={{
|
||||||
flex: 1, resize: "none", border: "1px solid #e8e4dc", borderRadius: 8,
|
flex: 1, resize: "none", border: "1px solid #e8e4dc", borderRadius: 8,
|
||||||
@@ -648,9 +739,14 @@ function AgentMode({ projectId, appName, appPath }: { projectId: string; appName
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: "0.65rem", color: "#b5b0a6", marginTop: 5, fontFamily: "Outfit, sans-serif" }}>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user