Skip to content

Add Devin agent watcher#42

Open
minatoaquaMK2 wants to merge 2 commits into
Ataraxy-Labs:mainfrom
minatoaquaMK2:main
Open

Add Devin agent watcher#42
minatoaquaMK2 wants to merge 2 commits into
Ataraxy-Labs:mainfrom
minatoaquaMK2:main

Conversation

@minatoaquaMK2
Copy link
Copy Markdown

Summary

  • Add built-in support for Devin CLI sessions through a new DevinAgentWatcher.
  • Register and export the watcher from the runtime/server startup path.
  • Update docs to list Devin support and document DEVIN_CLI_DB_PATH.
  • Refresh built-in watcher docs so the existing Pi watcher is also reflected consistently.

Details

The Devin watcher polls the Devin CLI SQLite database at ~/.local/share/devin/cli/sessions.db, or DEVIN_CLI_DB_PATH when set, using bun:sqlite in read-only mode.

It maps Devin sessions back to opensessions mux sessions through each row's working_directory, then derives status from the head message_nodes entry referenced by
main_chain_id.

Supported status handling includes:

  • running for user/tool messages, streaming assistant messages, and assistant tool calls
  • done for assistant messages with finish_reason=stop
  • error for finish_reason=error
  • interrupted for Devin interrupt marker messages
  • stale when a running session stops advancing for 15 seconds

The 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.ts

Result: 30 tests passing.
image

Copy link
Copy Markdown

@inspect-review inspect-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect review

Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review

Findings (1)

  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 = false or clear this.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 until last_activity_at changes.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

Signed-off-by: minatoaquaMK2 <jiacheng.yue@foxmail.com>
Copy link
Copy Markdown

@inspect-review inspect-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect review

Triage: 91 entities analyzed | 0 critical, 0 high, 24 medium, 67 low
Verdict: standard_review

Findings (1)

  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 rows from SELECT ... FROM sessions WHERE hidden = 0 AND last_activity_at > ? then only loops for (const row of rows); there is no code that deletes keys from this.sessions.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

Signed-off-by: minatoaquaMK2 <jiacheng.yue@foxmail.com>
Copy link
Copy Markdown

@inspect-review inspect-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect review

Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review

Findings (2)

  1. [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 assigns this.pollTimer = setInterval(() => this.poll(), POLL_MS); with no guard/cleanup if already started.
  2. [low] Sessions are never removed from the internal sessions Map 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 iterates rows and does this.sessions.set(row.id, ...) but never deletes entries not present in rows; the SELECT filters hidden = 0 and last_activity_at > ?, so previously tracked sessions can permanently drop out.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

@minatoaquaMK2
Copy link
Copy Markdown
Author

inspect review

Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low Verdict: standard_review

Findings (1)

  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 = false or clear this.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 until last_activity_at changes.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

updated

Copy link
Copy Markdown

@inspect-review inspect-review Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inspect review

Triage: 60 entities analyzed | 0 critical, 0 high, 12 medium, 48 low
Verdict: standard_review

Findings (1)

  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 via this.sessions.set(row.id, snapshot) (both seed and incremental paths). The scan query filters rows with WHERE hidden = 0 AND last_activity_at > ?, but there is no code to remove entries from this.sessions that are no longer returned by the query.

Reviewed by inspect | Entity-level triage found 0 high-risk changes

@Palanikannan1437 Palanikannan1437 self-requested a review May 18, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant