Add Devin agent watcher#42
Conversation
There was a problem hiding this comment.
inspect review
Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review
Findings (1)
- [low] Watcher restart bug: stop() does not reset internal state (seeded flag and sessions Map). Evidence: stop() only clears interval, closes db, and nulls ctx; it does not set
this.seeded = falseor clearthis.sessions. On a subsequent start(),poll()will skip the seed path (if (!this.seeded)) and treat existing DB rows as already-known, so it will not emit initial non-idle statuses untillast_activity_atchanges.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
Signed-off-by: minatoaquaMK2 <jiacheng.yue@foxmail.com>
There was a problem hiding this comment.
inspect review
Triage: 91 entities analyzed | 0 critical, 0 high, 24 medium, 67 low
Verdict: standard_review
Findings (1)
- [low] sessions Map is never pruned: DevinAgentWatcher.poll() only iterates over the current query result set (hidden=0 and last_activity_at > threshold) and updates/creates snapshots, but it never removes entries from this.sessions when a session becomes hidden, falls below the stale threshold, or is deleted. Evidence: poll() builds
rowsfromSELECT ... FROM sessions WHERE hidden = 0 AND last_activity_at > ?then only loopsfor (const row of rows); there is no code that deletes keys fromthis.sessions.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
Signed-off-by: minatoaquaMK2 <jiacheng.yue@foxmail.com>
There was a problem hiding this comment.
inspect review
Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review
Findings (2)
- [low] start() can be called multiple times without clearing an existing interval/timeout, causing duplicate polling loops and duplicate emits. Evidence: DevinAgentWatcher.start() always schedules
setTimeout(() => this.poll(), 50);and assignsthis.pollTimer = setInterval(() => this.poll(), POLL_MS);with no guard/cleanup if already started. - [low] Sessions are never removed from the internal
sessionsMap when they stop appearing in the DB query (e.g., become hidden or older than the stale threshold), causing unbounded growth over time. Evidence: poll() only iteratesrowsand doesthis.sessions.set(row.id, ...)but never deletes entries not present inrows; the SELECT filtershidden = 0andlast_activity_at > ?, so previously tracked sessions can permanently drop out.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
updated |
There was a problem hiding this comment.
inspect review
Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review
Findings (1)
- [low] Sessions map is never pruned for sessions that become hidden or fall outside the stale window, causing unbounded growth over time. Evidence:
private sessions = new Map<string, SessionSnapshot>();is only ever added to viathis.sessions.set(row.id, snapshot)(both seed and incremental paths). The scan query filters rows withWHERE hidden = 0 AND last_activity_at > ?, but there is no code to remove entries fromthis.sessionsthat are no longer returned by the query.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
Summary
DevinAgentWatcher.DEVIN_CLI_DB_PATH.Details
The Devin watcher polls the Devin CLI SQLite database at
~/.local/share/devin/cli/sessions.db, orDEVIN_CLI_DB_PATHwhen set, usingbun:sqlitein read-only mode.It maps Devin sessions back to opensessions mux sessions through each row's
working_directory, then derives status from the headmessage_nodesentry referenced bymain_chain_id.Supported status handling includes:
runningfor user/tool messages, streaming assistant messages, and assistant tool callsdonefor assistant messages withfinish_reason=stoperrorforfinish_reason=errorinterruptedfor Devin interrupt marker messagesstalewhen a running session stops advancing for 15 secondsThe watcher also skips hidden sessions, ignores old inactive sessions, emits title updates, and reopens the database after read failures.
Testing
cd packages/runtime && bun test test/devin-watcher.test.tsResult: 30 tests passing.
