diff --git a/src/worker.nim b/src/worker.nim index a30c772..a354c08 100644 --- a/src/worker.nim +++ b/src/worker.nim @@ -56,87 +56,100 @@ proc spawn(taskId: string, description: string = "", echo " State: ASSIGNED" echo " Review: (review-gate not available)" +# Status table column widths +const + ColTask = 14 + ColState = 12 + ColReview = 10 + ColAge = 8 + ColHeartbeat = 12 + ColStatus = 8 + ColSummaryMax = 30 + +proc renderStatusTable(stateFilter: string, staleOnly: bool, asJson: bool) = + ## Render worker status as table or JSON + let db = openBusDb() + defer: db.close() + + let workers = db.getAllWorkers() + + if asJson: + var arr = newJArray() + for w in workers: + let rs = getReviewState(w.taskId) + arr.add(%*{ + "task_id": w.taskId, + "state": $w.state, + "branch": w.branch, + "stale": w.staleLevel, + "age": $(getTime() - w.createdAt), + "review": $rs.status + }) + echo arr.pretty + return + + # Filter + var filtered = workers + if stateFilter != "": + filtered = workers.filterIt($it.state == stateFilter.toUpperAscii) + if staleOnly: + filtered = filtered.filterIt(it.isStale) + + if filtered.len == 0: + echo "No workers found." + return + + # Table header + echo "" + echo "TASK".alignLeft(ColTask), "STATE".alignLeft(ColState), + "REVIEW".alignLeft(ColReview), "AGE".alignLeft(ColAge), + "HEARTBEAT".alignLeft(ColHeartbeat), "STATUS".alignLeft(ColStatus), "SUMMARY" + echo "-".repeat(80) + + for w in filtered: + let age = getTime() - w.createdAt + let ageStr = if age.inHours > 0: &"{age.inHours}h" + elif age.inMinutes > 0: &"{age.inMinutes}m" + else: &"{age.inSeconds}s" + + var hbStr = "--" + if w.lastHeartbeat != Time(): + let hbAge = getTime() - w.lastHeartbeat + hbStr = if hbAge.inMinutes > 0: &"{hbAge.inMinutes}m ago" + else: &"{hbAge.inSeconds}s ago" + + let rs = getReviewState(w.taskId) + let reviewStr = case rs.status + of rsNone: "--" + of rsPending: "pending" + of rsApproved: "approved" + of rsRejected: "REJECTED" + + let staleStr = w.staleLevel + let summary = if w.description.len > ColSummaryMax: + w.description[0.. 0: &"{age.inHours}h" - elif age.inMinutes > 0: &"{age.inMinutes}m" - else: &"{age.inSeconds}s" - - var hbStr = "--" - if w.lastHeartbeat != Time(): - let hbAge = getTime() - w.lastHeartbeat - hbStr = if hbAge.inMinutes > 0: &"{hbAge.inMinutes}m ago" - else: &"{hbAge.inSeconds}s ago" - - let rs = getReviewState(w.taskId) - let reviewStr = case rs.status - of rsNone: "--" - of rsPending: "pending" - of rsApproved: "approved" - of rsRejected: "REJECTED" - - let staleStr = w.staleLevel - let summary = if w.description.len > 30: w.description[0..29] & "..." - else: w.description - - echo w.taskId.alignLeft(14), - ($w.state).alignLeft(12), - reviewStr.alignLeft(10), - ageStr.alignLeft(8), - hbStr.alignLeft(12), - staleStr.alignLeft(8), - summary - if watch: while true: eraseScreen() setCursorPos(0, 0) echo "Worker Status (", now().format("HH:mm:ss"), ") - Press Ctrl+C to exit" - render() + renderStatusTable(state, stale, json) sleep(2000) else: - render() + renderStatusTable(state, stale, json) proc approve(taskId: string) = ## Approve completed work (IN_REVIEW → APPROVED) @@ -254,7 +267,7 @@ proc start(task: string = "") = quit(ExitSuccess) # Start heartbeat before transition so we're heartbeating when state changes - startGlobalHeartbeat(dbPath, taskId) + discard startGlobalHeartbeat(dbPath, taskId) try: db.transition(taskId, wsAssigned, wsWorking) @@ -348,11 +361,10 @@ proc sendHeartbeat(status: string = "working", progress: float = 0.0) = let db = openBusDb(getMainRepoBusDbPath()) defer: db.close() - let hs = case status - of "idle": hsIdle - of "working": hsWorking - of "blocked": hsBlocked - else: hsWorking + let hs = try: + parseEnum[HeartbeatStatus](status) + except ValueError: + hsWorking # Default for unknown status db.writeHeartbeat(ctx.taskId, hs, ctx.taskId, progress) echo "Heartbeat: ", ctx.taskId, " - ", status diff --git a/src/worker/git.nim b/src/worker/git.nim index dbdc244..e09a2a2 100644 --- a/src/worker/git.nim +++ b/src/worker/git.nim @@ -138,17 +138,3 @@ proc getConflictedFiles*(worktree: string): seq[string] = if line.strip() != "": result.add(line.strip()) -proc getBranchStatus*(worktree: string): tuple[ahead, behind: int] = - ## Get commits ahead/behind integration - let (output, code) = runGit(["rev-list", "--left-right", "--count", - "origin/integration...HEAD"], workDir = worktree) - if code != 0: - return (0, 0) - - let parts = output.strip().split('\t') - if parts.len >= 2: - try: - result.behind = parseInt(parts[0]) - result.ahead = parseInt(parts[1]) - except ValueError as e: - logWarn("getBranchStatus", "failed to parse rev-list output '" & output.strip() & "': " & e.msg) diff --git a/src/worker/heartbeat.nim b/src/worker/heartbeat.nim index eb4142f..794931b 100644 --- a/src/worker/heartbeat.nim +++ b/src/worker/heartbeat.nim @@ -107,11 +107,13 @@ proc stopHeartbeat*(hb: HeartbeatThread) = # Simpler API using global state (for single-threaded CLI usage) var globalHeartbeat: HeartbeatThread = nil -proc startGlobalHeartbeat*(dbPath, agentId: string) = - ## Start heartbeat using global state (simpler API for CLI) +proc startGlobalHeartbeat*(dbPath, agentId: string): bool = + ## Start heartbeat using global state (simpler API for CLI). + ## Returns true if started, false if already running. if globalHeartbeat != nil: - return # Already running + return false # Already running globalHeartbeat = startHeartbeat(dbPath, agentId) + return true proc updateGlobalHeartbeat*(status: HeartbeatStatus, task: string = "", progress: float = 0.0) =