feat(web): DB-backed Jobs tab + scheduler-dispatched local jobs (#436)

* feat(web): DB-backed Jobs tab, scheduler-dispatched local jobs, remove active-jobs-bar

- Remove active-jobs-bar UI element (HTML, CSS, JS polling)
- Move job handlers from server.rs to handlers/jobs.rs
- Remove user_id scoping (single-user gateway)
- Add list_agent_jobs() and agent_job_summary() to Database trait
  (both postgres and libsql backends) for non-sandbox job visibility
- Wire SchedulerSlot into CreateJobTool so execute_local dispatches
  via scheduler (persists to DB + spawns worker) instead of creating
  phantom ContextManager-only jobs
- Update /status and /list slash commands to read from DB for
  consistency with Jobs tab
- Fix worker mark_completed: skip if already terminal or stuck
- Add agent job cancel via DB update in both web handler and slash cmd
- Add Stuck → Completed guard with tracing in worker completion path

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* fix: address PR review comments

- Log warning when get_context fails in worker completion path
- Extract duplicated status-counting logic into AgentJobSummary::add_count()
  helper, used by both postgres and libsql backends

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Nick Pismenkov <[email protected]>
This commit is contained in:
Henry Park
2026-02-28 19:58:07 -08:00
committed by GitHub
co-authored by Claude Sonnet 4.6 Nick Pismenkov
parent afb49597ac
commit 6481448d50
14 changed files with 508 additions and 605 deletions
+10
View File
@@ -456,9 +456,16 @@ async fn async_main() -> anyhow::Result<()> {
let session_manager =
Arc::new(ironclaw::agent::SessionManager::new().with_hooks(components.hooks.clone()));
// Lazy scheduler slot — filled after Agent::new creates the Scheduler.
// Allows CreateJobTool to dispatch local jobs via the Scheduler even though
// the Scheduler is created after tools are registered (chicken-and-egg).
let scheduler_slot: ironclaw::tools::builtin::SchedulerSlot =
Arc::new(tokio::sync::RwLock::new(None));
// Register job tools (sandbox deps auto-injected when container_job_manager is available)
components.tools.register_job_tools(
Arc::clone(&components.context_manager),
Some(scheduler_slot.clone()),
container_job_manager.clone(),
components.db.clone(),
job_event_tx.clone(),
@@ -648,6 +655,9 @@ async fn async_main() -> anyhow::Result<()> {
Some(session_manager),
);
// Fill the scheduler slot now that Agent (and its Scheduler) exist.
*scheduler_slot.write().await = Some(agent.scheduler());
agent.run().await?;
// ── Shutdown ────────────────────────────────────────────────────────