mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
fix: Run Now agent-run timing + non-404 webview placeholder
Two independent fixes that both blocked the "install → Run Now → see the Site tab render" loop. 1. CronViewModel.runNow stopped blocking on `cron tick`. Previously the UI waited up to 60 s on the tick before deciding whether the job succeeded, so any agent run that did real work (an LLM call + a few HTTP GETs + a file write = easily 90 s+) surfaced a false "Run failed" toast while the job kept running in the background. Dashboard updates landed minutes later, confusing the user. New shape: show "Agent started — dashboard will update when it finishes" the instant `cron run` queues the job, then call `cron tick` with a 300 s timeout to force execution. Tick failures are logged but don't overwrite the started-toast — HermesFileWatcher picks up the dashboard.json rewrite automatically when the agent finishes. 2. site-status-checker's webview widget pointed at `github.com/awizemann/scarf/tree/main/templates/awizemann/...`. The templates/ path only exists on project-sharing, not main, so GitHub returned 404 in the Site tab until the first cron run replaced the URL with the user's configured site. Switched the placeholder to `awizemann.github.io/scarf/` which always renders. Bundle + catalog rebuilt against the updated dashboard.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,23 +73,46 @@ final class CronViewModel {
|
||||
// with `hermes cron tick` which runs all due jobs once and
|
||||
// exits. Redundant-but-harmless when the gateway is running;
|
||||
// the actual trigger when it isn't.
|
||||
//
|
||||
// Feedback model: show a "Agent started" toast as soon as
|
||||
// `cron run` succeeds, WITHOUT waiting for `cron tick` to
|
||||
// return. Agent jobs routinely run past a minute (network IO +
|
||||
// an LLM call + a file rewrite), and earlier versions with a
|
||||
// 60s tick timeout surfaced a misleading "Run failed" toast
|
||||
// every time while the job kept running in the background.
|
||||
// The app's HermesFileWatcher picks up the dashboard.json
|
||||
// rewrite that the agent lands at the end — that's what the
|
||||
// user actually watches for, not this toast.
|
||||
let svc = fileService
|
||||
let jobID = job.id
|
||||
Task.detached { [weak self] in
|
||||
let runResult = svc.runHermesCLI(args: ["cron", "run", jobID], timeout: 30)
|
||||
// Give `cron run` a moment to register the queue entry
|
||||
// before forcing the tick. A few hundred ms is enough;
|
||||
// longer only delays the user-visible feedback.
|
||||
try? await Task.sleep(for: .milliseconds(250))
|
||||
let tickResult = svc.runHermesCLI(args: ["cron", "tick"], timeout: 60)
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self else { return }
|
||||
if runResult.exitCode == 0 && tickResult.exitCode == 0 {
|
||||
self.message = "Job executed (see Output panel for details)"
|
||||
} else {
|
||||
let errOutput = runResult.exitCode != 0 ? runResult.output : tickResult.output
|
||||
self.message = "Run failed: \(errOutput.prefix(200))"
|
||||
self.logger.warning("cron runNow failed: run=\(runResult.exitCode), tick=\(tickResult.exitCode) output=\(errOutput)")
|
||||
if runResult.exitCode != 0 {
|
||||
self.message = "Run failed to queue: \(runResult.output.prefix(200))"
|
||||
self.logger.warning("cron run failed: \(runResult.output)")
|
||||
self.load()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
||||
self?.message = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
self.message = "Agent started — dashboard will update when it finishes"
|
||||
self.load()
|
||||
}
|
||||
// `cron run` is queued; now force the tick. The 300s
|
||||
// timeout catches truly stuck processes without killing
|
||||
// the long-but-valid agent case that blew up the 60s
|
||||
// version. A timeout here is survivable — the Hermes
|
||||
// scheduler re-runs due jobs on its own cadence — so we
|
||||
// log but don't surface it as a failure toast.
|
||||
try? await Task.sleep(for: .milliseconds(250))
|
||||
let tickResult = svc.runHermesCLI(args: ["cron", "tick"], timeout: 300)
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self else { return }
|
||||
if tickResult.exitCode != 0 {
|
||||
self.logger.warning("cron tick exited non-zero (job may still complete via scheduler): \(tickResult.output)")
|
||||
}
|
||||
self.load()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
||||
|
||||
@@ -889,6 +889,10 @@
|
||||
},
|
||||
"••••••••••" : {
|
||||
|
||||
},
|
||||
"+ %lld more…" : {
|
||||
"comment" : "A button that shows the number of files that were left behind by the template uninstaller.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"<%@>" : {
|
||||
|
||||
@@ -6664,6 +6668,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete %@ from Finder if you don't need these files anymore." : {
|
||||
"comment" : "A note that lets the user delete",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Delete %@?" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -15447,6 +15455,9 @@
|
||||
},
|
||||
"Project directory will also be removed (nothing user-owned left inside)." : {
|
||||
|
||||
},
|
||||
"Project folder kept" : {
|
||||
|
||||
},
|
||||
"Project Name" : {
|
||||
"localizations" : {
|
||||
@@ -16511,6 +16522,10 @@
|
||||
"comment" : "A label that instructs the user to remove a project from Scarf's list of installed projects.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remove %@ from Scarf's project list (files are kept on disk)" : {
|
||||
"comment" : "A confirmation dialog that",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remove %@?" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -16591,8 +16606,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Remove from Scarf" : {
|
||||
"comment" : "A context menu option to remove a project from Scarf.",
|
||||
"Remove from List" : {
|
||||
"comment" : "A confirmation dialog that asks whether a user is sure they want to remove a project from Scarf's list.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remove from List (keep files)…" : {
|
||||
"comment" : "A button that removes a project from Scarf's list, but not from disk.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remove from Scarf's project list?" : {
|
||||
"comment" : "Title of a dialog that asks the user to confirm removing a project from Scarf's project list.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remove the entire namespace dir recursively" : {
|
||||
@@ -21572,6 +21595,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"These files weren't installed by the template (the agent or you created them after install), so Scarf left them in place along with the folder itself." : {
|
||||
"comment" : "A description of the files Scarf left in place when uninstalling a template.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"These list fields must be edited directly in config.yaml." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -22602,8 +22629,8 @@
|
||||
"comment" : "A button that uninstalls a template.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Uninstall Template…" : {
|
||||
"comment" : "A contextual menu item that uninstalls a template.",
|
||||
"Uninstall Template (remove installed files)…" : {
|
||||
"comment" : "A button that removes a project's files from the system.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Unknown: %@" : {
|
||||
|
||||
Reference in New Issue
Block a user