Compacta sozinho ao fim do turno quando o payload da conversa passa de 16 MB, inclusive em sessões headless (-p / SDK). Com o synapse-rate-limit instalado, os…

Português · English
A marketplace of Claude Code mods.
/plugin marketplace add fabioivan/claude-code-mods
/plugin install synapse-rate-limit@fabioivan-mods
/plugin install ctx-bytes-guard@fabioivan-mods
/reload-plugins
| Plugin | Description |
|---|---|
synapse-rate-limit | A full HUD (based on the hud mod and claude-hud) with alerts, a limit forecast, a budget, a detail pane and 12 themes, plus the synapse line: weather, context/attachments size in MB, tokens and a chart of the last turns. |
ctx-bytes-guard | Auto-compacts when the conversation payload passes 16 MB (maxMb), checked after each turn (also in headless -p / SDK sessions); optionally also at a context percent (maxPercent, off by default). With synapse-rate-limit installed, the context line shows the size and context against those limits. |
Disable the original hud plugin when using synapse-rate-limit, so the bar does not show twice.
MIT (see LICENSE).
synapse-rate-limit includes code from claude-hud, under MIT (see synapse-rate-limit/LICENSE.claude-hud).
hooks/register.ts 88 lines1// Compacta sozinho quando o contexto passa de MAX_PERCENT ou o payload de MAX_BYTES.
2// Em sessão headless (-p / SDK) compacta com o comando /compact ao fim do turno.
3// Os limites vão para o atom `limits`, que o synapse-rate-limit desenha ao lado dos MB.
4import { atom, update } from 'claude-code'
5import type { EngineInterface, Register } from 'claude-code'
6
7import type { GuardLimits } from '../types'
8
9const DEFAULT_MB = 16
10const DEFAULT_PERCENT = 100
11
12const limits = atom({ plugin: 'ctx-bytes-guard', key: 'limits' } as const, null as GuardLimits | null)
13
14interface Guard {
15 maxBytes: number
16 maxPercent: number
17 // Sessões headless (-p / SDK) recusam `$.session.compact`: lá a compactação só roda como o comando
18 // /compact, e só depois do turno. Vira true na primeira recusa.
19 headless: boolean
20}
21
22const HEADLESS_RE = /headless/i
23const DEFER_RE = /later event/i
24
25// Declarada no topo do módulo: o motor só aceita `$` repassado a função assim.
26// Devolve o motivo quando a compactação não aconteceu, para o hook avisar.
27async function check($: EngineInterface, guard: Guard, when: 'submit' | 'turn'): Promise<string | null> {
28 await update($, limits, () => ({ maxBytes: guard.maxBytes, maxPercent: guard.maxPercent }))
29
30 const msgs = await $.session.messages({ as: 'api' })
31 // ponytail: JSON.stringify materializes the payload; fine at ~16MB, stream-count if it hurts
32 const bytes = Array.isArray(msgs) ? JSON.stringify(msgs).length : 0
33 const { context } = await $.session.usage()
34 const percent = context.percent ?? 0
35
36 if (bytes < guard.maxBytes && percent < guard.maxPercent) return null
37
38 if (!guard.headless) {
39 $.ui.toast(`auto-compact: ${percent}% / ${(bytes / 1048576).toFixed(1)}MB`)
40 try {
41 const result = await $.session.compact()
42 return result && 'skip' in result && result.skip ? `compactação vetada: ${result.skip}` : null
43 } catch (err) {
44 const msg = String(err)
45 // O motor recusa compactar de dentro do prompt.submit (o turno está preso): fica para o turn.complete.
46 if (when === 'submit' && DEFER_RE.test(msg)) return null
47 if (!HEADLESS_RE.test(msg)) throw err
48 guard.headless = true
49 }
50 }
51
52 // Headless: antes da mensagem o turno está preso e /compact esperaria por ele; depois do turno roda.
53 if (when === 'submit') return null
54 await $.command.run({ command: 'compact' })
55 return null
56}
57
58async function run($: EngineInterface, guard: Guard, when: 'submit' | 'turn'): Promise<void> {
59 try {
60 const why = await check($, guard, when)
61 if (why) $.ui.toast(`ctx-bytes-guard: ${why}`)
62 } catch (err) {
63 $.ui.toast(`ctx-bytes-guard: falhou: ${String(err)}`)
64 }
65}
66
67export const register: Register = (on, options) => {
68 const mb = typeof options.maxMb === 'number' && options.maxMb > 0 ? options.maxMb : DEFAULT_MB
69 const guard: Guard = { maxBytes: mb * 1024 * 1024, maxPercent: 0, headless: false }
70 guard.maxPercent =
71 typeof options.maxPercent === 'number' && options.maxPercent > 0 ? options.maxPercent : DEFAULT_PERCENT
72
73 // Antes de cada mensagem: compactar com o turno em andamento é recusado, e o erro de request grande
74 // acontece justamente durante o turno.
75 on('prompt.submit', async ($, e, next) => {
76 await run($, guard, 'submit')
77 return next(e)
78 })
79
80 // Depois do turno: cobre o que cresceu durante ele.
81 on('turn.complete', async ($, e, next) => {
82 const ran = await next(e)
83 if (e.agentId === undefined) {
84 await run($, guard, 'turn')
85 }
86 return ran
87 })
88}types/index.d.ts 11 lines1/** Os limites em que o ctx-bytes-guard compacta; o synapse-rate-limit os lê para a linha dos MB. */
2export type GuardLimits = { maxBytes: number; maxPercent: number }
3
4declare module 'claude-code' {
5 interface PluginState {
6 'ctx-bytes-guard': {
7 limits: GuardLimits | null
8 }
9 }
10}
11