claude-code-infinite 1.0.11 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +214 -47
  2. package/dist/cc-request.d.ts +94 -0
  3. package/dist/cc-request.d.ts.map +1 -0
  4. package/dist/cc-request.js +194 -0
  5. package/dist/cc-request.js.map +1 -0
  6. package/dist/claude-env.d.ts +30 -0
  7. package/dist/claude-env.d.ts.map +1 -0
  8. package/dist/claude-env.js +55 -0
  9. package/dist/claude-env.js.map +1 -0
  10. package/dist/cli-args.d.ts +32 -0
  11. package/dist/cli-args.d.ts.map +1 -0
  12. package/dist/cli-args.js +72 -0
  13. package/dist/cli-args.js.map +1 -0
  14. package/dist/cli-lifecycle.d.ts +17 -0
  15. package/dist/cli-lifecycle.d.ts.map +1 -0
  16. package/dist/cli-lifecycle.js +30 -0
  17. package/dist/cli-lifecycle.js.map +1 -0
  18. package/dist/cli-main.d.ts +15 -0
  19. package/dist/cli-main.d.ts.map +1 -0
  20. package/dist/cli-main.js +464 -0
  21. package/dist/cli-main.js.map +1 -0
  22. package/dist/cli.js +13 -178
  23. package/dist/cli.js.map +1 -1
  24. package/dist/config.d.ts +3 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +29 -3
  27. package/dist/config.js.map +1 -1
  28. package/dist/count-tokens.d.ts +78 -0
  29. package/dist/count-tokens.d.ts.map +1 -0
  30. package/dist/count-tokens.js +277 -0
  31. package/dist/count-tokens.js.map +1 -0
  32. package/dist/final-index.d.ts +10 -0
  33. package/dist/final-index.d.ts.map +1 -0
  34. package/dist/final-index.js +37 -0
  35. package/dist/final-index.js.map +1 -0
  36. package/dist/hooks.d.ts +239 -0
  37. package/dist/hooks.d.ts.map +1 -0
  38. package/dist/hooks.js +618 -0
  39. package/dist/hooks.js.map +1 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +8 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/memtree-fetch.d.ts +24 -0
  45. package/dist/memtree-fetch.d.ts.map +1 -0
  46. package/dist/memtree-fetch.js +121 -0
  47. package/dist/memtree-fetch.js.map +1 -0
  48. package/dist/memtree-finder.d.ts +99 -0
  49. package/dist/memtree-finder.d.ts.map +1 -0
  50. package/dist/memtree-finder.js +235 -0
  51. package/dist/memtree-finder.js.map +1 -0
  52. package/dist/memtree-links.d.ts +16 -0
  53. package/dist/memtree-links.d.ts.map +1 -0
  54. package/dist/memtree-links.js +66 -0
  55. package/dist/memtree-links.js.map +1 -0
  56. package/dist/memtree-mcp-config.d.ts +28 -0
  57. package/dist/memtree-mcp-config.d.ts.map +1 -0
  58. package/dist/memtree-mcp-config.js +121 -0
  59. package/dist/memtree-mcp-config.js.map +1 -0
  60. package/dist/memtree-mcp.d.ts +265 -0
  61. package/dist/memtree-mcp.d.ts.map +1 -0
  62. package/dist/memtree-mcp.js +667 -0
  63. package/dist/memtree-mcp.js.map +1 -0
  64. package/dist/memtree-newest.d.ts +74 -0
  65. package/dist/memtree-newest.d.ts.map +1 -0
  66. package/dist/memtree-newest.js +217 -0
  67. package/dist/memtree-newest.js.map +1 -0
  68. package/dist/memtree-tools.d.ts +105 -0
  69. package/dist/memtree-tools.d.ts.map +1 -0
  70. package/dist/memtree-tools.js +408 -0
  71. package/dist/memtree-tools.js.map +1 -0
  72. package/dist/memtree.d.ts +398 -0
  73. package/dist/memtree.d.ts.map +1 -0
  74. package/dist/memtree.js +1231 -0
  75. package/dist/memtree.js.map +1 -0
  76. package/dist/notices.d.ts +177 -0
  77. package/dist/notices.d.ts.map +1 -0
  78. package/dist/notices.js +469 -0
  79. package/dist/notices.js.map +1 -0
  80. package/dist/payment-gate.d.ts +53 -0
  81. package/dist/payment-gate.d.ts.map +1 -0
  82. package/dist/payment-gate.js +97 -0
  83. package/dist/payment-gate.js.map +1 -0
  84. package/dist/project-meta.d.ts +15 -0
  85. package/dist/project-meta.d.ts.map +1 -0
  86. package/dist/project-meta.js +80 -0
  87. package/dist/project-meta.js.map +1 -0
  88. package/dist/prompt-accounting.d.ts +12 -0
  89. package/dist/prompt-accounting.d.ts.map +1 -0
  90. package/dist/prompt-accounting.js +70 -0
  91. package/dist/prompt-accounting.js.map +1 -0
  92. package/dist/proxy.d.ts +197 -0
  93. package/dist/proxy.d.ts.map +1 -0
  94. package/dist/proxy.js +4247 -0
  95. package/dist/proxy.js.map +1 -0
  96. package/dist/reqlog.d.ts +419 -0
  97. package/dist/reqlog.d.ts.map +1 -0
  98. package/dist/reqlog.js +146 -0
  99. package/dist/reqlog.js.map +1 -0
  100. package/dist/request-sizing.d.ts +52 -0
  101. package/dist/request-sizing.d.ts.map +1 -0
  102. package/dist/request-sizing.js +92 -0
  103. package/dist/request-sizing.js.map +1 -0
  104. package/dist/resume-repair-file.d.ts +17 -0
  105. package/dist/resume-repair-file.d.ts.map +1 -0
  106. package/dist/resume-repair-file.js +100 -0
  107. package/dist/resume-repair-file.js.map +1 -0
  108. package/dist/resume-repair.d.ts +27 -0
  109. package/dist/resume-repair.d.ts.map +1 -0
  110. package/dist/resume-repair.js +198 -0
  111. package/dist/resume-repair.js.map +1 -0
  112. package/dist/route-cache.d.ts +5 -0
  113. package/dist/route-cache.d.ts.map +1 -0
  114. package/dist/route-cache.js +63 -0
  115. package/dist/route-cache.js.map +1 -0
  116. package/dist/route-fallback.d.ts +29 -0
  117. package/dist/route-fallback.d.ts.map +1 -0
  118. package/dist/route-fallback.js +49 -0
  119. package/dist/route-fallback.js.map +1 -0
  120. package/dist/route-identity.d.ts +6 -0
  121. package/dist/route-identity.d.ts.map +1 -0
  122. package/dist/route-identity.js +100 -0
  123. package/dist/route-identity.js.map +1 -0
  124. package/dist/transcript-usage.d.ts +57 -0
  125. package/dist/transcript-usage.d.ts.map +1 -0
  126. package/dist/transcript-usage.js +570 -0
  127. package/dist/transcript-usage.js.map +1 -0
  128. package/dist/turns.d.ts +73 -0
  129. package/dist/turns.d.ts.map +1 -0
  130. package/dist/turns.js +298 -0
  131. package/dist/turns.js.map +1 -0
  132. package/dist/update-check.d.ts +38 -0
  133. package/dist/update-check.d.ts.map +1 -0
  134. package/dist/update-check.js +67 -0
  135. package/dist/update-check.js.map +1 -0
  136. package/package.json +6 -2
package/README.md CHANGED
@@ -4,74 +4,234 @@
4
4
 
5
5
  # Claude Code Infinite
6
6
 
7
- * Maximize Claude's intelligence with context-management from [MemTree.dev](https://memtree.dev)
8
- * Supports unlimited-length coding sessions
9
- * Feels fast and fresh with every message
10
- * Automatically recalls only the relevant past information
11
- * Allows you to continue your session without summarizing or compacting
12
-
7
+ Memory for long-running agents. Claude Code Infinite indexes your Claude Code sessions with
8
+ [MemTree](https://memtree.dev) so their history can be easily searched, navigated, and understood,
9
+ by you and by Claude, within a session and across all of them.
10
+
11
+ While Claude works, MemTree summarizes each new batch of messages in the background and files it
12
+ under a topic. Each topic gets its own summary, and the whole session gets one at the top. When a
13
+ session outgrows the context window, Claude works from these summaries plus your most recent
14
+ messages, so the session can keep going indefinitely. When it needs a detail, it opens a branch and
15
+ follows it down to the original messages.
16
+
17
+ * **Search across every session.** Claude can find what was decided, tried or ruled out in any of
18
+ your past sessions, by exact words or by meaning, filtered by project and date
19
+ * **Browse each session's MemTree** in your browser, from the link `ccc` prints in the terminal
20
+ * **Replaces `/compact` without losing the thread.** In a blind-graded benchmark, MemTree matched
21
+ full-context Claude Code and beat Claude Code's built-in `/compact` by 20 points
22
+ ([details](#long-session-benchmark))
23
+ * **Unlimited-length sessions**, with relevant past information recalled automatically
24
+
25
+ ## Search across sessions
26
+
27
+ Every `ccc` session is indexed as it runs, and Claude gets read-only `memtree` tools over all of
28
+ your indexed sessions, so you can just ask:
29
+
30
+ * "What did we decide about the retry limit last week?"
31
+ * "Find the session where we fixed the token-count bug, and show me the fix."
32
+ * "We tried this before. What went wrong last time?"
33
+
34
+ | Tool | What it does |
35
+ | --- | --- |
36
+ | `search` | Searches this session's tree or all of your sessions. Text mode (default, free) matches exact words, ids, paths and errors; vector mode matches meaning. Filter by project, `since` and `until`. |
37
+ | `list` | Lists your sessions, most recently active first, with title, first message, project (directory, repo, branch), models and times. Filter by time, project or words. |
38
+ | `read_node` | Opens a node: its summary, its path from the root, and its children. |
39
+ | `read_lines` | Reads the exact original transcript lines under a leaf. |
40
+
41
+ Hits name their node, so Claude goes from a search hit to the summary around it and then to the
42
+ exact lines. Type `/memtree-view` for the link to this session's tree. Details are under
43
+ [Memory tools](#memory-tools).
44
+
45
+ ## Long-session benchmark
46
+
47
+ In a blind-graded benchmark on a 775k-token Claude Code session, MemTree matched full-context
48
+ Claude Code and beat Claude Code's built-in `/compact` by 20 points, finishing in about half the
49
+ time. That is the same score as full context with a third of the tokens.
50
+
51
+ | Setup | Score | Input tokens / run | Wall time | How it handles the history |
52
+ | --- | --- | --- | --- | --- |
53
+ | **Claude Code Infinite** | **69** | **39M** | **11–22 min** | Sent whole until the 800k budget, then compressed once to ~420k; never compacted |
54
+ | Full context | 69 | 113M | 24–54 min | Grew to ~970k and hit Claude Code's auto-compaction mid-task every round |
55
+ | `/compact` | 49 | 17M | 29–40 min | Claude Code's `/compact` before the task, 789k → 13k |
56
+
57
+ Every setup resumes the same ~775k-token history and is given one task: fix the security issues
58
+ found earlier in the session, which are listed only in the conversation. Claude Opus 5.5, 3
59
+ rounds, graded blind against a fixed answer key by an independent grader. Scores are 0–100 means
60
+ (68.7 / 69.3 / 48.7 in table order); the noise floor is 10 points, so MemTree vs full context is parity, not a
61
+ win, while MemTree was ahead of `/compact` in every round. Average wall time was 16 min
62
+ for MemTree and 38 min for full context. Input tokens count uncached, cache-read and cache-write
63
+ tokens; `/compact`'s exclude the one-time `/compact` step. This is one task family, one model and
64
+ 3 rounds, not a general benchmark.
65
+
13
66
  ## Requirements
14
67
 
15
- * [node.js 18 or newer](https://nodejs.org/en/download/)
68
+ * [node.js 20.3 or newer](https://nodejs.org/en/download/)
16
69
  * [Claude Code (the terminal version)](https://code.claude.com/docs/en/quickstart)
17
70
  * **Claude Subscription** - optional but highly recommended as this offers up to 1000x cost savings vs Anthropic's API pricing
18
71
 
19
- > [!NOTE]
20
- > #### No Anthropic Subscription
21
- > If you do not want to buy Anthropic subscription, choose option 2. "Anthropic Console account", during the Claude Code setup.
22
- >
23
- > You don't need to buy API credits, just login and Claude Code will let you complete setup. Anthropic API usage will be billed through https://polychat.co.
24
- >
25
- > Finally run `/logout` within Claude Code and rerun with `ccc`
26
-
27
72
  ## Setup
28
73
 
29
- 1. Install with npm
74
+ > [!TIP]
75
+ > No Claude subscription? Use an Anthropic API key exactly as with vanilla Claude Code
76
+ > (`ANTHROPIC_API_KEY` or a Console login). `ccc` forwards it unchanged to api.anthropic.com, and
77
+ > you pay Anthropic per token.
78
+
79
+ 1. Install (Node ≥ 20.3; no git needed)
30
80
  ```bash
31
- npm install -g claude-code-infinite
81
+ npm install -g claude-code-infinite
32
82
  ```
33
83
  2. Run Claude Code Infinite with
34
84
  ```bash
35
85
  ccc
36
86
  ```
37
87
 
38
- ## How it works
88
+ This will guide you through setting up your PolyChat key which you can also get [here](https://polychat.co/auth?memtree=true).
39
89
 
40
- <table><tr><td>
41
- <img width="1050" height="445" alt="image" src="https://github.com/user-attachments/assets/d1ab2456-9a64-4118-a72a-b9d133c7c8bd" />
42
- </td></tr></table>
90
+ ## Environments
43
91
 
92
+ The tool supports multiple environments (this selects the MemTree compression API only — Anthropic traffic always goes directly from your machine to api.anthropic.com):
44
93
 
45
- When you send a message, we retrieve relevant details and summaries from the prior messages in your thread. These details and summaries populate a **memory message**. Following the memory message, we append a compressed version of your recent message history. The resulting context-window is dramatically smaller, allowing Claude to process your request with much greater efficacy, lower latency, and reduced cost.
94
+ - **Production** (default): `ccc` - Uses https://api.polychat.co
95
+ - **Local**: `ccc local` - Uses http://localhost:8080 for local development
96
+
97
+ Each environment maintains its own separate API key.
98
+
99
+ ## Claude Code auto-compaction
100
+
101
+ `ccc` disables Claude Code's automatic conversation compaction for the Claude process it launches. MemTree manages the context sent to the model, so letting Claude Code independently summarize the full local transcript can compact a conversation MemTree already reduced. Manual `/compact` remains available.
102
+
103
+ Claude Code 2.1.219 also recommends “Resume from summary” for any old session
104
+ above a fixed 100k-token threshold, even when the active model has a 1M window
105
+ and auto-compaction is disabled. `ccc` suppresses that resume-time
106
+ recommendation as well; it does not disable the manual `/compact` command.
107
+
108
+ A session whose last response reached the verified local refusal threshold
109
+ (for example one sent whole while MemTree was unavailable) would otherwise be
110
+ refused locally with "Context limit reached" on every prompt after a resume.
111
+ On `ccc --resume <id>`, `ccc` backs the transcript up to
112
+ `~/.claude-code-infinite/transcript-backups/` and lowers that last response's
113
+ recorded usage to half the window, so the next message is sent and MemTree
114
+ compresses it.
115
+
116
+ To restore Claude Code's native auto-compaction setting for one invocation, use:
117
+
118
+ ```bash
119
+ CCC_AUTO_COMPACT=1 ccc
120
+ ```
46
121
 
47
- ## What this is NOT
122
+ Claude Code normally treats any custom `ANTHROPIC_BASE_URL` as an unverifiable
123
+ gateway and can therefore account for a resumed native-1M model as if it had a
124
+ 200k window. `ccc` identifies its transparent localhost relay as trusted, so
125
+ current native-1M models retain the same context window they have when Claude
126
+ Code connects directly to Anthropic. The official
127
+ `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` switch still forces the legacy 200k behavior.
48
128
 
49
- This is not a MPC or tool for simply retrieving memories. While we are compatible with all MPC's, tools, and other Anthropic features, these do not prevent your context window from becoming detrimentally large. MCP's and tools are some of the biggest token bloaters and it's exactly these types of messages that we heavily reduce during our compression phase.
129
+ ## Privacy & architecture: your Anthropic credentials never leave your machine
50
130
 
51
- ## Why it works
131
+ `ccc` runs a small proxy on `127.0.0.1` and launches Claude Code with `ANTHROPIC_BASE_URL` pointed at it and automatic compaction disabled. Claude Code keeps its **native login** — token refresh, plan-default model selection, and rate-limit handling behave exactly like vanilla Claude Code, and your OAuth token is sent only to `api.anthropic.com` from your own machine.
52
132
 
53
- LLMs get exponentially less intelligent as their input grows.
133
+ ```
134
+ Claude Code ──▶ localhost proxy (ccc)
135
+ ├──(messages only, MemTree API key)──▶ api.polychat.co /v1/context_memory
136
+ │◀──(compressed messages)─────────────┘
137
+ └──(compressed request + your local OAuth)──▶ api.anthropic.com
138
+ ```
54
139
 
55
- References:
56
- - [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172) (2023)
57
- - [RULER: What's the Real Context Size of Your Long-Context Language Models?](https://arxiv.org/abs/2404.06654) (2024)
58
- - <a href="https://research.trychroma.com/context-rot" target="_blank" rel="noopener noreferrer">Context Rot from Chroma</a> (2025)
59
-
60
- <a href="https://www.youtube.com/watch?v=TUjQuC4ugak" target="_blank" rel="noopener noreferrer">
61
- <img src="https://img.youtube.com/vi/TUjQuC4ugak/0.jpg" alt="Context Rot Video">
62
- </a>
140
+ - Only message content is sent to MemTree for indexing/compression — never credentials.
141
+ - Anthropic requests go directly from the local proxy to api.anthropic.com using the authentication Claude Code supplied. PolyChat never sees that credential or Anthropic traffic.
142
+ - If MemTree is unreachable, slow, or your MemTree plan needs payment, `ccc` degrades to a transparent passthrough so your session is never interrupted.
63
143
 
144
+ ### Inline notices
64
145
 
65
- Furthermore, the above research primarily tests on needle-in-a-haystack tasks, which underestimates the effect for more difficult tasks encountered in coding.
146
+ In interactive sessions, `ccc` reports these MemTree states as display-only lines in Claude Code:
66
147
 
67
- This is why starting sessions from scratch provides such a significant uplift in ability. What we're essentially doing is keeping each session as close to from-scratch as possible by limiting the tokens in Claude's context window to around 30k, or 15% of the standard 200k context-limit, filled precisely with the information relevant to your **last** message. Read more about how MemTree works [here](https://api.polychat.co/context-memory).
148
+ - `✓ MemTree · conversation optimized · ~813k → 408k tokens`, with the session's newest completed MemTree page on its own line below it and a blank line before the answer, when a compressed turn reflects new indexing (MemTree's index covers more of the conversation than before) or a newly finished MemTree page. The before-count is `ccc`'s size estimate for the request that would otherwise have been sent (MemTree's `usage.raw_prompt_tokens` when `ccc` has none); the after-count is Anthropic's reported compressed input, estimated from bytes until usage arrives. Counts under a million are whole thousands, larger ones one decimal (`1.3m`). Without both counts, or when the result is not smaller, the line reads just `✓ MemTree · conversation optimized`. The line is green when terminal color is available, and plain when `NO_COLOR` or a monochrome terminal is configured. `ccc` uses the standard ANSI green foreground sequence and Node's capability detection, so the same path works in ANSI terminals on macOS/Linux and supported Windows consoles.
149
+ - `⚠ MemTree degraded — this turn ran uncompressed` when a blocking compression call fails or times out.
150
+ - `⚠ MemTree is off — payment required…` once when compression and indexing are disabled for payment.
151
+ - `• MemTree` with the page on its own line below it, once at the end of a user turn, when the turn used a MemTree index whose link has not been shown yet (the first one of the session, then each newly finished index), unless the turn's success line already showed it. The line comes from the `Stop` hook, so it lands after the turn's last message. When MemTree passed the conversation through whole because it already fits the model's budget, the label line reads `• MemTree · /memtree-compact to compact session`. The URL always gets a line to itself, so a long link does not wrap the label or note. The displayed link follows the server's newest completed tree for this session, preserving its pinned namespace and own/served selection. If that lookup is unavailable, it falls back to the compression page; that page can show a completed prefix while its own tree builds. Open it in a browser, read it as JSON with `ccc fetch <url>`, or, from an agent inside the session, `GET $ANTHROPIC_BASE_URL/memtree/<short id>.json` on the loopback proxy without handling the key. Every MemTree call carries Claude Code's session id, so `GET $ANTHROPIC_BASE_URL/memtree/sessions/<session id>.json` lists every page from one session, newest first (the session id is the `--resume` id and the name of the transcript under `~/.claude/projects/`). `/memtree` lists the MemTree commands. Type `/memtree-view` at any time to print the session's latest link. `/memtree-compact` is MemTree's replacement for Claude Code's `/compact`: your next message is sent compressed to half the budget (or `/memtree-compact 400k` for another target), and from then on the session follows the compaction rules under [How it works](#how-it-works) with that target. `/memtree-compact off` goes back to sending the conversation whole. `CCC_COMPACT_TARGET=500k` sets the target of every compaction for every session from launch, for benchmarks and headless (`-p`) runs, where the slash command is unavailable; it does not compact by itself, the budget still decides when, and `/memtree-compact off` still turns compaction off for one session. `CCC_COMPACT_TARGET=off` starts every session in the `/memtree-compact off` state instead (`/memtree-compact [N]` still turns it back on). `ccc` answers these commands locally, so they cost no model turn and nothing is added to the conversation; the compaction setting lasts for the life of the `ccc` process. When a session starts and already has a page, which in practice means a resumed one (`ccc --resume`, or `/resume` inside a running session), its latest link is shown right away: `ccc` remembers the newest page per session in `~/.claude-code-infinite/memtree-links.json` (most recent 200 sessions) and a `SessionStart` hook prints it. `CCC_MEMTREE_LINK` changes where the link appears: `turn` (default), `message` (under every finished assistant message, green when the index is new, dim otherwise), `stop` (every turn, on `Stop`), `success` (only on the line below `✓ MemTree · conversation optimized`), or `off`.
68
152
 
69
- ### Operating System Analogy
153
+ The newest-tree lookup runs in the background with a 60-second cache, at most eight outstanding fetches, and one 10-second deadline across at most 32 finder pages. `/memtree-view` and resume hooks wait at most 2.5 seconds. Servers with the finder session-ID search (polychat `0202eaa` and later, including reviewed `c67cadb`) use `/v1/memtree/sessions?q=<id>`, following cursors and accepting only the exact session ID. A missing endpoint falls back to the compression page and is not retried for 30 minutes. The older exact route `/usage/memtree/sessions/<id>.json` accepts the bearer key, but excludes index-only calls and does not report completed trees, so it cannot supply this link. No verified server version provides a suitable exact newest-completed-tree lookup yet.
70
154
 
71
- It may seem strange that we are advocating for small context windows in a product called Claude Code Infinite. But Infinite is referring to the size of a new memory layer, the MemTree, which is a layer above the context window. This layer is larger and updated more slowly than the LLMs main input, just as disk is larger + slower than RAM.
155
+ `/memtree-view` emits OSC 8 hyperlinks only when the ccc proxy has a TTY and detects iTerm2 3.1+ (`TERM_PROGRAM=iTerm.app` plus its version), JetBrains (`TERMINAL_EMULATOR=JetBrains-JediTerm`), Ghostty, WezTerm, VS Code, kitty, or Windows Terminal (`WT_SESSION`). Detection happens in the proxy, before hook output is relayed through pipes. `NO_COLOR`, dumb/unknown terminals, tmux/screen, and non-TTY output get a plain URL. Apple Terminal also gets the full visible URL for its normal URL handling. The success and Stop notices retain their visible URLs, and a late lookup cannot repeat the same page already linked by that turn's success notice.
72
156
 
73
- So you can think of MemTree as an operating system's virtual memory manager. Just as an OS manages RAM by swapping less-used data to disk, MemTree manages the model's context window by intelligently recalling only the most relevant information from past interactions. This ensures that the model always has access to the most pertinent data without being overwhelmed by the entire history of the conversation.
157
+ **The end of a session.** MemTree indexes once about 10k new tokens build up, and each index call carries a request's messages, so a session's last turns, including the reply it ended with (often where a PR was opened), would never be indexed. `ccc` therefore sends one final index call per session: the main conversation plus that last reply, read from Claude Code's transcript, flagged so MemTree indexes the tail even below its minimum (at most once per conversation per 30 minutes; two conversations with an identical first message share that allowance). It goes out 10 minutes after a turn ends with no new request, or at exit for the 3 most recent sessions, inside the 2-second background drain; `-p` and non-TTY runs, which have no `Stop` hook, get it at exit. A failure is logged and never changes `ccc`'s exit code. Until then, `read_lines` with `"tail": true` reads the messages after the tree verbatim. When `ccc` runs inside another Claude Code session, the inner Claude Code writes no transcript unless `CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1` is set; without it the final index still covers everything through the last request, just not the final reply.
74
158
 
159
+ `ccc` installs a minimal session-only Claude Code plugin using the repeatable `--plugin-dir` option. Its `MessageDisplay` hook changes only what the terminal renders and never alters stored assistant content; a `Stop` hook supplies a fallback for tool-only responses. That fallback may be saved by Claude Code as non-model hook UI metadata, but it is excluded from resumed model and recap requests. Notices are never added to model context, and Anthropic responses are left untouched with one exception: the hidden recap request Claude Code sends when you come back to an idle session gets a final `• MemTree` / `<url>` text block, so the recap ends with the link (not added when `CCC_MEMTREE_LINK=off`). Claude Code keeps that recap as a UI-only system entry, never as conversation content, and the block is skipped when it would push the recap past Claude Code's 400-character cap, and `-p`/non-TTY output is left unchanged. Legacy marker cleanup remains for transcripts created by older `ccc` releases. The payment state can also produce a separate terminal warning at startup.
160
+
161
+ Claude Code currently displays the original assistant text instead of `MessageDisplay` replacements while verbose mode is enabled. Turn verbose mode off to see the inline MemTree line.
162
+
163
+ ### Memory tools
164
+
165
+ Interactive `ccc` sessions also get a small MCP server, `memtree`, with four read-only tools (see [Search across sessions](#search-across-sessions)): `mcp__memtree__search` (`query`; optional `tree`, `"current"` for this session or omitted for all of your sessions; `mode` `text` (default, free) or `vector` (meaning, first page charged); `project`, `since`, `until`, `cursor`, `limit` (default 10, at most 50)), `mcp__memtree__read_node` (`node`, an address `<tree>#<id>` from search, or `id` with optional `tree`: summary, path from the root, children, leaf line range), `mcp__memtree__read_lines` (a leaf's `node`, or `block`, `start`, `end` with optional `tree`: exact transcript lines, at most 400 lines / 40k characters per call) (with `tail: true` instead, the newest messages after the tree that are not indexed yet) and `mcp__memtree__list` (your sessions, most recently active first; `since`, `until`, `project`, `q`, `cursor`, `limit`). Other sessions' trees are named explicitly by reference and authorized by your MemTree key. This session's tree (`tree: "current"`, or no `tree` on `read_node`/`read_lines`) is the one the current request was served from: the server asks the `ccc` proxy for `GET $ANTHROPIC_BASE_URL/memtree/current?session=<id>` (only that session's compression source page, or its stored source page on resume; `current.json?session=<id>` returns the page itself). Completed pages are cached. If the proxy temporarily serves an older prefix while a newer tree is indexing, the tools reuse that prefix during a 45-second cooldown, then check the page again. `ccc` registers it with `--mcp-config=<temp file>`, which keeps working under `--strict-mcp-config`, and pre-allows the four read-only tools; `CCC_MEMTREE_MCP=0` turns it off. The MCP process requires `CLAUDE_CODE_SESSION_ID` and checks that both the pointer and page belong to it. If Claude Code retains an MCP process with an old session ID after `/clear` or a session switch, reconnect the MCP server with the current session ID; it cannot infer a new identity from another session using the proxy. Print (`-p`) and non-TTY runs get it only when their own `--mcp-config` names a server `memtree` run by `ccc memtree-mcp` (add `--allowedTools mcp__memtree__search,mcp__memtree__read_node,mcp__memtree__read_lines,mcp__memtree__list`). Whenever the tools are configured, compress calls carry `x-memtree-tools: search,read_node,read_lines,list` so MemTree tells the model how to use them.
166
+
167
+ ## How it works
168
+
169
+ <table><tr><td>
170
+ <img width="1050" height="445" alt="image" src="https://github.com/user-attachments/assets/d1ab2456-9a64-4118-a72a-b9d133c7c8bd" />
171
+ </td></tr></table>
172
+
173
+
174
+ When you send a message, we retrieve relevant details and summaries from the prior messages in your thread. These details and summaries populate a **memory message**. Following the memory message, we append a compressed version of your recent message history. The resulting context-window is dramatically smaller, allowing Claude to process your request with much greater efficacy, lower latency, and reduced cost.
175
+
176
+ **Compaction keeps a stable prefix.** While a conversation fits the model's budget (800k tokens for Opus 5.5), it is sent whole. When a request reaches the budget, `ccc` compresses it once, to half the budget (or your `/memtree-compact N`), and keeps those compressed bytes as the session's prefix. Every later request, human turns included, is that same prefix followed by the messages that came after it, verbatim, with no compression call (MemTree still indexes in the background). Because the prefix does not change, Anthropic reads it from the prompt cache on every turn after the one that compacted, instead of writing the whole compressed context again. `ccc` compresses again only when prefix plus newer turns reach the budget (measured from the size Anthropic reported for the previous request, plus an estimate for what was added), or when the messages the prefix stands for change (rewind, edit, fork, `/clear`). If that recompression fails, the turn is sent on the old prefix rather than the whole history. The budget is the one MemTree reports for the model; until the server reports one, `ccc` uses 80% of the model's context window. `CCC_BUDGET_TOKENS=60k` overrides it for testing. Every `requests.jsonl` line carries the `pid` of the `ccc` process that wrote it, which tells apart concurrent sessions sharing the log. The same rule holds inside a tool loop, so a long single turn (a headless `-p` run, or a long agentic loop) is compacted as it grows rather than only when you next type: each tool request under the budget goes out with no compression call, the one that reaches it is compressed once and becomes the stable prefix, and later tool requests and your next message ride that prefix until it reaches the budget again. `requests.jsonl` shows the `compaction` of each main-thread human turn and of every tool turn (budget, its source, the size estimate, and the reason for a compaction: `budget`, `prefix-mismatch`, `manual` or `target-change`); a human turn sent on the stable prefix is `turnType: "followup-prefix"`, a tool turn `tool-memory` (on its route) or `tool-prefix` (on the prefix directly), and a tool turn that compacted `tool-recompressed`.
177
+
178
+ That compressed context stays in force for the rest of the turn: the tool loop it kicks off, and the matching Count Tokens calls, are routed through the same compressed prefix. The Count Tokens part matters — Claude Code sizes its context from those replies, so counting the uncompressed history would make it auto-compact a conversation memory had already shrunk. A mismatched or resumed conversation shape drops the route rather than grafting one session's prefix onto another.
179
+
180
+ Compressed prefixes are stored per *lane*, not in a single slot: the main thread, each subagent, and the away-summary side channel each key their own route by session and identity. Every lane gets the same deal — it installs its own prefix and rides it — and lanes cannot evict one another, so a subagent or a background side request can no longer strand the main conversation on full history.
181
+
182
+ Programs you run from inside a `ccc` session (scripts, test suites, SDK apps) inherit its `ANTHROPIC_BASE_URL`, so their Anthropic calls reach `ccc` too. `ccc` handles only Claude Code's own requests, which always carry an `X-Claude-Code-Session-Id` header. Anything else is forwarded to Anthropic unchanged: it is never compressed or indexed on MemTree, and it cannot disturb the session's compressed history. These requests are logged in `requests.jsonl` as `turnType: "foreign"` with the start of their `User-Agent`. `CCC_CLAUDE_CODE_ONLY=0` turns the filter off.
183
+
184
+ Subagents follow the same budget rule in their own tool loops: under the budget a subagent's tool request goes out as it is, and the one that reaches it is compressed once and becomes that subagent's route, which its later tool requests ride. A tool-turn compression is a blocking MemTree call and a soft fuse, not a hard cap: if MemTree is unavailable or returns nothing usable within its normal compression budget, the request goes out on the old prefix, or whole when there is none; a failed call puts compression on a brief cooldown, so an outage costs one stall rather than one per tool call, and a lane whose attempt produced nothing waits until its history has grown by a twentieth of the budget before trying again. An attempt that finds nothing indexed yet (a no-op with no tree) makes the lane skip compression calls until MemTree reports that tree built; a request that would overflow the context window still tries. A request with no session id is compressed without installing, since a route that cannot be matched later must not be stored. `/memtree-compact off` and `CCC_COMPACT_TARGET=off` pass tool requests through with no compression call. `CCC_TOOL_ROUTE_RECOVERY=0` turns off tool-turn compression for one invocation (tool requests make no size check and no compression call; human turns still compact); outcomes are recorded in `~/.claude-code-infinite/logs/requests.jsonl` under `routeLane`/`routeMiss`/`routeRecovery` (`compressed`, `noop`, `failed`, `no-gain`, `cooldown`, `in-flight`, `backoff`, `awaiting-index`, `disabled`, ...).
185
+
186
+ Routing decisions, per-turn timings and usage, recovery outcomes, and delivery status are recorded in `~/.claude-code-infinite/logs/requests.jsonl`.
187
+
188
+ The compression budget is a trigger to try MemTree, not a hard forwarding limit.
189
+ If compression is unavailable, `ccc` sends the available request when its input
190
+ plus output reservation fits the model's native window. If Count Tokens fails or
191
+ is disabled, a learned estimate alone cannot block the turn: the final check uses
192
+ the plain bytes-based estimate. Such a fallback can still be rejected by the
193
+ upstream API, which makes the final tokenization decision. An `overBudgetForward`
194
+ field in the request log marks requests forwarded above the compression budget.
195
+
196
+ Sizing belongs to the individual request. Original bodies, validated compressed
197
+ prefixes, and replacement bodies each retain their own estimate and count result;
198
+ the compression planner, `client_input_tokens`, and final selection consult that
199
+ shared record. A fitting compressed candidate is preferred, with the original as
200
+ a fallback. If counting is unavailable, candidate selection uses the conservative
201
+ bytes-based input estimate plus the requested output reservation against the
202
+ model's native window. Refusal requires that no eligible candidate fits; a failed
203
+ replacement cannot discard an original or prefix that still fits.
204
+
205
+ `ccc` uses Anthropic Count Tokens selectively to check uncertain sizes and before
206
+ refusing an estimated overflow. Counts describe the particular body being sent,
207
+ including a compressed body when applicable. A request shares a three-second
208
+ counting allowance across its checks; authentication failures and rate limits
209
+ put counting on a bounded cooldown. `CCC_COUNT_TOKENS=0` disables these checks.
210
+ The `countTokens` array in each request-log record lists the status class and
211
+ elapsed milliseconds of each actual attempt, without bodies or credentials.
212
+ If counting an assembled compressed prefix discovers that it is over budget,
213
+ `ccc` makes at most one bounded recovery compression attempt before deciding
214
+ whether to forward or refuse it. The request log records that decision under
215
+ `lateCountRecovery.outcome` as `compressed`, `forwarded`, or `refused`.
216
+
217
+ Before publishing a release, verify Claude subscription OAuth counting in a live
218
+ session: launch this checkout's `node dist/cli.js --resume` with the normal Claude
219
+ subscription login and resume a large conversation. After sending a turn, inspect
220
+ the new `countTokens` entries in the request log for that process's `pid`. A
221
+ successful count confirms acceptance; an authentication failure should leave the
222
+ turn on the bytes-based fallback and suppress further counts during cooldown.
223
+ An absent entry means no count was needed, so that turn does not verify OAuth
224
+ acceptance. Mock tests verify header forwarding and failure behavior; they do not
225
+ establish that Anthropic accepts subscription OAuth for this endpoint.
226
+
227
+ Memory quality is evaluated offline: the weekly `memtree-bench` harness replays a fixed scenario through a ccc-wrapped arm and a vanilla Claude Code arm and grades complete outcomes blind against an answer key. (An earlier in-request memory-vs-full A/B comparison with a live grader was removed in 2026-08; it was off by default, and the offline benchmark measures the same question with a stronger instrument.)
228
+
229
+ ## More than a memory-retrieval tool
230
+
231
+ Search tools alone don't keep a long session's context from growing until the model degrades.
232
+ Claude Code Infinite does both: it indexes and searches your history, and it manages what the model
233
+ sees, keeping it within budget. It works alongside your MCP servers, tools and other Claude Code
234
+ features, and their output, often the biggest source of tokens, is exactly what MemTree reduces most.
75
235
 
76
236
  ## Usage Tips
77
237
 
@@ -85,20 +245,27 @@ So you can think of MemTree as an operating system's virtual memory manager. Jus
85
245
 
86
246
  * You can resume previous threads with `/resume`
87
247
 
248
+ For `ccc --resume <id>`, an automatic, best-effort repair can unblock a transcript whose last response already exceeds Claude Code’s local refusal limit. It changes only that response’s usage totals and the iteration Claude Code counts, after writing an exclusive, uniquely named backup under `~/.claude-code-infinite/transcript-backups`. Earlier iterations and conversation content stay intact. The installed Claude Code 2.1.288 computes its blocking limit as the context window minus the output allowance (capped at 20,000) minus 3,000 tokens: 977,000 for the supported 1M models. Other versions use the conservative full-window threshold. The window comes from ccc’s model table, explicit `[1m]` transcript model signals, and any `--model` or `ANTHROPIC_MODEL` override, conservatively keeping the largest supported window. A successful response with input already beyond that inferred window disproves the inference and is never repaired. Unknown model overrides (including unresolved aliases), models or usage shapes, threshold overrides, possible running writers, changed snapshots, backup collisions, and filesystem errors all skip repair and continue the original resume. Process and file checks are bounded; other running JavaScript runtimes may conservatively prevent repair.
249
+
88
250
 
89
251
  ## Troubleshooting
90
252
 
91
- ### 401 {"detail":"Your session has expired or the token is invalid. Please sign in again."}
253
+ ### Anthropic auth errors (401s, login prompts)
92
254
 
93
- This happens if you chose "Anthropic Console account" during setup instead of using an Anthropic subscription.
255
+ `ccc` never touches your Anthropic credentials — Claude Code manages its own login exactly as it does without `ccc`. If you see auth errors, fix them the vanilla way: run `/login` inside Claude Code (or `claude` directly) and re-authenticate.
94
256
 
95
- Fix:
257
+ ### MemTree degraded / passthrough mode
96
258
 
97
- 1. Run: `/logout`
98
- 2. Re-run `ccc`
259
+ If you see an inline "⚠ MemTree degraded — this turn ran uncompressed" notice, the compression API is unreachable or your MemTree key is invalid/expired. Your session keeps working uncompressed. Check your key at [polychat.co](https://polychat.co/auth?memtree=true), or delete it from `~/.claude-code-infinite/config.json` and re-run `ccc` to re-enter it.
99
260
 
100
- This logs you out of your Anthropic Console account and keeps you in `ccc` which uses PolyChat. If you'd like to use your own API key with PolyChat, login to polychat.co and head over to our [BYOK settings](https://app.polychat.co/api-keys).
261
+ ## Upgrading
101
262
 
102
- ## API Rate limit errors
263
+ `ccc` checks npm at startup (bounded to two seconds, silent if offline) and, when a
264
+ newer release exists, shows one line under the MemTree banner inside Claude Code with
265
+ the upgrade command. Upgrading is never automatic:
266
+
267
+ ```bash
268
+ npm install -g claude-code-infinite
269
+ ```
103
270
 
104
- If you hit your Anthropic subscription's rate limits, you can still continue by running `/logout` and restarting `ccc`. This will bill tokens through your PolyChat subscription. Remember that you can use the `/resume` slash command to resume previous sessions.
271
+ Set `CCC_SKIP_UPDATE_CHECK=1` to disable the check (air-gapped or CI runs).
@@ -0,0 +1,94 @@
1
+ import type { ProjectMeta } from "./project-meta.js";
2
+ /** Characters of system prompt kept for auditing a suspected side request. */
3
+ export declare const SYSTEM_HEAD_CHARS = 120;
4
+ export interface ClaudeCodeRequestInfo {
5
+ /** A Claude Code billing header was found in the system prompt. */
6
+ billingHeader: boolean;
7
+ ccVersion?: string;
8
+ entrypoint?: string;
9
+ /** `cc_turn_origin`, e.g. "human"; absent on side requests seen so far. */
10
+ turnOrigin?: string;
11
+ /** `cc_prompt_id` was present (the value itself is not logged). */
12
+ promptId: boolean;
13
+ /** Number of tool definitions sent; the main agent always sends its tools. */
14
+ tools: number;
15
+ /**
16
+ * Header present but no turn origin: the shape of the security monitor.
17
+ * Logged only, for now.
18
+ */
19
+ suspectedSideRequest: boolean;
20
+ /**
21
+ * Opening of the system prompt after the header, one line, only for a
22
+ * suspected side request (e.g. "You are a security monitor for autonomous
23
+ * AI coding agents."). Local log only.
24
+ */
25
+ systemHead?: string;
26
+ /**
27
+ * For a request with a header but no turn origin: the opening of its last
28
+ * user message (reminders removed), its message count, and a short hash of
29
+ * the session id. Recaps, /btw and background-notification turns share the
30
+ * main system prompt and differ only here. Local log only.
31
+ */
32
+ lastUserHead?: string;
33
+ messageCount?: number;
34
+ sessionTag?: string;
35
+ }
36
+ export declare function describeClaudeCodeRequest(body: Record<string, any>): ClaudeCodeRequestInfo;
37
+ /** A short, non-reversible tag grouping requests of one session in the log. */
38
+ export declare function sessionTag(sessionId: string | undefined): string | undefined;
39
+ /**
40
+ * Claude Code's own side request (the auto-mode security monitor): a billing
41
+ * header, no turn origin, and no tools. The proxy forwards these untouched
42
+ * and never involves MemTree or the main thread's route state.
43
+ */
44
+ export declare function isClaudeCodeSideRequest(info: ClaudeCodeRequestInfo): boolean;
45
+ /**
46
+ * Shape of the monitor's `<transcript>` block, checked on every side request
47
+ * so a change in Claude Code's internal format shows up in the request log
48
+ * before anything relies on it. Observed on 2.1.281: one user message holding
49
+ * `<transcript>`, then one single-key JSON object per line (`{"user": ...}`,
50
+ * `{"<ToolName>": <input>}`, `{"meta": ...}`), then `</transcript>` and the
51
+ * grading instructions.
52
+ */
53
+ export interface TranscriptShape {
54
+ ok: boolean;
55
+ /** Why the shape was not recognised; absent when ok. */
56
+ reason?: "no-transcript" | "unclosed" | "bad-line" | "empty";
57
+ lines: number;
58
+ userLines: number;
59
+ toolLines: number;
60
+ metaLines: number;
61
+ /** For a bad line: its 1-based index inside the block and its opening. */
62
+ badLine?: number;
63
+ sample?: string;
64
+ }
65
+ export declare function inspectMonitorTranscript(body: Record<string, any>): TranscriptShape;
66
+ /**
67
+ * What the MemTree server stores about a request's client (usage_requests
68
+ * .client_meta), sent as one `x-client-meta` JSON header. Only short
69
+ * printable-ASCII values go out, so the header is always valid.
70
+ */
71
+ export interface MemtreeClientMeta {
72
+ claude_code_version?: string;
73
+ entrypoint?: string;
74
+ turn_origin?: string;
75
+ lane?: string;
76
+ agent_id?: string;
77
+ parent_agent_id?: string;
78
+ requested_model?: string;
79
+ /** The session's project (project-meta.ts), for the sessions list's project filter. */
80
+ project_dir?: string;
81
+ git_repo?: string;
82
+ git_branch?: string;
83
+ git_commit?: string;
84
+ }
85
+ export declare function memtreeClientMeta(input: {
86
+ info?: ClaudeCodeRequestInfo;
87
+ lane?: string;
88
+ agentId?: string;
89
+ parentAgentId?: string;
90
+ model?: unknown;
91
+ /** Read once at startup (project-meta.ts); the same for every request. */
92
+ project?: ProjectMeta;
93
+ }): MemtreeClientMeta;
94
+ //# sourceMappingURL=cc-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cc-request.d.ts","sourceRoot":"","sources":["../src/cc-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmBrD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,mEAAmE;IACnE,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,qBAAqB,CAqC1F;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAE5E;AA6CD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAE5E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,wDAAwD;IACxD,MAAM,CAAC,EAAE,eAAe,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,eAAe,CAgCnF;AAWD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,iBAAiB,CAmBpB"}
@@ -0,0 +1,194 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * What Claude Code says about a request in its billing header, for telling
4
+ * the main conversation apart from Claude Code's side requests (the auto-mode
5
+ * security monitor, recaps, titles, …).
6
+ *
7
+ * Claude Code puts `x-anthropic-billing-header: cc_version=…; cc_entrypoint=…;
8
+ * …` as the first system text block. Observed on 2.1.281: the main thread's
9
+ * requests carry `cc_turn_origin=human;` and `cc_prompt_id=…;`, while the
10
+ * security monitor's request carries neither.
11
+ *
12
+ * The request log (2026-09-24, 112 requests) showed a header without a turn
13
+ * origin also on recaps and on background-notification turns, which carry
14
+ * the full tool list and must keep MemTree. Only the security monitor sends
15
+ * zero tools, so ``isClaudeCodeSideRequest`` requires all three.
16
+ */
17
+ const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
18
+ /** Characters of system prompt kept for auditing a suspected side request. */
19
+ export const SYSTEM_HEAD_CHARS = 120;
20
+ export function describeClaudeCodeRequest(body) {
21
+ const texts = systemTexts(body);
22
+ let fields;
23
+ const rest = [];
24
+ for (const text of texts) {
25
+ if (!fields && text.startsWith(BILLING_HEADER_PREFIX)) {
26
+ const parsed = parseHeader(text.slice(BILLING_HEADER_PREFIX.length));
27
+ fields = parsed.fields;
28
+ if (parsed.rest.trim())
29
+ rest.push(parsed.rest);
30
+ }
31
+ else {
32
+ rest.push(text);
33
+ }
34
+ }
35
+ const tools = Array.isArray(body?.tools) ? body.tools.length : 0;
36
+ const info = {
37
+ billingHeader: fields !== undefined,
38
+ promptId: fields?.cc_prompt_id !== undefined,
39
+ tools,
40
+ suspectedSideRequest: fields !== undefined && fields.cc_turn_origin === undefined,
41
+ };
42
+ if (fields?.cc_version)
43
+ info.ccVersion = fields.cc_version;
44
+ if (fields?.cc_entrypoint)
45
+ info.entrypoint = fields.cc_entrypoint;
46
+ if (fields?.cc_turn_origin)
47
+ info.turnOrigin = fields.cc_turn_origin;
48
+ if (info.suspectedSideRequest) {
49
+ const head = oneLine(rest.join(" ")).slice(0, SYSTEM_HEAD_CHARS);
50
+ if (head)
51
+ info.systemHead = head;
52
+ const messages = Array.isArray(body?.messages) ? body.messages : [];
53
+ info.messageCount = messages.length;
54
+ const lastUser = [...messages].reverse().find((m) => m?.role === "user");
55
+ if (lastUser) {
56
+ const text = oneLine(messageText(lastUser).replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, " ")).slice(0, SYSTEM_HEAD_CHARS);
57
+ info.lastUserHead = text || `[${lastUserPartTypes(lastUser)}]`;
58
+ }
59
+ }
60
+ return info;
61
+ }
62
+ /** A short, non-reversible tag grouping requests of one session in the log. */
63
+ export function sessionTag(sessionId) {
64
+ return sessionId ? createHash("sha256").update(sessionId).digest("hex").slice(0, 8) : undefined;
65
+ }
66
+ function lastUserPartTypes(message) {
67
+ const content = message?.content;
68
+ if (!Array.isArray(content))
69
+ return typeof content;
70
+ return [...new Set(content.map((p) => p?.type ?? typeof p))].join(",");
71
+ }
72
+ /** Text of the top-level `system` value, then of leading `role: system` messages. */
73
+ function systemTexts(body) {
74
+ const out = [];
75
+ const add = (value) => {
76
+ if (typeof value === "string")
77
+ out.push(value);
78
+ else if (Array.isArray(value)) {
79
+ for (const item of value) {
80
+ if (typeof item === "string")
81
+ out.push(item);
82
+ else if (item && typeof item === "object" && typeof item.text === "string")
83
+ out.push(item.text);
84
+ }
85
+ }
86
+ };
87
+ add(body?.system);
88
+ const messages = Array.isArray(body?.messages) ? body.messages : [];
89
+ for (const message of messages) {
90
+ if (message?.role !== "system")
91
+ break;
92
+ add(message.content);
93
+ }
94
+ return out;
95
+ }
96
+ /** `key=value;` pairs from the start of the header; whatever follows is prompt text. */
97
+ function parseHeader(text) {
98
+ const fields = {};
99
+ const pair = /^\s*([a-z_]+)=([^;\n]*);/;
100
+ let remaining = text;
101
+ for (let match = pair.exec(remaining); match; match = pair.exec(remaining)) {
102
+ fields[match[1]] = match[2].trim();
103
+ remaining = remaining.slice(match[0].length);
104
+ }
105
+ return { fields, rest: remaining };
106
+ }
107
+ function oneLine(text) {
108
+ return text.replace(/\s+/g, " ").trim();
109
+ }
110
+ /**
111
+ * Claude Code's own side request (the auto-mode security monitor): a billing
112
+ * header, no turn origin, and no tools. The proxy forwards these untouched
113
+ * and never involves MemTree or the main thread's route state.
114
+ */
115
+ export function isClaudeCodeSideRequest(info) {
116
+ return info.billingHeader && info.turnOrigin === undefined && info.tools === 0;
117
+ }
118
+ const TRANSCRIPT_OPEN = "<transcript>";
119
+ const TRANSCRIPT_CLOSE = "</transcript>";
120
+ const SAMPLE_CHARS = 80;
121
+ export function inspectMonitorTranscript(body) {
122
+ const shape = { ok: false, lines: 0, userLines: 0, toolLines: 0, metaLines: 0 };
123
+ const messages = Array.isArray(body?.messages) ? body.messages : [];
124
+ const text = messages
125
+ .filter((m) => m?.role === "user")
126
+ .map((m) => messageText(m))
127
+ .find((t) => t.includes(TRANSCRIPT_OPEN));
128
+ if (text === undefined)
129
+ return { ...shape, reason: "no-transcript" };
130
+ const start = text.indexOf(TRANSCRIPT_OPEN) + TRANSCRIPT_OPEN.length;
131
+ const end = text.indexOf(TRANSCRIPT_CLOSE, start);
132
+ if (end < 0)
133
+ return { ...shape, reason: "unclosed" };
134
+ const lines = text.slice(start, end).split("\n");
135
+ for (let i = 0; i < lines.length; i++) {
136
+ const line = lines[i].trim();
137
+ if (!line)
138
+ continue;
139
+ let parsed;
140
+ try {
141
+ parsed = JSON.parse(line);
142
+ }
143
+ catch {
144
+ parsed = undefined;
145
+ }
146
+ const keys = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? Object.keys(parsed) : [];
147
+ if (keys.length !== 1) {
148
+ return { ...shape, reason: "bad-line", badLine: i + 1, sample: oneLine(line).slice(0, SAMPLE_CHARS) };
149
+ }
150
+ shape.lines++;
151
+ if (keys[0] === "user")
152
+ shape.userLines++;
153
+ else if (keys[0] === "meta")
154
+ shape.metaLines++;
155
+ else
156
+ shape.toolLines++;
157
+ }
158
+ if (shape.lines === 0)
159
+ return { ...shape, reason: "empty" };
160
+ return { ...shape, ok: true };
161
+ }
162
+ function messageText(message) {
163
+ const content = message?.content;
164
+ if (typeof content === "string")
165
+ return content;
166
+ if (!Array.isArray(content))
167
+ return "";
168
+ return content
169
+ .map((p) => (typeof p === "string" ? p : p?.type === "text" && typeof p.text === "string" ? p.text : ""))
170
+ .join("");
171
+ }
172
+ const META_VALUE = /^[\x20-\x7e]{1,128}$/;
173
+ export function memtreeClientMeta(input) {
174
+ const candidate = {
175
+ project_dir: input.project?.project_dir,
176
+ git_repo: input.project?.git_repo,
177
+ git_branch: input.project?.git_branch,
178
+ git_commit: input.project?.git_commit,
179
+ claude_code_version: input.info?.ccVersion,
180
+ entrypoint: input.info?.entrypoint,
181
+ turn_origin: input.info?.turnOrigin,
182
+ lane: input.lane,
183
+ agent_id: input.agentId,
184
+ parent_agent_id: input.parentAgentId,
185
+ requested_model: input.model,
186
+ };
187
+ const meta = {};
188
+ for (const [key, value] of Object.entries(candidate)) {
189
+ if (typeof value === "string" && META_VALUE.test(value))
190
+ meta[key] = value;
191
+ }
192
+ return meta;
193
+ }
194
+ //# sourceMappingURL=cc-request.js.map