diff --git a/agent/agent.go b/agent/agent.go index ad47efaf..b7472f40 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1019,6 +1019,24 @@ func shortcutToolCalls(prompt string) []shortcutToolCall { "weather": {{Tool: "weather_forecast", Args: map[string]any{"lat": 51.5074, "lon": -0.1278}}}, "reminder": {{Tool: "reminder", Args: map[string]any{}}}, "apps": {{Tool: "apps_search", Args: map[string]any{}}}, + "mail": {{Tool: "mail_read", Args: map[string]any{}}}, + // Personal queries + "do i have mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "do i have unread mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "do i have email": {{Tool: "mail_read", Args: map[string]any{}}}, + "check my mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "check my email": {{Tool: "mail_read", Args: map[string]any{}}}, + "any new mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "any new email": {{Tool: "mail_read", Args: map[string]any{}}}, + "any mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "unread mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "my mail": {{Tool: "mail_read", Args: map[string]any{}}}, + "btc price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "bitcoin price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "eth price": {{Tool: "markets", Args: map[string]any{"category": "crypto"}}}, + "what's happening": {{Tool: "news", Args: map[string]any{}}}, + "what's happening?": {{Tool: "news", Args: map[string]any{}}}, + "today's news": {{Tool: "news", Args: map[string]any{}}}, // Starter pill phrases "give me a summary of today's top news": {{Tool: "news", Args: map[string]any{}}}, "what's in the news?": {{Tool: "news", Args: map[string]any{}}}, diff --git a/agent/run.go b/agent/run.go index bd61f4d5..726273f1 100644 --- a/agent/run.go +++ b/agent/run.go @@ -133,10 +133,19 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { _ = acc // authenticated // Step 1: Plan + userCtx := "" + if UserContextFunc != nil { + userCtx = UserContextFunc(acc.ID) + } + planSystem := "You are an AI agent. Given a user question, output ONLY a JSON array of tool calls.\n\n" + + agentToolsDesc + + "\n\nOutput format: [{\"tool\":\"tool_name\",\"args\":{}}]\nUse at most 5 tool calls. Output [] if no tools needed." + + "\n\nIMPORTANT: For personal questions like 'do I have mail', 'what's the weather', 'news today', 'btc price' — ALWAYS use the appropriate tool. Never say you can't access something. You have tools for everything." + if userCtx != "" { + planSystem += "\n\nUser context:\n" + userCtx + } planResult, err := ai.Ask(&ai.Prompt{ - System: "You are an AI agent. Given a user question, output ONLY a JSON array of tool calls.\n\n" + - agentToolsDesc + - "\n\nOutput format: [{\"tool\":\"tool_name\",\"args\":{}}]\nUse at most 5 tool calls. Output [] if no tools needed.", + System: planSystem, Question: req.Prompt, Priority: ai.PriorityHigh, Provider: model.Provider, @@ -186,12 +195,9 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { // Step 3: Synthesise with user context. today := time.Now().UTC().Format("Monday, 2 January 2006 (UTC)") - userCtx := "" - if UserContextFunc != nil { - userCtx = UserContextFunc(acc.ID) - } synthSystem := "You are Micro, a personal AI assistant. Today is " + today + ". " + - "Answer concisely using the tool results below. Use markdown." + "Answer concisely using the tool results and user context below. Use markdown. " + + "If the user context already contains the answer (e.g. unread mail count), use it directly." if userCtx != "" { synthSystem += "\n\nUser context:\n" + userCtx } diff --git a/home/home.go b/home/home.go index 34eb4ea1..a22e917c 100644 --- a/home/home.go +++ b/home/home.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "sort" "strings" "sync" @@ -20,7 +21,6 @@ import ( "mu/mail" "mu/news" "mu/social" - "mu/stream" "mu/markets" "mu/reminder" "mu/video" @@ -389,7 +389,11 @@ func Handler(w http.ResponseWriter, r *http.Request) { } inviteHTML = fmt.Sprintf(`%s`, link, label) } - dateLine.WriteString(fmt.Sprintf(`
%s%s
`, now.Format("Monday, 2 January 2006"), inviteHTML)) + gearHTML := "" + if viewerAcc != nil { + gearHTML = ` ` + } + dateLine.WriteString(fmt.Sprintf(`
%s%s%s
`, now.Format("Monday, 2 January 2006"), inviteHTML, gearHTML)) // Inline weather: reads cached summary, and refreshes it in the // background if stale (>1 hour). This runs independently of the // weather card — even if the card is hidden, the date-line temp @@ -443,9 +447,8 @@ function fetchW(la,lo){ // ── Cards (always visible) ── b.WriteString(`
`) - // AI prompt — the primary interface. First thing on screen. + // AI prompt — submits to agent page. No inline response. if viewerID != "" { - // Build contextual suggestions based on user state. var suggestions []string if unread := mail.GetUnreadCount(viewerID); unread > 0 { suggestions = append(suggestions, fmt.Sprintf("%d unread email(s)", unread)) @@ -458,32 +461,21 @@ function fetchW(la,lo){ var suggestHTML string for _, s := range suggestions { - suggestHTML += fmt.Sprintf(``, htmlEsc(s)) + suggestHTML += fmt.Sprintf(`%s`, htmlEsc(url.QueryEscape(s)), htmlEsc(s)) } b.WriteString(fmt.Sprintf(`
-
-
%s
-
- -
- + +
-
`, suggestHTML, stream.MaxContentLength)) - b.WriteString(consoleScript) +
%s
+
`, suggestHTML)) } - // Date + browse section — cards are secondary to the AI prompt. + // Date + cards b.WriteString(dateHTML) - b.WriteString(`
`) - b.WriteString(`Browse`) - b.WriteString(`
`) - if viewerAcc != nil { - b.WriteString(` `) - } - b.WriteString(`
`) // Inline card preferences panel if viewerAcc != nil { @@ -732,103 +724,6 @@ func htmlEsc(s string) string { // suggestions, and typing indicator. // consoleScript — AI prompt with flip layout (input moves below response), // persistent last response, typing dots, suggestion pills. -const consoleScript = `` const statusCardScript = `