Merge pull request #5751 from aden-hive/load-new-session-from-home
Fix new session from home and add email reply agent template
This commit is contained in:
@@ -280,10 +280,17 @@ export default function Workspace() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const rawAgent = searchParams.get("agent") || "new-agent";
|
||||
const initialAgent = rawAgent;
|
||||
const hasExplicitAgent = searchParams.has("agent");
|
||||
const initialPrompt = searchParams.get("prompt") || "";
|
||||
|
||||
// When submitting a new prompt from home for "new-agent", use a unique key
|
||||
// so each prompt gets its own tab instead of overwriting the previous one.
|
||||
const [initialAgent] = useState(() =>
|
||||
initialPrompt && hasExplicitAgent && rawAgent === "new-agent"
|
||||
? `new-agent-${makeId()}`
|
||||
: rawAgent
|
||||
);
|
||||
|
||||
// Sessions grouped by agent type — restore from localStorage if available
|
||||
const [sessionsByAgent, setSessionsByAgent] = useState<Record<string, Session[]>>(() => {
|
||||
const persisted = loadPersistedTabs();
|
||||
@@ -315,11 +322,14 @@ export default function Workspace() {
|
||||
|
||||
// If the user submitted a new prompt from the home page, always create
|
||||
// a fresh session so the prompt isn't lost into an existing session.
|
||||
// initialAgent is already a unique key (e.g. "new-agent-abc123") when
|
||||
// coming from home, so the new tab won't overwrite existing ones.
|
||||
if (initialPrompt && hasExplicitAgent) {
|
||||
const newSession = initialAgent === "new-agent"
|
||||
? createSession("new-agent", "New Agent")
|
||||
: createSession(initialAgent, formatAgentDisplayName(initialAgent));
|
||||
initial[initialAgent] = [...(initial[initialAgent] || []), newSession];
|
||||
const label = initialAgent.startsWith("new-agent")
|
||||
? "New Agent"
|
||||
: formatAgentDisplayName(initialAgent);
|
||||
const newSession = createSession(initialAgent, label);
|
||||
initial[initialAgent] = [newSession];
|
||||
return initial;
|
||||
}
|
||||
|
||||
@@ -342,14 +352,8 @@ export default function Workspace() {
|
||||
if (persisted) {
|
||||
const restored = { ...persisted.activeSessionByAgent };
|
||||
const urlSessions = sessionsByAgent[initialAgent];
|
||||
if (urlSessions?.length) {
|
||||
// When a prompt was submitted from home, activate the newly created
|
||||
// session (last in array) instead of the previously active one.
|
||||
if (initialPrompt && hasExplicitAgent) {
|
||||
restored[initialAgent] = urlSessions[urlSessions.length - 1].id;
|
||||
} else if (!restored[initialAgent]) {
|
||||
restored[initialAgent] = urlSessions[0].id;
|
||||
}
|
||||
if (urlSessions?.length && !restored[initialAgent]) {
|
||||
restored[initialAgent] = urlSessions[0].id;
|
||||
}
|
||||
return restored;
|
||||
}
|
||||
@@ -489,7 +493,7 @@ export default function Workspace() {
|
||||
// --- Agent loading: loadAgentForType ---
|
||||
const loadingRef = useRef(new Set<string>());
|
||||
const loadAgentForType = useCallback(async (agentType: string) => {
|
||||
if (agentType === "new-agent") {
|
||||
if (agentType === "new-agent" || agentType.startsWith("new-agent-")) {
|
||||
// Create a queen-only session (no worker) for agent building
|
||||
updateAgentState(agentType, { loading: true, error: null, ready: false, sessionId: null });
|
||||
try {
|
||||
@@ -1948,7 +1952,7 @@ export default function Workspace() {
|
||||
<CredentialsModal
|
||||
agentType={activeWorker}
|
||||
agentLabel={activeWorkerLabel}
|
||||
agentPath={credentialAgentPath || (activeWorker !== "new-agent" ? activeWorker : undefined)}
|
||||
agentPath={credentialAgentPath || (!activeWorker.startsWith("new-agent") ? activeWorker : undefined)}
|
||||
open={credentialsOpen}
|
||||
onClose={() => { setCredentialsOpen(false); setCredentialAgentPath(null); setDismissedBanner(null); }}
|
||||
credentials={activeSession?.credentials || []}
|
||||
|
||||
@@ -90,7 +90,7 @@ edges = [
|
||||
source="confirm-draft",
|
||||
target="intake",
|
||||
condition=EdgeCondition.CONDITIONAL,
|
||||
condition_expr="batch_complete == True",
|
||||
condition_expr="batch_complete == True and send_started == True and send_count >= 1 and sent_message_ids is not None and len(sent_message_ids) >= 1",
|
||||
priority=1,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -83,8 +83,8 @@ confirm_draft_node = NodeSpec(
|
||||
client_facing=True,
|
||||
max_node_visits=0,
|
||||
input_keys=["email_list", "filter_criteria"],
|
||||
output_keys=["batch_complete", "restart"],
|
||||
nullable_output_keys=["batch_complete", "restart"],
|
||||
output_keys=["batch_complete", "restart", "send_started", "send_count", "sent_message_ids", "send_failures"],
|
||||
nullable_output_keys=["batch_complete", "restart", "send_started", "send_count", "sent_message_ids", "send_failures"],
|
||||
success_criteria="User confirmed recipients and personalized replies sent for each.",
|
||||
system_prompt="""\
|
||||
You are a Gmail reply assistant. Present emails for confirmation, then send personalized replies.
|
||||
@@ -99,14 +99,22 @@ You are a Gmail reply assistant. Present emails for confirmation, then send pers
|
||||
**STEP 2 — Handle user response:**
|
||||
|
||||
If user CONFIRMS (says yes, go ahead, sounds good, etc.):
|
||||
For EACH email in email_list:
|
||||
1. Read the subject and snippet
|
||||
2. Use tone_guidance from filter_criteria + any user-specified preferences
|
||||
3. Call gmail_reply_email with:
|
||||
1. Immediately call set_output("send_started", True) before any send tools.
|
||||
2. For EACH email in email_list, call gmail_reply_email with:
|
||||
- message_id: the email's message_id
|
||||
- html: personalized 2-4 sentence reply based on email context
|
||||
(The tool automatically handles recipient, subject, and threading)
|
||||
4. After all replies sent, call: set_output("batch_complete", True)
|
||||
- html: personalized 2-4 sentence reply based on email context, using tone_guidance from filter_criteria and any new user preferences.
|
||||
3. Track send results during this run:
|
||||
- send_count: number of successful gmail_reply_email calls
|
||||
- sent_message_ids: list of message_ids successfully replied to
|
||||
- send_failures: list of {"message_id": "...", "error": "..."} for failed sends
|
||||
4. REQUIRED completion gate:
|
||||
- You MUST NOT set batch_complete=True unless send_started is True AND send_count >= 1 AND sent_message_ids is non-empty.
|
||||
- If no sends succeeded, do NOT set batch_complete=True. Instead explain what failed and ask user whether to retry or restart.
|
||||
5. After successful sends, call set_output in a separate turn:
|
||||
- set_output("send_count", <int>)
|
||||
- set_output("sent_message_ids", <list>)
|
||||
- set_output("send_failures", <list>)
|
||||
- set_output("batch_complete", True)
|
||||
|
||||
If user wants to CHANGE LOGIC/FILTER (says change filter, different criteria, not these emails, wrong emails, etc.):
|
||||
1. Acknowledge their request
|
||||
|
||||
@@ -30,7 +30,10 @@ class TestAgentStructure:
|
||||
assert len(confirm_edges) == 2
|
||||
edge_conditions = {e.condition_expr for e in confirm_edges}
|
||||
assert "restart == True" in edge_conditions
|
||||
assert "batch_complete == True" in edge_conditions
|
||||
assert (
|
||||
"batch_complete == True and send_started == True and send_count >= 1 and sent_message_ids is not None and len(sent_message_ids) >= 1"
|
||||
in edge_conditions
|
||||
)
|
||||
|
||||
def test_entry_points(self, agent_module):
|
||||
"""Entry points configured."""
|
||||
|
||||
Reference in New Issue
Block a user