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 { join } from "node:path"
|
||||||
import type { Project, SessionInfo } from "../lib/types"
|
import type { Project, SessionInfo } from "../lib/types"
|
||||||
|
|
||||||
const PROJECTS_DIR = `${Bun.env.HOME}/.claude/projects`
|
const PROJECTS_DIR = `${Bun.env.HOME}/.claude/projects`
|
||||||
const BUSY_THRESHOLD_MS = 5000
|
const BUSY_THRESHOLD_MS = 5000
|
||||||
|
const TAIL_BYTES = 8192 // read last 8KB of JSONL to find final assistant entry
|
||||||
|
|
||||||
export interface ActiveSession {
|
export interface ActiveSession {
|
||||||
pid: string
|
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 {
|
function escapeAppleScript(s: string): string {
|
||||||
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
||||||
}
|
}
|
||||||
@@ -176,7 +211,10 @@ export async function detectActiveSessions(): Promise<Map<string, number>> {
|
|||||||
const key = cwdToProjectKey(cwd)
|
const key = cwdToProjectKey(cwd)
|
||||||
const jsonl = findActiveJsonl(key)
|
const jsonl = findActiveJsonl(key)
|
||||||
const now = Date.now()
|
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 }
|
return { pid, cwd, tty, sessionFile: jsonl?.path ?? null, busy, lastActivityMs: jsonl?.mtime ?? 0 }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user