fix: detect busy sessions by JSONL content, not just mtime
The previous 5-second mtime threshold caused false idle triggers during tool calls and subtasks. Now reads the last 8KB of the JSONL to check if the final assistant message contains tool_use — if so, Claude is still working regardless of how long ago the file was modified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { readdirSync, statSync } from "node:fs"
|
||||
import { readdirSync, statSync, openSync, readSync, closeSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import type { Project, SessionInfo } from "../lib/types"
|
||||
|
||||
const PROJECTS_DIR = `${Bun.env.HOME}/.claude/projects`
|
||||
const BUSY_THRESHOLD_MS = 5000
|
||||
const TAIL_BYTES = 8192 // read last 8KB of JSONL to find final assistant entry
|
||||
|
||||
export interface ActiveSession {
|
||||
pid: string
|
||||
@@ -40,6 +41,40 @@ function findActiveJsonl(projectKey: string): { path: string; mtime: number } |
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last assistant message in a JSONL has pending tool_use (= still working)
|
||||
function lastAssistantHasToolUse(filePath: string): boolean {
|
||||
try {
|
||||
const st = statSync(filePath)
|
||||
const size = st.size
|
||||
if (size === 0) return false
|
||||
const readSize = Math.min(TAIL_BYTES, size)
|
||||
const buf = Buffer.alloc(readSize)
|
||||
const fd = openSync(filePath, "r")
|
||||
try {
|
||||
readSync(fd, buf, 0, readSize, size - readSize)
|
||||
} finally {
|
||||
closeSync(fd)
|
||||
}
|
||||
const tail = buf.toString("utf-8")
|
||||
const lines = tail.split("\n")
|
||||
// Walk backwards to find last assistant entry
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const line = lines[i].trim()
|
||||
if (!line) continue
|
||||
// Quick pre-check before JSON.parse
|
||||
if (!line.includes('"assistant"')) continue
|
||||
try {
|
||||
const d = JSON.parse(line)
|
||||
if (d.type !== "assistant") continue
|
||||
const content = d.message?.content
|
||||
if (!Array.isArray(content)) continue
|
||||
return content.some((c: any) => c.type === "tool_use")
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
return false
|
||||
}
|
||||
|
||||
function escapeAppleScript(s: string): string {
|
||||
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
||||
}
|
||||
@@ -176,7 +211,10 @@ export async function detectActiveSessions(): Promise<Map<string, number>> {
|
||||
const key = cwdToProjectKey(cwd)
|
||||
const jsonl = findActiveJsonl(key)
|
||||
const now = Date.now()
|
||||
const busy = jsonl ? (now - jsonl.mtime) < BUSY_THRESHOLD_MS : false
|
||||
// Busy if: actively writing (<5s ago) OR last assistant message has tool_use (waiting for tool)
|
||||
const recentlyWritten = jsonl ? (now - jsonl.mtime) < BUSY_THRESHOLD_MS : false
|
||||
const pendingTool = jsonl ? lastAssistantHasToolUse(jsonl.path) : false
|
||||
const busy = recentlyWritten || pendingTool
|
||||
|
||||
return { pid, cwd, tty, sessionFile: jsonl?.path ?? null, busy, lastActivityMs: jsonl?.mtime ?? 0 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user