Skip to content

Commit 8dafe4f

Browse files
committed
Main tab doesn't wait for agent scripts — fire and forget into script tab
1 parent 146bbd0 commit 8dafe4f

1 file changed

Lines changed: 26 additions & 25 deletions

File tree

AgentXcode/Agent/AgentViewModel/AgentViewModel+TaskUtilities.swift

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)