@@ -574,8 +574,8 @@ extension AgentViewModel {
574574 // MARK: - Direct Agent Execution (no LLM)
575575
576576 /// Run an agent script directly — skips the LLM entirely.
577- /// Compiles only if the dylib is out of date, then executes .
578- /// Returns true on success, false on error (caller should fall through to LLM) .
577+ /// Opens a fresh tab and kicks off execution without blocking the main tab .
578+ /// Returns true if the agent was found and launched, false if not found .
579579 @discardableResult
580580 func runAgentDirect( name: String , arguments: String = " " , switchToTab: Bool = true ) async -> Bool {
581581 let resolved = await Self . offMain { [ ss = scriptService] in ss. resolveScriptName ( name) }
@@ -588,49 +588,54 @@ extension AgentViewModel {
588588
589589 // Close any existing tab for this agent and open fresh
590590 if let existing = scriptTabs. first ( where: { $0. scriptName == resolved } ) {
591- AuditLog . log ( . agentScript, " runAgentDirect: closing old tab \( existing. id) " )
592591 closeScriptTab ( id: existing. id)
593592 }
594593 let tab = openScriptTab ( scriptName: resolved, selectTab: switchToTab)
595- AuditLog . log ( . agentScript, " runAgentDirect: opened tab \( tab. id) " )
596594
597- // Log on main tab so user sees something
595+ // Log on main tab so user sees something — main tab is now free
598596 appendLog ( " 🏃 \( resolved) ... (see tab) " )
599597 flushLog ( )
598+ isRunning = false
600599
601- // Add to tab's prompt history for up-arrow recall
602- let prompt = arguments. isEmpty ? " run \( resolved) " : " run \( resolved) \( arguments) "
600+ // Fire and forget — run in the tab's own Task, main tab doesn't wait
601+ Task { [ weak self] in
602+ guard let self else { return }
603+ await self . executeAgentInTab ( tab: tab, name: resolved, arguments: arguments, compileCmd: compileCmd)
604+ }
605+ return true
606+ }
607+
608+ /// Execute the agent script inside its tab — called from a detached Task so main tab is free.
609+ private func executeAgentInTab( tab: ScriptTab , name: String , arguments: String , compileCmd: String ) async {
610+
611+ let prompt = arguments. isEmpty ? " run \( name) " : " run \( name) \( arguments) "
603612 tab. addToHistory ( prompt)
604613
605- // Mark tab as running (not LLM — direct execution)
606614 tab. isRunning = true
607615 tab. isLLMRunning = false
608616 tab. isLLMThinking = false
609617 tab. appendLog ( " --- Direct Run --- " )
610618
611619 // Compile only if needed
612- AuditLog . log ( . agentScript, " runAgentDirect: checking dylib " )
613- if await Self . offMain ( { [ ss = scriptService] in !ss. isDylibCurrent ( name: resolved) } ) {
614- tab. appendLog ( " 🦾 Compiling: \( resolved) " )
620+ if await Self . offMain ( { [ ss = scriptService] in !ss. isDylibCurrent ( name: name) } ) {
621+ tab. appendLog ( " 🦾 Compiling: \( name) " )
615622 tab. flush ( )
616- AuditLog . log ( . agentScript, " runAgentDirect: compiling " )
617623 let compileResult = await userService. execute ( command: compileCmd)
618624 if compileResult. status != 0 {
619625 tab. appendLog ( " ❌ Compile error: \n \( compileResult. output) " )
620626 tab. flush ( )
621627 tab. isRunning = false
622- return false
628+ return
623629 }
624630 }
625631
626- AuditLog . log ( . agentScript, " runAgentDirect: executing " )
627- tab. appendLog ( " 🦾 Running: \( resolved) " )
632+ tab. appendLog ( " 🦾 Running: \( name) " )
628633 tab. flush ( )
629- RecentAgentsService . shared. recordRun ( agentName: resolved , arguments: arguments, prompt: " run \( resolved ) \( arguments ) " )
634+ RecentAgentsService . shared. recordRun ( agentName: name , arguments: arguments, prompt: prompt )
630635
631636 let cancelFlag = tab. _cancelFlag
632637 let runResult = await scriptService. loadAndRunScriptViaProcess (
633- name: resolved ,
638+ name: name ,
634639 arguments: arguments,
635640 isCancelled: { cancelFlag. value }
636641 ) { [ weak tab] chunk in
@@ -643,22 +648,18 @@ extension AgentViewModel {
643648 let success = runResult. status == 0
644649 let isUsageOutput = runResult. output. trimmingCharacters ( in: . whitespacesAndNewlines) . hasPrefix ( " Usage: " )
645650 let statusNote = success ? " completed " : ( isUsageOutput ? " usage " : " exit code: \( runResult. status) " )
646- tab. appendLog ( " \( resolved ) \( statusNote) " )
651+ tab. appendLog ( " \( name ) \( statusNote) " )
647652 tab. flush ( )
648653 tab. isRunning = false
649654
650- // Update agent menu status based on outcome
651655 let wasCancelled = tab. isCancelled || runResult. status == 15
652656 if wasCancelled {
653- RecentAgentsService . shared. updateStatus ( agentName: resolved , arguments: arguments, status: . cancelled)
657+ RecentAgentsService . shared. updateStatus ( agentName: name , arguments: arguments, status: . cancelled)
654658 } else if isUsageOutput || !success {
655- RecentAgentsService . shared. updateStatus ( agentName: resolved , arguments: arguments, status: . failed)
659+ RecentAgentsService . shared. updateStatus ( agentName: name , arguments: arguments, status: . failed)
656660 } else {
657- RecentAgentsService . shared. updateStatus ( agentName: resolved , arguments: arguments, status: . success)
661+ RecentAgentsService . shared. updateStatus ( agentName: name , arguments: arguments, status: . success)
658662 }
659-
660- // User-initiated run — output is visible in tab, no alert needed
661- return success || isUsageOutput
662663 }
663664}
664665
0 commit comments