feat: pi agent watcher#2
Conversation
There was a problem hiding this comment.
Thanks for the effort @mbcrocci, overall the structure closely follows our existing JSONL watchers and the code is clean.
I verified against the real Pi data on my machine and caught a few issues, most notably a status flicker bug and a missing seed emit that the other watchers all have now.
Main things to fix:
toolResultrole causes idle flicker mid-turn — confirmed with real Pi JSONL data- Missing seed-time emit — Pi agents running at startup won't show in the TUI
decodeProjectDirbreaks on hyphenated paths — confirmed with real path/Users/.../mi-pi
See inline comments for details 👇
| await this.processFile(filePath); | ||
| } | ||
| } finally { | ||
| if (!this.seeded) this.seeded = true; |
There was a problem hiding this comment.
This is the biggest gap vs. the other watchers right now! All of them (codex, claude-code, amp, opencode) emit non-idle sessions after the seed scan completes — that's what populates the TUI sidebar when the server starts up.
Your scan() sets this.seeded = true but never loops through this.sessions to emit them. Compare with the Codex watcher's finally block:
if (!this.seeded) {
this.seeded = true;
for (const [threadId, snapshot] of this.sessions) {
if (snapshot.status === "idle" || !snapshot.projectDir) continue;
const session = this.ctx?.resolveSession(snapshot.projectDir);
if (!session) continue;
this.ctx?.emit({
agent: "pi",
session,
status: snapshot.status,
ts: Date.now(),
threadId: snapshot.sessionId ?? threadId,
...(snapshot.threadName && { threadName: snapshot.threadName }),
});
}
}Without this, if Pi is mid-task when the server starts, it'll be invisible in the sidebar until the next status change.
There was a problem hiding this comment.
I've added the codex block almost exactly, but I'm not sure if the issue is fixed.
In my setup, no sessions show up until I initiate the conversion (even if resuming a session). But this happens in Pi, OpenCode and Codex.
There was a problem hiding this comment.
no no i meant if the tui was quit and restarted, if say you were running 4 sessions and then quit the sidebar (using q) or updated the version (using tpm update flow), it would seed your data back in for the running sessions on restart, I'll check it though once
Adds a new watcher for the pi coding agent. It reads sessions jsonl transcripts to detect the harness' status.
ef527ef to
b55489e
Compare
There is no need to encode/decode as the filename already comes in the way it should be used to read.
|
Hi, thank you for your thorough review and the project - it's really cool! There are two pending issues I detected that I'm not sure if they are related to the pi-watcher or my specific tmux setup: Let me know if there is anything else that needs fixing. |
|
/inspect |
There was a problem hiding this comment.
inspect review
Triage: 37 entities analyzed | 0 critical, 0 high, 3 medium, 34 low
Verdict: standard_review
Findings (3)
- [low] Seed scan can crash after stop(): scan()'s finally block uses this.ctx.resolveSession(...) and this.ctx.emit(...) without re-checking this.ctx. stop() sets this.ctx = null, so if stop() is called while scan() is in-flight, finally will throw. Evidence: stop():
this.ctx = null;and scan() finally:const session = this.ctx.resolveSession(snapshot.projectDir); ... this.ctx.emit({ ... }). - [low] Incremental tail parsing can permanently drop entries when the last JSONL line is partially written: processFile() advances snapshot.fileSize to fileStat.size even if applyEntries() skips a truncated JSON line (JSON.parse fails). On the next run, those bytes are no longer re-read, so the entry is lost. Evidence: incremental path sets
nextSnapshot = applyEntries(text, { ...prev, fileSize: fileStat.size });and applyEntries():try { entry = JSON.parse(rawLine); } catch { continue; }with no buffering/retry of the incomplete last line. - [low] sessions Map key collision across directories: sessions are keyed only by threadId parsed from filename, ignoring the encoded directory. Two different projects with the same session-id suffix will overwrite each other, mixing state and emitting against the wrong session. Evidence:
const threadId = parseThreadId(filePath); const prev = this.sessions.get(threadId); ... this.sessions.set(threadId, nextSnapshot);and parseThreadId uses only basename split on '_' (return name.split('_').at(-1) ?? name;).
Reviewed by inspect | Entity-level triage found 0 high-risk changes
|
hey @mbcrocci, merged! thanks for the clean work and for sticking with the review — appreciate it. those two issues you mentioned aren't related to the pi watcher, will look into them separately. welcome to the project 🙌 |
Adds a new watcher for the pi coding agent.
It reads sessions jsonl transcripts to detect the harness' status.