SLOPSHOPPER

idle-compact

Compacts a quiet session when cache-keepalive reaches its 55-minute mark

newnetworktimer
v1.0.0CC0-1.0updated 2026-10-11eturkes/agents/claude/mods/idle-compact
A shopper browsing a rack in a slop shop
README

agents

Source 1 files
hooks/register.ts 59 lines
1import type { EngineInterface, ModelUsage, Register } from 'claude-code'
2
3// cache-keepalive decides at its mark (claude/cache-keepalive/README.md); this mod reports quiet + runs the compaction.
4const POLL_MS = 30_000
5const KEEPER = 'http://127.0.0.1:8318'
6// Quiet = no turn running (an AskUserQuestion wait included) + the last main Stop left no background task or cron.
7const running = new Set<string>()
8let background = false
9let compacting = false
10
11async function report($: EngineInterface, path: string, body: object): Promise<unknown> {
12  const url = `${(await $.env.get('CACHE_KEEPALIVE_URL')) || KEEPER}/_keepalive/${path}`
13  const r = await $.http.fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
14  return r.ok ? JSON.parse(r.text) : undefined
15}
16
17async function poll($: EngineInterface): Promise<void> {
18  const session = await $.session.id()
19  const quiet = running.size === 0 && !background && !compacting
20  const asked = (await report($, 'poll', { session, quiet })) as { compact?: unknown } | undefined
21  if (asked?.compact !== true) return
22  let reason = ''
23  let usage: ModelUsage | undefined
24  compacting = true
25  try {
26    const r = await $.session.compact()
27    if (r.skip === undefined) usage = r.usage
28    else reason = r.skip
29  } catch (err) {
30    reason = String(err)
31  } finally {
32    compacting = false
33  }
34  await report($, 'compacted', { session, ok: !reason, reason, usage })
35}
36
37export const register: Register = on => {
38  on('session.start', async ($, e, next) => {
39    const r = await next(e)
40    if (e.isInteractive) $.clock.every(POLL_MS, () => void poll($).catch(() => {}))
41    return r
42  })
43
44  on('turn.start', (_$, e, next) => {
45    running.add(e.turnId)
46    return next(e)
47  })
48
49  on('turn.complete', (_$, e, next) => {
50    running.delete(e.turnId)
51    return next(e)
52  })
53
54  on('classic.Stop', (_$, e, next) => {
55    if (e.agent_id === undefined) background = !!(e.background_tasks?.length || e.session_crons?.length)
56    return next(e)
57  })
58}
59