claude-code-infinite 1.0.21 → 1.0.22
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.
- package/README.md +74 -24
- package/dist/claude-env.d.ts +14 -1
- package/dist/claude-env.d.ts.map +1 -1
- package/dist/claude-env.js +17 -1
- package/dist/claude-env.js.map +1 -1
- package/dist/cli-lifecycle.d.ts +4 -0
- package/dist/cli-lifecycle.d.ts.map +1 -1
- package/dist/cli-lifecycle.js +18 -0
- package/dist/cli-lifecycle.js.map +1 -1
- package/dist/cli-main.js +118 -18
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/count-tokens.d.ts.map +1 -1
- package/dist/count-tokens.js +7 -1
- package/dist/count-tokens.js.map +1 -1
- package/dist/diagnostics-schema.d.ts +44 -0
- package/dist/diagnostics-schema.d.ts.map +1 -0
- package/dist/diagnostics-schema.js +162 -0
- package/dist/diagnostics-schema.js.map +1 -0
- package/dist/diagnostics.d.ts +116 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +316 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/first-tree.d.ts +11 -0
- package/dist/first-tree.d.ts.map +1 -0
- package/dist/first-tree.js +45 -0
- package/dist/first-tree.js.map +1 -0
- package/dist/hooks.d.ts +37 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +69 -9
- package/dist/hooks.js.map +1 -1
- package/dist/memtree-links.d.ts +2 -0
- package/dist/memtree-links.d.ts.map +1 -1
- package/dist/memtree-links.js +4 -0
- package/dist/memtree-links.js.map +1 -1
- package/dist/memtree.d.ts +7 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +8 -0
- package/dist/memtree.js.map +1 -1
- package/dist/prefix-store.js +3 -0
- package/dist/prefix-store.js.map +1 -1
- package/dist/proxy.d.ts +28 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +63 -7
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +17 -1
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js.map +1 -1
- package/dist/resume-hint.d.ts +25 -0
- package/dist/resume-hint.d.ts.map +1 -0
- package/dist/resume-hint.js +36 -0
- package/dist/resume-hint.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,10 +17,27 @@ follows it down to the original messages.
|
|
|
17
17
|
* **Search across every session.** Claude can find what was decided, tried or ruled out in any of
|
|
18
18
|
your past sessions, by exact words or by meaning, filtered by project and date
|
|
19
19
|
* **Browse each session's MemTree** in your browser, from the link `ccc` prints in the terminal
|
|
20
|
-
* **
|
|
21
|
-
full
|
|
22
|
-
([details](#long-session-benchmark))
|
|
20
|
+
* **Never loses anything vs Claude Code.** Sessions are sent word for word until near the context
|
|
21
|
+
limit; past it, MemTree matched full context (69 vs 69) and beat `/compact` by 20 ([benchmark](#long-session-benchmark))
|
|
23
22
|
* **Unlimited-length sessions**, with relevant past information recalled automatically
|
|
23
|
+
* **A local proxy, so no slowdown.** Requests go straight from your machine to Anthropic and
|
|
24
|
+
MemTree indexes in the background; only the occasional compression step, when a session
|
|
25
|
+
outgrows the context budget, waits on MemTree
|
|
26
|
+
|
|
27
|
+
The magic starts past the context limit. Keep one session going for days and it learns how you
|
|
28
|
+
work: your priorities, the nuances of your projects, your company and your environment. Where
|
|
29
|
+
`/compact` would wipe that detail, MemTree keeps it, so the thread becomes an expert in you. Let it
|
|
30
|
+
orchestrate dozens of subagents on your behalf.
|
|
31
|
+
|
|
32
|
+
> [!IMPORTANT]
|
|
33
|
+
> Requires a Claude Pro or Max subscription.
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<picture>
|
|
37
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/crizCraig/claude-code-infinite/main/docs/images/benchmark-dark.svg">
|
|
38
|
+
<img width="600" alt="Long-session benchmark, judged score out of 100: Claude Code Infinite 69, full context 69, /compact 49" src="https://raw.githubusercontent.com/crizCraig/claude-code-infinite/main/docs/images/benchmark-light.svg">
|
|
39
|
+
</picture>
|
|
40
|
+
</p>
|
|
24
41
|
|
|
25
42
|
## Search across sessions
|
|
26
43
|
|
|
@@ -67,14 +84,11 @@ tokens; `/compact`'s exclude the one-time `/compact` step. This is one task fami
|
|
|
67
84
|
|
|
68
85
|
* [node.js 20.3 or newer](https://nodejs.org/en/download/)
|
|
69
86
|
* [Claude Code (the terminal version)](https://code.claude.com/docs/en/quickstart)
|
|
70
|
-
* **Claude
|
|
87
|
+
* **A Claude Pro or Max subscription**
|
|
71
88
|
|
|
72
|
-
|
|
89
|
+
<sub>Also works with an Anthropic API key, but that's far costlier than Pro or Max.</sub>
|
|
73
90
|
|
|
74
|
-
|
|
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.
|
|
91
|
+
## Setup
|
|
78
92
|
|
|
79
93
|
1. Install (Node ≥ 20.3; no git needed)
|
|
80
94
|
```bash
|
|
@@ -96,6 +110,32 @@ The tool supports multiple environments (this selects the MemTree compression AP
|
|
|
96
110
|
|
|
97
111
|
Each environment maintains its own separate API key.
|
|
98
112
|
|
|
113
|
+
## Commands
|
|
114
|
+
|
|
115
|
+
Type these inside a `ccc` session (the `ccc:` prefix is optional). `ccc` answers them
|
|
116
|
+
locally, so they cost no model turn.
|
|
117
|
+
|
|
118
|
+
| Command | What it does |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| `/ccc:memtree` | Lists the MemTree commands and links your usage dashboard. |
|
|
121
|
+
| `/ccc:memtree-view` | Shows the link to this session's MemTree page. |
|
|
122
|
+
| `/ccc:memtree-compact [tokens]` | Compacts the session on your next message (to half the budget, or a target such as `400k`) and keeps the compressed history. |
|
|
123
|
+
| `/ccc:memtree-compact off` | Goes back to sending the conversation whole. |
|
|
124
|
+
|
|
125
|
+
Environment variables, set when launching `ccc`:
|
|
126
|
+
|
|
127
|
+
| Variable | What it does |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `CCC_COMPACT_TARGET=500k` | Compaction target for every session (useful for headless `-p` runs); `off` starts every session with compaction off. |
|
|
130
|
+
| `CCC_MEMTREE_LINK` | Where the MemTree link appears: `turn` (default, end of a turn with a new page), `message` (under every reply), `stop` (end of every turn), `success` (only after an optimized turn), or `off`. |
|
|
131
|
+
| `CCC_AUTO_COMPACT=1` | Restores Claude Code's own auto-compaction setting ([details](#claude-code-auto-compaction)). |
|
|
132
|
+
| `CCC_NO_UPDATE_CHECK=1` | Turns off the update check, and with it automatic updates ([details](#upgrading)). |
|
|
133
|
+
| `CCC_DISABLE_AUTOUPDATE=1` | Keeps the update notice but never installs ([details](#automatic-updates)). |
|
|
134
|
+
| `CCC_NO_DIAGNOSTICS=1` | Turns off diagnostics ([details](#diagnostics)). So do `DO_NOT_TRACK=1`, Claude Code's `DISABLE_TELEMETRY=1` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, and `"diagnostics": false` in `~/.claude-code-infinite/config.json`. |
|
|
135
|
+
|
|
136
|
+
Links are clickable in terminals that support OSC 8 hyperlinks (iTerm2, Ghostty, WezTerm,
|
|
137
|
+
kitty, VS Code, JetBrains, Windows Terminal); elsewhere you get the plain URL.
|
|
138
|
+
|
|
99
139
|
## Claude Code auto-compaction
|
|
100
140
|
|
|
101
141
|
`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.
|
|
@@ -105,6 +145,13 @@ above a fixed 100k-token threshold, even when the active model has a 1M window
|
|
|
105
145
|
and auto-compaction is disabled. `ccc` suppresses that resume-time
|
|
106
146
|
recommendation as well; it does not disable the manual `/compact` command.
|
|
107
147
|
|
|
148
|
+
With auto-compaction off, Claude Code still refuses a request itself, without
|
|
149
|
+
sending it, once its own estimate reaches the window minus 23k (977k on a 1M
|
|
150
|
+
window). MemTree compresses at 900k, so a single large tool round could reach that
|
|
151
|
+
refusal before `ccc` ever saw the request. `ccc` sets
|
|
152
|
+
`CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE` (unless you exported your own) so such a
|
|
153
|
+
request reaches the proxy, which compresses it mid-turn.
|
|
154
|
+
|
|
108
155
|
A session whose last response reached the verified local refusal threshold
|
|
109
156
|
(for example one sent whole while MemTree was unavailable) would otherwise be
|
|
110
157
|
refused locally with "Context limit reached" on every prompt after a resume.
|
|
@@ -141,24 +188,27 @@ Claude Code ──▶ localhost proxy (ccc)
|
|
|
141
188
|
- 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
189
|
- If MemTree is unreachable, slow, or your MemTree plan needs payment, `ccc` degrades to a transparent passthrough so your session is never interrupted.
|
|
143
190
|
|
|
144
|
-
###
|
|
145
|
-
|
|
146
|
-
In interactive sessions, `ccc` reports these MemTree states as display-only lines in Claude Code:
|
|
147
|
-
|
|
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. Both also end with a `• Usage` link to your usage dashboard on polychat (`/usage` on the same host as MemTree: `https://app.polychat.co/usage` in production, the staging or local host otherwise); it opens only with your browser signed in to polychat as the same account. `/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`.
|
|
152
|
-
|
|
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.
|
|
191
|
+
### Diagnostics
|
|
154
192
|
|
|
155
|
-
|
|
193
|
+
`ccc` sends a few diagnostics to MemTree so we can see why a session fails to start or stalls:
|
|
156
194
|
|
|
157
|
-
|
|
195
|
+
- versions (ccc, Claude Code, Node), how ccc was installed (npm, npx, linked), OS and CPU
|
|
196
|
+
architecture, terminal type and locale;
|
|
197
|
+
- lifecycle events (first run, sign-in prompt shown, key accepted or rejected, session start,
|
|
198
|
+
the MemTree-paused notice shown, update hint or background update result);
|
|
199
|
+
- errors as codes, status numbers, sizes and timings (an Anthropic 401, a MemTree 402, a
|
|
200
|
+
compression timeout).
|
|
158
201
|
|
|
159
|
-
|
|
202
|
+
Never sent: your prompts or any conversation content, file paths, project or repository names,
|
|
203
|
+
hostnames, usernames, environment values, keys or tokens, or error message text. Each event is
|
|
204
|
+
checked against a fixed allowlist on your machine before it is queued, and again by the server.
|
|
205
|
+
They are tied to your MemTree key when you have one, otherwise to a random install id in
|
|
206
|
+
`~/.claude-code-infinite/install_id`. Sending is batched in the background and never slows a
|
|
207
|
+
request; a failed send is dropped.
|
|
160
208
|
|
|
161
|
-
|
|
209
|
+
Turn diagnostics off with any of: `CCC_NO_DIAGNOSTICS=1`, `"diagnostics": false` in
|
|
210
|
+
`~/.claude-code-infinite/config.json`, `DO_NOT_TRACK=1`, or Claude Code's own
|
|
211
|
+
`DISABLE_TELEMETRY=1` / `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`.
|
|
162
212
|
|
|
163
213
|
### Memory tools
|
|
164
214
|
|
|
@@ -173,7 +223,7 @@ Interactive `ccc` sessions also get a small MCP server, `memtree`, with four rea
|
|
|
173
223
|
|
|
174
224
|
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
225
|
|
|
176
|
-
**Compaction keeps a stable prefix.** While a conversation fits the model's budget (
|
|
226
|
+
**Compaction keeps a stable prefix.** While a conversation fits the model's budget (900k tokens for Opus 5.5, where plain Claude Code would auto-compact at 967k), 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 prefix is also kept on disk, one file per session under `~/.claude-code-infinite/stable-prefixes/` (mode 600 in a private directory, at most 64 sessions and 256 MB, oldest dropped first), so the first turn after `ccc --resume` rides it instead of waiting on a fresh compression. A stored prefix belongs to the MemTree backend and key that wrote it (identified by a hash, never the key itself) and is never used with another. It is used only if the resumed history still matches what it replaced; a main-thread history that no longer matches discards it, while side requests (such as Claude Code's web-search helper) leave it alone. Only the main thread's prefix is stored, never a subagent's. The budget is the one MemTree reports for the model; until the server reports one, `ccc` uses 90% of the context window on Opus 5.5, Opus 5, Opus 4.8 and Fable 5 and 5.1 with a 1M window, and 80% otherwise. `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
227
|
|
|
178
228
|
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
229
|
|
package/dist/claude-env.d.ts
CHANGED
|
@@ -17,9 +17,22 @@
|
|
|
17
17
|
* DISABLE_AUTO_COMPACT. Suppress that ccc-inappropriate recommendation with an
|
|
18
18
|
* effectively unreachable finite threshold; manual `/compact` remains available.
|
|
19
19
|
*
|
|
20
|
+
* With auto-compaction off, Claude Code still refuses a request itself once
|
|
21
|
+
* its own estimate (last reported usage plus the new messages) reaches the
|
|
22
|
+
* window minus 23k (977k on a 1M window; 2.1.302, measured) and never sends
|
|
23
|
+
* it: "Prompt is too long", and every later message is refused the same way.
|
|
24
|
+
* MemTree compresses at a budget near the window (900k), so one large tool
|
|
25
|
+
* round could otherwise strand a session there before ccc sees the request.
|
|
26
|
+
* CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE lifts that refusal: the request reaches
|
|
27
|
+
* the proxy, which compresses it mid-turn or, failing that, refuses it with
|
|
28
|
+
* its own bounded retry/400. A value the user already exported is kept.
|
|
29
|
+
*
|
|
20
30
|
* `CCC_AUTO_COMPACT=1` is an escape hatch: remove ccc's override and let the
|
|
21
|
-
* user's native Claude Code setting decide whether auto-compaction is enabled
|
|
31
|
+
* user's native Claude Code setting decide whether auto-compaction is enabled
|
|
32
|
+
* (Claude Code's own limits then apply unchanged).
|
|
22
33
|
*/
|
|
34
|
+
/** Claude Code's local refusal limit under ccc: effectively never reached. */
|
|
35
|
+
export declare const UNREACHABLE_BLOCKING_LIMIT = "1000000000";
|
|
23
36
|
export declare function claudeChildEnv(parentEnv: NodeJS.ProcessEnv, anthropicBaseUrl: string): NodeJS.ProcessEnv;
|
|
24
37
|
/**
|
|
25
38
|
* Keep the proxy's MemTree budget in lockstep with Claude Code's own context
|
package/dist/claude-env.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-env.d.ts","sourceRoot":"","sources":["../src/claude-env.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"claude-env.d.ts","sourceRoot":"","sources":["../src/claude-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAEvD,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAAC,UAAU,CAuBnB;AAOD;;;;GAIG;AACH,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,MAAM,CAAC,UAAU,GAC3B,OAAO,CAET"}
|
package/dist/claude-env.js
CHANGED
|
@@ -17,9 +17,22 @@
|
|
|
17
17
|
* DISABLE_AUTO_COMPACT. Suppress that ccc-inappropriate recommendation with an
|
|
18
18
|
* effectively unreachable finite threshold; manual `/compact` remains available.
|
|
19
19
|
*
|
|
20
|
+
* With auto-compaction off, Claude Code still refuses a request itself once
|
|
21
|
+
* its own estimate (last reported usage plus the new messages) reaches the
|
|
22
|
+
* window minus 23k (977k on a 1M window; 2.1.302, measured) and never sends
|
|
23
|
+
* it: "Prompt is too long", and every later message is refused the same way.
|
|
24
|
+
* MemTree compresses at a budget near the window (900k), so one large tool
|
|
25
|
+
* round could otherwise strand a session there before ccc sees the request.
|
|
26
|
+
* CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE lifts that refusal: the request reaches
|
|
27
|
+
* the proxy, which compresses it mid-turn or, failing that, refuses it with
|
|
28
|
+
* its own bounded retry/400. A value the user already exported is kept.
|
|
29
|
+
*
|
|
20
30
|
* `CCC_AUTO_COMPACT=1` is an escape hatch: remove ccc's override and let the
|
|
21
|
-
* user's native Claude Code setting decide whether auto-compaction is enabled
|
|
31
|
+
* user's native Claude Code setting decide whether auto-compaction is enabled
|
|
32
|
+
* (Claude Code's own limits then apply unchanged).
|
|
22
33
|
*/
|
|
34
|
+
/** Claude Code's local refusal limit under ccc: effectively never reached. */
|
|
35
|
+
export const UNREACHABLE_BLOCKING_LIMIT = "1000000000";
|
|
23
36
|
export function claudeChildEnv(parentEnv, anthropicBaseUrl) {
|
|
24
37
|
const childEnv = {
|
|
25
38
|
...parentEnv,
|
|
@@ -36,6 +49,9 @@ export function claudeChildEnv(parentEnv, anthropicBaseUrl) {
|
|
|
36
49
|
}
|
|
37
50
|
else {
|
|
38
51
|
childEnv.DISABLE_AUTO_COMPACT = "1";
|
|
52
|
+
if (!childEnv.CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE) {
|
|
53
|
+
childEnv.CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE = UNREACHABLE_BLOCKING_LIMIT;
|
|
54
|
+
}
|
|
39
55
|
}
|
|
40
56
|
return childEnv;
|
|
41
57
|
}
|
package/dist/claude-env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-env.js","sourceRoot":"","sources":["../src/claude-env.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"claude-env.js","sourceRoot":"","sources":["../src/claude-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GAAG,YAAY,CAAC;AAEvD,MAAM,UAAU,cAAc,CAC5B,SAA4B,EAC5B,gBAAwB;IAExB,MAAM,QAAQ,GAAsB;QAClC,GAAG,SAAS;QACZ,kBAAkB,EAAE,gBAAgB;QACpC,wCAAwC,EAAE,GAAG;QAC7C,kCAAkC,EAAE,YAAY;KACjD,CAAC;IAEF,gEAAgE;IAChE,OAAO,QAAQ,CAAC,gBAAgB,CAAC;IAEjC,IAAI,SAAS,CAAC,gBAAgB,KAAK,GAAG,EAAE,CAAC;QACvC,oEAAoE;QACpE,iCAAiC;QACjC,OAAO,QAAQ,CAAC,oBAAoB,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,oBAAoB,GAAG,GAAG,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,mCAAmC,EAAE,CAAC;YAClD,QAAQ,CAAC,mCAAmC,GAAG,0BAA0B,CAAC;QAC5E,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,cAAc,CAAC,KAAyB;IAC/C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oCAAoC,CAClD,SAA4B;IAE5B,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;AACnE,CAAC"}
|
package/dist/cli-lifecycle.d.ts
CHANGED
|
@@ -14,4 +14,8 @@ export interface SignalShutdownActions {
|
|
|
14
14
|
* Dependencies are injected so this policy can be tested without process.exit.
|
|
15
15
|
*/
|
|
16
16
|
export declare function createSignalShutdownHandler(actions: SignalShutdownActions): (signal: ShutdownSignal) => void;
|
|
17
|
+
/** Resolves once `text` is handed to the OS (or the write fails); never rejects. */
|
|
18
|
+
export declare function writeStdout(text: string, stream?: Pick<NodeJS.WriteStream, "write">): Promise<void>;
|
|
19
|
+
/** Waits for `pending` at most `ms`; a bound for cleanup that must not hang exit. */
|
|
20
|
+
export declare function settleWithin(pending: Promise<unknown>, ms: number): Promise<void>;
|
|
17
21
|
//# sourceMappingURL=cli-lifecycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-lifecycle.d.ts","sourceRoot":"","sources":["../src/cli-lifecycle.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,CAGhE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,GAC5B,MAAM,CAGR;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,qBAAqB,GAC7B,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CASlC"}
|
|
1
|
+
{"version":3,"file":"cli-lifecycle.d.ts","sourceRoot":"","sources":["../src/cli-lifecycle.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,CAGhE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,GAC5B,MAAM,CAGR;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,qBAAqB,GAC7B,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CASlC;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAkB,GACzD,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF"}
|
package/dist/cli-lifecycle.js
CHANGED
|
@@ -27,4 +27,22 @@ export function createSignalShutdownHandler(actions) {
|
|
|
27
27
|
actions.forceExit(code);
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
/** Resolves once `text` is handed to the OS (or the write fails); never rejects. */
|
|
31
|
+
export function writeStdout(text, stream = process.stdout) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
try {
|
|
34
|
+
stream.write(text, () => resolve());
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
resolve();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Waits for `pending` at most `ms`; a bound for cleanup that must not hang exit. */
|
|
42
|
+
export function settleWithin(pending, ms) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const timer = setTimeout(resolve, ms);
|
|
45
|
+
pending.then(() => { clearTimeout(timer); resolve(); }, () => { clearTimeout(timer); resolve(); });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
30
48
|
//# sourceMappingURL=cli-lifecycle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-lifecycle.js","sourceRoot":"","sources":["../src/cli-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAInD,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,MAAsB;IACtD,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAC9B,IAAmB,EACnB,MAA6B;IAE7B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;AACzD,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAA8B;IAE9B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;;YACtC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"cli-lifecycle.js","sourceRoot":"","sources":["../src/cli-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAInD,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,MAAsB;IACtD,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAC9B,IAAmB,EACnB,MAA6B;IAE7B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;AACzD,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAA8B;IAE9B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;;YACtC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,SAA4C,OAAO,CAAC,MAAM;IAE1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,OAAyB,EAAE,EAAU;IAChE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,IAAI,CACV,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EACzC,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAC1C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/cli-main.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as path from "node:path";
|
|
|
17
17
|
import * as readline from "node:readline";
|
|
18
18
|
import { MEMTREE_COMPACT_MIN_TOKENS, parseTokenCount, startProxy } from "./proxy.js";
|
|
19
19
|
import { MemtreeLinkStore } from "./memtree-links.js";
|
|
20
|
+
import { FirstTreeMarker } from "./first-tree.js";
|
|
20
21
|
import { StablePrefixStore } from "./prefix-store.js";
|
|
21
22
|
import { NewestTreeLookup } from "./memtree-newest.js";
|
|
22
23
|
import { ClaudeTranscriptUsage } from "./transcript-usage.js";
|
|
@@ -33,9 +34,13 @@ import { runMemtreeMcpServer } from "./memtree-mcp.js";
|
|
|
33
34
|
import { readProjectMeta } from "./project-meta.js";
|
|
34
35
|
import { argsConfigureMemtreeMcp, MEMTREE_TOOLS_HEADER_VALUE, memtreeMcpEnabledByEnv, withMemtreeMcpArgs, writeMemtreeMcpConfig, } from "./memtree-mcp-config.js";
|
|
35
36
|
import { claudeChildEnv, claudeNativeOneMillionContextEnabled, } from "./claude-env.js";
|
|
36
|
-
import { createSignalShutdownHandler, exitCodeForChild, } from "./cli-lifecycle.js";
|
|
37
|
+
import { createSignalShutdownHandler, exitCodeForChild, settleWithin, writeStdout, } from "./cli-lifecycle.js";
|
|
37
38
|
import { getPolychatApiKey, setPolychatApiKey, getLocalPolychatApiKey, setLocalPolychatApiKey, getStagingPolychatApiKey, setStagingPolychatApiKey, getConfigDir, loadConfig, } from "./config.js";
|
|
38
39
|
import { repairStrandedResume, resumeSessionId } from "./resume-repair.js";
|
|
40
|
+
import { Diagnostics, DiagnosticsRequestSink, recordError } from "./diagnostics.js";
|
|
41
|
+
import { signalName } from "./diagnostics-schema.js";
|
|
42
|
+
import { PACKAGE_DIR, detectInstallMethod } from "./auto-update.js";
|
|
43
|
+
import { exitResumeHint } from "./resume-hint.js";
|
|
39
44
|
// MemTree (polychat) API hosts — /v1/context_memory lives at the app root.
|
|
40
45
|
const POLYCHAT_BASE_URL = "https://api.polychat.co";
|
|
41
46
|
const STAGING_BASE_URL = "https://polychat-staging-421312241218.us-west2.run.app";
|
|
@@ -45,10 +50,14 @@ const POLYCHAT_AUTH_URL = "https://polychat.co/auth?memtree=true&utm_source=ccc"
|
|
|
45
50
|
const SHUTDOWN_PROXY_DRAIN_MS = 5_000;
|
|
46
51
|
const SHUTDOWN_MEMTREE_DRAIN_MS = 2_000;
|
|
47
52
|
const SHUTDOWN_LOG_FLUSH_MS = 2_000;
|
|
53
|
+
/** Shutdown's wait for the child's exit and the resume hint's (asynchronous) write. */
|
|
54
|
+
const SHUTDOWN_OUTPUT_FLUSH_MS = 1_000;
|
|
48
55
|
// Payment gate: how long "subscribe now" waits for the Stripe webhook to flip
|
|
49
56
|
// the key to paid, and how often it re-asks the status probe meanwhile.
|
|
50
57
|
const SUBSCRIBE_WAIT_MS = 180_000;
|
|
51
58
|
const SUBSCRIBE_POLL_MS = 3_000;
|
|
59
|
+
/** This launch's diagnostics, for the top-level error handler. */
|
|
60
|
+
let activeDiagnostics = null;
|
|
52
61
|
function openUrl(url) {
|
|
53
62
|
const platform = process.platform;
|
|
54
63
|
const command = platform === "darwin" ? "open" : platform === "win32" ? "explorer" : "xdg-open";
|
|
@@ -85,7 +94,7 @@ async function promptForApiKey(mode) {
|
|
|
85
94
|
* mean "unknown" (null): the caller stays quiet. Bounded by a short timeout
|
|
86
95
|
* and never throws, so startup is never gated on polychat availability.
|
|
87
96
|
*/
|
|
88
|
-
async function fetchPaymentStatus(baseUrl, apiKey, timeoutMs = 2000) {
|
|
97
|
+
async function fetchPaymentStatus(baseUrl, apiKey, timeoutMs = 2000, diagnostics) {
|
|
89
98
|
try {
|
|
90
99
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/context_memory/status`, {
|
|
91
100
|
headers: {
|
|
@@ -95,11 +104,23 @@ async function fetchPaymentStatus(baseUrl, apiKey, timeoutMs = 2000) {
|
|
|
95
104
|
},
|
|
96
105
|
signal: AbortSignal.timeout(timeoutMs),
|
|
97
106
|
});
|
|
98
|
-
if (!res.ok)
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
diagnostics?.record("key_status", {
|
|
109
|
+
result: res.status === 401 ? "invalid" : res.status === 402 ? "payment_required" : "unknown",
|
|
110
|
+
status: res.status,
|
|
111
|
+
});
|
|
99
112
|
return null;
|
|
100
|
-
|
|
113
|
+
}
|
|
114
|
+
const status = parsePaymentStatus(await res.json());
|
|
115
|
+
diagnostics?.record("key_status", {
|
|
116
|
+
result: status?.paid === false ? "payment_required" : status ? "ok" : "unknown",
|
|
117
|
+
status: res.status,
|
|
118
|
+
});
|
|
119
|
+
return status;
|
|
101
120
|
}
|
|
102
|
-
catch {
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (diagnostics)
|
|
123
|
+
recordError(diagnostics, "status_probe_failed", err);
|
|
103
124
|
return null;
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -110,13 +131,16 @@ async function fetchPaymentStatus(baseUrl, apiKey, timeoutMs = 2000) {
|
|
|
110
131
|
* the webhook to flip the key to paid), use claude without MemTree (launch
|
|
111
132
|
* uncompressed, the in-session notice still fires), or quit.
|
|
112
133
|
*/
|
|
113
|
-
async function runPaymentGate(status, recheck) {
|
|
134
|
+
async function runPaymentGate(status, recheck, diagnostics) {
|
|
114
135
|
let current = status;
|
|
115
136
|
for (;;) {
|
|
116
137
|
console.warn(`\x1b[1;33m${formatPaymentNotice(current, { hyperlinks: true })}\x1b[0m\n`);
|
|
117
138
|
const choice = parsePaymentChoice(await askLine(PAYMENT_GATE_PROMPT));
|
|
118
|
-
|
|
139
|
+
diagnostics.record("payment_gate", { choice });
|
|
140
|
+
if (choice === "quit") {
|
|
141
|
+
await diagnostics.flush();
|
|
119
142
|
process.exit(0);
|
|
143
|
+
}
|
|
120
144
|
if (choice === "continue") {
|
|
121
145
|
console.log("\nStarting claude without MemTree.\n");
|
|
122
146
|
return;
|
|
@@ -124,6 +148,7 @@ async function runPaymentGate(status, recheck) {
|
|
|
124
148
|
openUrl(current.url ?? FALLBACK_SUBSCRIBE_URL);
|
|
125
149
|
console.log("\nOpened the subscribe page. Waiting for payment to complete…");
|
|
126
150
|
const paid = await waitUntilPaid(recheck, SUBSCRIBE_WAIT_MS, SUBSCRIBE_POLL_MS);
|
|
151
|
+
diagnostics.record("payment_gate", { choice: paid ? "paid" : "still_unpaid" });
|
|
127
152
|
if (paid) {
|
|
128
153
|
console.log("\x1b[1;32m✓ MemTree is on — thank you.\x1b[0m\n");
|
|
129
154
|
return;
|
|
@@ -195,16 +220,28 @@ async function main() {
|
|
|
195
220
|
else if (mode === "staging") {
|
|
196
221
|
console.log("\x1b[1;35m🚧 STAGING MODE\x1b[0m\n");
|
|
197
222
|
}
|
|
223
|
+
const memtreeBaseUrl = mode === "local" ? LOCAL_BASE_URL :
|
|
224
|
+
mode === "staging" ? STAGING_BASE_URL :
|
|
225
|
+
POLYCHAT_BASE_URL;
|
|
198
226
|
// Get or prompt for the MemTree API key (separate keys per environment)
|
|
199
227
|
let polychatApiKey = mode === "local" ? getLocalPolychatApiKey() :
|
|
200
228
|
mode === "staging" ? getStagingPolychatApiKey() :
|
|
201
229
|
getPolychatApiKey();
|
|
230
|
+
// Client diagnostics (diagnostics.ts): versions, OS and errors, never content.
|
|
231
|
+
// Started before the key prompt so a signup that stalls there is visible too.
|
|
232
|
+
const diagnostics = startDiagnostics(mode, memtreeBaseUrl, polychatApiKey);
|
|
202
233
|
if (!polychatApiKey) {
|
|
234
|
+
diagnostics.record("sign_in_prompt");
|
|
235
|
+
// The user may quit at the prompt: send what is known now.
|
|
236
|
+
diagnostics.flushNow();
|
|
203
237
|
polychatApiKey = await promptForApiKey(mode);
|
|
204
238
|
if (!polychatApiKey) {
|
|
205
239
|
console.error("A MemTree API key is required.");
|
|
240
|
+
await diagnostics.flush();
|
|
206
241
|
process.exit(1);
|
|
207
242
|
}
|
|
243
|
+
diagnostics.record("key_saved");
|
|
244
|
+
diagnostics.setApiKey(polychatApiKey);
|
|
208
245
|
if (mode === "local") {
|
|
209
246
|
setLocalPolychatApiKey(polychatApiKey);
|
|
210
247
|
}
|
|
@@ -216,9 +253,6 @@ async function main() {
|
|
|
216
253
|
}
|
|
217
254
|
console.log("API key saved.\n");
|
|
218
255
|
}
|
|
219
|
-
const memtreeBaseUrl = mode === "local" ? LOCAL_BASE_URL :
|
|
220
|
-
mode === "staging" ? STAGING_BASE_URL :
|
|
221
|
-
POLYCHAT_BASE_URL;
|
|
222
256
|
// Interactive UI only: print/non-TTY invocations are programmatic
|
|
223
257
|
// interfaces whose output must stay byte-for-byte vanilla, and they get no
|
|
224
258
|
// notice plugin to deliver a banner anyway.
|
|
@@ -236,7 +270,7 @@ async function main() {
|
|
|
236
270
|
// call so sessions can be found by project; read once, never fails.
|
|
237
271
|
const config = loadConfig();
|
|
238
272
|
const [paymentStatus, updateAvailable, projectMeta] = await Promise.all([
|
|
239
|
-
fetchPaymentStatus(memtreeBaseUrl, polychatApiKey),
|
|
273
|
+
fetchPaymentStatus(memtreeBaseUrl, polychatApiKey, undefined, diagnostics),
|
|
240
274
|
interactiveUi
|
|
241
275
|
? checkForUpdate({
|
|
242
276
|
currentVersion: CLIENT_VERSION,
|
|
@@ -248,7 +282,7 @@ async function main() {
|
|
|
248
282
|
]);
|
|
249
283
|
if (paymentStatus?.paid === false) {
|
|
250
284
|
if (interactiveUi) {
|
|
251
|
-
await runPaymentGate(paymentStatus, () => fetchPaymentStatus(memtreeBaseUrl, polychatApiKey));
|
|
285
|
+
await runPaymentGate(paymentStatus, () => fetchPaymentStatus(memtreeBaseUrl, polychatApiKey), diagnostics);
|
|
252
286
|
}
|
|
253
287
|
else {
|
|
254
288
|
console.warn(`\x1b[1;33m${formatPaymentNotice(paymentStatus)}\x1b[0m`);
|
|
@@ -269,10 +303,17 @@ async function main() {
|
|
|
269
303
|
: null;
|
|
270
304
|
if (isDebugMode && autoUpdate)
|
|
271
305
|
console.log(`[DEBUG] ${autoUpdate.debugLine}`);
|
|
306
|
+
diagnostics.setContext({ interactive: interactiveUi });
|
|
307
|
+
if (autoUpdate?.bannerUpdate) {
|
|
308
|
+
diagnostics.record("update_hint", { latest: autoUpdate.bannerUpdate.latest });
|
|
309
|
+
}
|
|
272
310
|
// Always-on request/timing log (reqlog.ts): messages, MemTree calls, and
|
|
273
311
|
// successful notice claims, so incidents can be reconstructed after the
|
|
274
312
|
// fact without --debug. Never blocks or throws.
|
|
275
313
|
const reqlog = new RequestLogger();
|
|
314
|
+
// The proxy and MemTree client log through this tee: unchanged lines in the
|
|
315
|
+
// request log, plus a few allowlisted numbers for diagnostics.
|
|
316
|
+
const observedLog = new DiagnosticsRequestSink(reqlog, diagnostics);
|
|
276
317
|
// The `memtree` MCP server (memtree-mcp-config.ts): registered by ccc for
|
|
277
318
|
// interactive sessions unless CCC_MEMTREE_MCP=0; print/non-TTY runs have it
|
|
278
319
|
// only when their own --mcp-config names it. Either way MemTree is told
|
|
@@ -285,17 +326,19 @@ async function main() {
|
|
|
285
326
|
baseUrl: memtreeBaseUrl,
|
|
286
327
|
apiKey: polychatApiKey,
|
|
287
328
|
debug: isDebugMode,
|
|
288
|
-
reqlog,
|
|
329
|
+
reqlog: observedLog,
|
|
289
330
|
// Mutable below: a failed config write withdraws it before any call.
|
|
290
331
|
memtreeTools: memtreeToolsConfigured ? MEMTREE_TOOLS_HEADER_VALUE : undefined,
|
|
291
332
|
});
|
|
292
333
|
const nativeOneMillionContext = claudeNativeOneMillionContextEnabled(process.env);
|
|
293
334
|
const memtreeLinkPlacement = memtreeLinkPlacementFromEnv(process.env.CCC_MEMTREE_LINK);
|
|
335
|
+
const memtreeLinkStore = new MemtreeLinkStore();
|
|
294
336
|
const transcriptUsage = new ClaudeTranscriptUsage();
|
|
295
337
|
const proxy = await startProxy({
|
|
296
338
|
memtree,
|
|
297
339
|
debug: isDebugMode,
|
|
298
|
-
reqlog,
|
|
340
|
+
reqlog: observedLog,
|
|
341
|
+
diagnostics,
|
|
299
342
|
nativeOneMillionContext,
|
|
300
343
|
// Kill switch for tool-turn COMPACTION only: CCC_TOOL_ROUTE_RECOVERY=0
|
|
301
344
|
// makes tool turns pure passthrough — no size check, no compress call;
|
|
@@ -321,7 +364,9 @@ async function main() {
|
|
|
321
364
|
// CCC_MEMTREE_LINK=message|stop|success|off picks where the MemTree page
|
|
322
365
|
// link is shown while the placement is being tried out; see ProxyOptions.
|
|
323
366
|
memtreeLinkPlacement,
|
|
324
|
-
memtreeLinkStore
|
|
367
|
+
memtreeLinkStore,
|
|
368
|
+
// A session's first tree gets a one-time notice saying what it is.
|
|
369
|
+
firstTreeIntro: new FirstTreeMarker(memtreeLinkStore),
|
|
325
370
|
// Each session's adopted compressed prefix, so a resumed session's first
|
|
326
371
|
// turn rides it instead of blocking on a fresh compress.
|
|
327
372
|
stablePrefixStore: new StablePrefixStore(),
|
|
@@ -401,6 +446,11 @@ async function main() {
|
|
|
401
446
|
stdio: "inherit",
|
|
402
447
|
});
|
|
403
448
|
let shuttingDown = false;
|
|
449
|
+
// Settles once the child has exited and the resume hint (if any) is written,
|
|
450
|
+
// or the child failed to start. Awaited (bounded) just before exiting, so a
|
|
451
|
+
// signal whose cleanup finishes first still lets the hint print.
|
|
452
|
+
let markChildDone = () => { };
|
|
453
|
+
const childDone = new Promise((resolve) => { markChildDone = resolve; });
|
|
404
454
|
const shutdown = async (code) => {
|
|
405
455
|
if (shuttingDown)
|
|
406
456
|
return;
|
|
@@ -416,7 +466,9 @@ async function main() {
|
|
|
416
466
|
// would never fire), inside the same bounded background drain.
|
|
417
467
|
memtree.flushFinalIndexes();
|
|
418
468
|
await memtree.drainBackground(SHUTDOWN_MEMTREE_DRAIN_MS);
|
|
419
|
-
|
|
469
|
+
// Before the final flushes, so a late child exit's diagnostics event is sent.
|
|
470
|
+
await settleWithin(childDone, SHUTDOWN_OUTPUT_FLUSH_MS);
|
|
471
|
+
await Promise.all([reqlog.flush(SHUTDOWN_LOG_FLUSH_MS), diagnostics.flush()]);
|
|
420
472
|
process.exit(code);
|
|
421
473
|
};
|
|
422
474
|
const handleShutdownSignal = createSignalShutdownHandler({
|
|
@@ -441,24 +493,72 @@ async function main() {
|
|
|
441
493
|
process.on("SIGTERM", () => handleShutdownSignal("SIGTERM"));
|
|
442
494
|
// The background install starts once Claude Code is up; it outlives this
|
|
443
495
|
// process if the session ends first.
|
|
444
|
-
autoUpdate?.scheduleInstall()
|
|
496
|
+
autoUpdate?.scheduleInstall(undefined, (result) => {
|
|
497
|
+
diagnostics.record("auto_update", { result });
|
|
498
|
+
diagnostics.flushNow();
|
|
499
|
+
});
|
|
445
500
|
child.on("error", (err) => {
|
|
446
501
|
if (err.code === "ENOENT") {
|
|
502
|
+
diagnostics.record("claude_missing");
|
|
447
503
|
console.error("Could not find 'claude' command. Make sure Claude Code is installed.");
|
|
448
504
|
}
|
|
449
505
|
else {
|
|
506
|
+
recordError(diagnostics, "spawn_failed", err);
|
|
450
507
|
console.error("Failed to start claude:", err.message);
|
|
451
508
|
}
|
|
509
|
+
markChildDone();
|
|
452
510
|
void shutdown(1);
|
|
453
511
|
});
|
|
454
512
|
child.on("exit", (code, signal) => {
|
|
513
|
+
if (code !== 0) {
|
|
514
|
+
diagnostics.record("claude_exit", { code: code ?? undefined, signal: signalName(signal) });
|
|
515
|
+
}
|
|
516
|
+
// Under Claude Code's own "claude --resume <id>", which would skip MemTree.
|
|
517
|
+
const hint = exitResumeHint({
|
|
518
|
+
interactive: interactiveUi,
|
|
519
|
+
sessionId: proxy.mainSessionId(),
|
|
520
|
+
mode,
|
|
521
|
+
color: terminalSupportsColor(),
|
|
522
|
+
});
|
|
523
|
+
// Writes to a Windows console or a pipe are asynchronous: let it land before exit.
|
|
524
|
+
void (hint ? writeStdout(hint) : Promise.resolve()).then(markChildDone);
|
|
455
525
|
void shutdown(exitCodeForChild(code, signal));
|
|
456
526
|
});
|
|
457
527
|
}
|
|
458
|
-
main().catch((err) => {
|
|
528
|
+
main().catch(async (err) => {
|
|
459
529
|
console.error(err);
|
|
530
|
+
if (activeDiagnostics) {
|
|
531
|
+
recordError(activeDiagnostics, "startup_failed", err);
|
|
532
|
+
await activeDiagnostics.flush();
|
|
533
|
+
}
|
|
460
534
|
process.exit(1);
|
|
461
535
|
});
|
|
536
|
+
/**
|
|
537
|
+
* This launch's diagnostics: environment context, install method, the first
|
|
538
|
+
* run of this install, and a periodic background flush. Off under any opt-out.
|
|
539
|
+
*/
|
|
540
|
+
function startDiagnostics(mode, baseUrl, apiKey) {
|
|
541
|
+
const diagnostics = new Diagnostics({
|
|
542
|
+
baseUrl, configDir: getConfigDir(), clientName: CLIENT_NAME, clientVersion: CLIENT_VERSION,
|
|
543
|
+
config: loadConfig(), apiKey,
|
|
544
|
+
});
|
|
545
|
+
activeDiagnostics = diagnostics;
|
|
546
|
+
if (!diagnostics.enabled)
|
|
547
|
+
return diagnostics;
|
|
548
|
+
try {
|
|
549
|
+
const method = detectInstallMethod({
|
|
550
|
+
packageDir: PACKAGE_DIR, env: process.env, uid: process.getuid?.(),
|
|
551
|
+
});
|
|
552
|
+
diagnostics.setContext({ mode, install_method: method.kind });
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
diagnostics.setContext({ mode });
|
|
556
|
+
}
|
|
557
|
+
if (diagnostics.firstRun)
|
|
558
|
+
diagnostics.record("first_run");
|
|
559
|
+
diagnostics.start();
|
|
560
|
+
return diagnostics;
|
|
561
|
+
}
|
|
462
562
|
/** `CCC_COMPACT_TARGET` ("500k", "20000", "off"): see cli-args.ts. */
|
|
463
563
|
/**
|
|
464
564
|
* A resumed session whose last response was recorded past Claude Code's limit
|