claude-code-infinite 1.0.36 → 1.0.37
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 +100 -1
- package/dist/cc-request.d.ts +15 -0
- package/dist/cc-request.d.ts.map +1 -1
- package/dist/cc-request.js +30 -0
- package/dist/cc-request.js.map +1 -1
- package/dist/claude-env.d.ts +24 -1
- package/dist/claude-env.d.ts.map +1 -1
- package/dist/claude-env.js +48 -7
- package/dist/claude-env.js.map +1 -1
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +1 -1
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-main.js +99 -8
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/desktop-command.d.ts +38 -0
- package/dist/desktop-command.d.ts.map +1 -0
- package/dist/desktop-command.js +324 -0
- package/dist/desktop-command.js.map +1 -0
- package/dist/desktop-service.d.ts +41 -0
- package/dist/desktop-service.d.ts.map +1 -0
- package/dist/desktop-service.js +449 -0
- package/dist/desktop-service.js.map +1 -0
- package/dist/desktop-setup.d.ts +94 -0
- package/dist/desktop-setup.d.ts.map +1 -0
- package/dist/desktop-setup.js +288 -0
- package/dist/desktop-setup.js.map +1 -0
- package/dist/https-proxy-eligibility.d.ts +31 -0
- package/dist/https-proxy-eligibility.d.ts.map +1 -0
- package/dist/https-proxy-eligibility.js +204 -0
- package/dist/https-proxy-eligibility.js.map +1 -0
- package/dist/https-proxy-mode.d.ts +59 -0
- package/dist/https-proxy-mode.d.ts.map +1 -0
- package/dist/https-proxy-mode.js +255 -0
- package/dist/https-proxy-mode.js.map +1 -0
- package/dist/https-proxy-session.d.ts +64 -0
- package/dist/https-proxy-session.d.ts.map +1 -0
- package/dist/https-proxy-session.js +165 -0
- package/dist/https-proxy-session.js.map +1 -0
- package/dist/memtree-mcp.d.ts +32 -1
- package/dist/memtree-mcp.d.ts.map +1 -1
- package/dist/memtree-mcp.js +73 -7
- package/dist/memtree-mcp.js.map +1 -1
- package/dist/memtree.d.ts +10 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +59 -12
- package/dist/memtree.js.map +1 -1
- package/dist/message-refs.d.ts +81 -0
- package/dist/message-refs.d.ts.map +1 -0
- package/dist/message-refs.js +378 -0
- package/dist/message-refs.js.map +1 -0
- package/dist/mitm-ca.d.ts +31 -0
- package/dist/mitm-ca.d.ts.map +1 -0
- package/dist/mitm-ca.js +236 -0
- package/dist/mitm-ca.js.map +1 -0
- package/dist/paused-session.d.ts.map +1 -1
- package/dist/paused-session.js +9 -2
- package/dist/paused-session.js.map +1 -1
- package/dist/proxy.d.ts +20 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +101 -16
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +7 -1
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js.map +1 -1
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { UNKNOWN_INDEX_WAIT_SECONDS } from "./index-wait-413.js";
|
|
42
42
|
import { capCacheBreakpoints } from "./route-cache.js";
|
|
43
|
+
import { attachHttpsProxyMode, } from "./https-proxy-mode.js";
|
|
43
44
|
import { PromptAccounting } from "./prompt-accounting.js";
|
|
44
45
|
import { INDEX_WAIT_RETRY_AFTER_SECONDS, RefusalStreaks, RouteFallbackFailures, pausedRefusal, unreachableRefusal } from "./route-fallback.js";
|
|
45
46
|
import { refusalCause, refusalMessage } from "./refusal-reason.js";
|
|
@@ -68,7 +69,7 @@ import { keyInvalidNoticeText, keyInvalidRefusalMessage } from "./key-status.js"
|
|
|
68
69
|
import { KEY_INVALID_KIND, ModelContextNotices, PAUSED_KIND, keyInvalidModelContext, lastToldKindInFile, pausedModelContext } from "./model-context.js";
|
|
69
70
|
import { PaymentNoticeThrottle, pausedCompactionText, pausedRefusalMessage, paymentNoticeText, subscribeUrlFromDetail } from "./payment-notice.js";
|
|
70
71
|
import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
|
|
71
|
-
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
|
|
72
|
+
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, notSessionConversation, sessionTag, } from "./cc-request.js";
|
|
72
73
|
import { isResumableSessionId } from "./resume-hint.js";
|
|
73
74
|
import { isSubagentCall, postToolUseOutput, SUBAGENT_RESULT_HINT, subagentMemoryHintText, subagentStartOutput, } from "./subagent-hint.js";
|
|
74
75
|
import { PROJECT_TREES_HOOK_SUFFIX, RESUME_GUIDANCE_HOOK_SUFFIX, SUBAGENT_RESULT_HOOK_SUFFIX, } from "./hooks.js";
|
|
@@ -427,10 +428,12 @@ export function startProxy(opts) {
|
|
|
427
428
|
// A newer tree from the background lookup reaches the status line without a turn ending.
|
|
428
429
|
opts.newestTrees?.onTree((sessionId) => publishStatusLink(state, sessionId));
|
|
429
430
|
state.notices.setFirstTreeIntro(opts.firstTreeIntro ?? null);
|
|
430
|
-
|
|
431
|
+
// One handler for the loopback server and, in HTTPS-proxy mode, for the decrypted
|
|
432
|
+
// api.anthropic.com streams (`intercepted`): both are tracked, drained and cancelled alike.
|
|
433
|
+
const onRequest = (intercepted) => (req, res) => {
|
|
431
434
|
const accepted = { req, res };
|
|
432
435
|
acceptedRequests.add(accepted);
|
|
433
|
-
const task = handleRequest(req, res, opts, upstream, state, hookPath).catch((err) => {
|
|
436
|
+
const task = handleRequest(req, res, opts, upstream, state, hookPath, intercepted).catch((err) => {
|
|
434
437
|
try {
|
|
435
438
|
sendAnthropicError(res, `local proxy error: ${err?.message ?? err}`);
|
|
436
439
|
}
|
|
@@ -448,7 +451,20 @@ export function startProxy(opts) {
|
|
|
448
451
|
});
|
|
449
452
|
if (shutdownAbort.signal.aborted)
|
|
450
453
|
cancelAcceptedRequest(accepted);
|
|
451
|
-
}
|
|
454
|
+
};
|
|
455
|
+
const server = http.createServer(opts.innerToken === undefined ? onRequest(false)
|
|
456
|
+
: (req, res) => {
|
|
457
|
+
const inner = acceptInnerRequest(req, opts.innerToken);
|
|
458
|
+
if (inner === undefined) {
|
|
459
|
+
res.writeHead(403, { "content-type": "application/json" });
|
|
460
|
+
res.end(JSON.stringify({ detail: "ccc inner proxy: not for direct use" }));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
onRequest(inner.intercepted)(req, res);
|
|
464
|
+
});
|
|
465
|
+
const closeTunnels = opts.httpsProxyMode
|
|
466
|
+
? attachHttpsProxyMode(server, onRequest(true), interceptUpstream(upstream), opts.httpsProxyMode)
|
|
467
|
+
: { blind: () => { }, all: () => { } };
|
|
452
468
|
// Long-running SSE responses must not be cut by idle timeouts.
|
|
453
469
|
server.requestTimeout = 0;
|
|
454
470
|
server.headersTimeout = 60_000;
|
|
@@ -470,6 +486,9 @@ export function startProxy(opts) {
|
|
|
470
486
|
? Math.floor(timeoutMs)
|
|
471
487
|
: 5_000;
|
|
472
488
|
backgroundAbort.abort();
|
|
489
|
+
// CONNECT tunnels are raw sockets that keep server.close() waiting: the blind ones
|
|
490
|
+
// (and idle decrypted streams) carry no ccc work, so they go now.
|
|
491
|
+
closeTunnels.blind();
|
|
473
492
|
const quiesced = (async () => {
|
|
474
493
|
// Once close completes, every accepted request has entered the tracked
|
|
475
494
|
// set.
|
|
@@ -498,6 +517,7 @@ export function startProxy(opts) {
|
|
|
498
517
|
shutdownAbort.abort();
|
|
499
518
|
for (const accepted of acceptedRequests)
|
|
500
519
|
cancelAcceptedRequest(accepted);
|
|
520
|
+
closeTunnels.all();
|
|
501
521
|
await quiesced;
|
|
502
522
|
sendDeferredAgentIndexes(state);
|
|
503
523
|
return false;
|
|
@@ -532,8 +552,10 @@ function cancelAcceptedRequest(accepted) {
|
|
|
532
552
|
if (!res.destroyed && !res.writableEnded)
|
|
533
553
|
res.destroy();
|
|
534
554
|
}
|
|
535
|
-
async function handleRequest(req, res, opts, upstream, state, hookPath) {
|
|
555
|
+
async function handleRequest(req, res, opts, upstream, state, hookPath, intercepted) {
|
|
536
556
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
557
|
+
if (intercepted)
|
|
558
|
+
return handleInterceptedRequest(req, res, url, opts, upstream, state);
|
|
537
559
|
if (!isLocalCaller(req)) {
|
|
538
560
|
res.writeHead(403, { "content-type": "application/json" });
|
|
539
561
|
res.end(JSON.stringify({ detail: "The ccc proxy only serves programs on this machine" }));
|
|
@@ -578,6 +600,32 @@ async function handleRequest(req, res, opts, upstream, state, hookPath) {
|
|
|
578
600
|
}
|
|
579
601
|
return passThroughStreaming(req, res, upstream, state.shutdownSignal);
|
|
580
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Claude Code's own api.anthropic.com traffic, decrypted (HTTPS-proxy mode): model calls
|
|
605
|
+
* get MemTree exactly as through the base URL; everything else (Remote Control's bridge,
|
|
606
|
+
* sessions, files, OAuth, telemetry) goes verbatim to api.anthropic.com. ccc's
|
|
607
|
+
* loopback-only routes (hooks, /memtree/*) are never answered here: such a path is
|
|
608
|
+
* Anthropic's.
|
|
609
|
+
*/
|
|
610
|
+
function handleInterceptedRequest(req, res, url, opts, upstream, state) {
|
|
611
|
+
if (req.method === "POST" && url.pathname === "/v1/messages") {
|
|
612
|
+
try {
|
|
613
|
+
opts.httpsProxyMode?.onModelCall?.();
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// A notice hook must never fail a model call.
|
|
617
|
+
}
|
|
618
|
+
return handleMessages(req, res, opts, upstream, state);
|
|
619
|
+
}
|
|
620
|
+
if (req.method === "POST" && url.pathname === "/v1/messages/count_tokens") {
|
|
621
|
+
return handleCountTokens(req, res, opts, upstream, state);
|
|
622
|
+
}
|
|
623
|
+
return passThroughStreaming(req, res, upstream, state.shutdownSignal);
|
|
624
|
+
}
|
|
625
|
+
/** Where a WebSocket upgrade on a decrypted stream is replayed: the same upstream. */
|
|
626
|
+
function interceptUpstream(upstream) {
|
|
627
|
+
return { host: upstream.host, port: upstream.port, secure: upstream.module === https };
|
|
628
|
+
}
|
|
581
629
|
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
582
630
|
const BROWSER_SAME_SITE = new Set(["same-origin", "none"]);
|
|
583
631
|
/**
|
|
@@ -1383,6 +1431,12 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1383
1431
|
const { lane: requestRouteLane, key: requestRouteKey } = routeIdentity(req, isAwaySummary);
|
|
1384
1432
|
const isSubagentRequest = requestRouteLane === "agent";
|
|
1385
1433
|
const isMainRequest = requestRouteLane === "main";
|
|
1434
|
+
// A side request or a thread continuation carries a fragment, not the conversation:
|
|
1435
|
+
// it is forwarded as sent, with no compress, route, recovery or MemTree upload.
|
|
1436
|
+
// The away summary keeps its own lane and handling.
|
|
1437
|
+
const notConversation = isAwaySummary ? undefined : notSessionConversation(body, clientInfo);
|
|
1438
|
+
if (notConversation)
|
|
1439
|
+
rec.notConversation = notConversation;
|
|
1386
1440
|
const requestSizeKey = laneSizeKey(req, requestRouteKey, body.model);
|
|
1387
1441
|
const hold = indexHolds.get(rec);
|
|
1388
1442
|
if (hold) {
|
|
@@ -1449,7 +1503,9 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1449
1503
|
// API identity and validated history alone govern route eligibility.
|
|
1450
1504
|
const rideableCandidate = getMemoryRoute(state, requestRouteKey);
|
|
1451
1505
|
const existingRide = rideableCandidate && memoryRoutedToolBody(body, messages, rideableCandidate, state.mainRouteEpoch, requestSessionId(req));
|
|
1452
|
-
|
|
1506
|
+
// A fragment is never a follow-up: it must not reserve a route decision (it would never
|
|
1507
|
+
// release it, and an in-flight main compression could not install its route).
|
|
1508
|
+
const isFollowupUserTurn = !notConversation && isUserTurn && hasEarlierNonToolUserMessage(messages);
|
|
1453
1509
|
const routeEpoch = state.mainRouteEpoch;
|
|
1454
1510
|
let routeDecisionGeneration = state.mainRouteDecisionGeneration;
|
|
1455
1511
|
let routeDecisionSettled = false;
|
|
@@ -1495,7 +1551,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1495
1551
|
// ORIGINAL conversation (the one MemTree indexed), never the ride itself.
|
|
1496
1552
|
recoverLate: async (_ride, rideTokens, target) => {
|
|
1497
1553
|
// A compression already made may be redone only to a smaller target.
|
|
1498
|
-
if ((rec.compress && target === undefined) ||
|
|
1554
|
+
if (notConversation || (rec.compress && target === undefined) ||
|
|
1499
1555
|
compactionMode(opts, state, requestSessionKey).mode === "off" ||
|
|
1500
1556
|
!hasEarlierNonToolUserMessage(messages) ||
|
|
1501
1557
|
(rec.turnType.startsWith("tool") && opts.toolRouteRecovery === false))
|
|
@@ -1518,9 +1574,14 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1518
1574
|
// The main lane's latest conversation: what its final index sends, plus the
|
|
1519
1575
|
// reply the session ends with (scheduleFinalIndex, on Stop).
|
|
1520
1576
|
const mainSessionId = isMainRequest ? requestSessionId(req) : undefined;
|
|
1521
|
-
if (mainSessionId !== undefined) {
|
|
1522
|
-
|
|
1523
|
-
|
|
1577
|
+
if (mainSessionId !== undefined && isResumableSessionId(mainSessionId)) {
|
|
1578
|
+
state.resumableMainSession = mainSessionId;
|
|
1579
|
+
}
|
|
1580
|
+
// A continuation is the session at work (a side request, e.g. a status summary, is not).
|
|
1581
|
+
if (mainSessionId !== undefined && notConversation === "thread-continue") {
|
|
1582
|
+
opts.memtree.noteMainActivity(mainSessionId);
|
|
1583
|
+
}
|
|
1584
|
+
if (mainSessionId !== undefined && !notConversation) {
|
|
1524
1585
|
opts.memtree.noteMainConversation(mainSessionId, {
|
|
1525
1586
|
messages: msgsForMemtree,
|
|
1526
1587
|
modelContextLimit,
|
|
@@ -1540,7 +1601,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1540
1601
|
// Observation only: which requests carry Claude Code's turn origin.
|
|
1541
1602
|
if (clientInfo)
|
|
1542
1603
|
rec.client = clientInfo;
|
|
1543
|
-
if (!isFollowupUserTurn) {
|
|
1604
|
+
if (!isFollowupUserTurn || notConversation) {
|
|
1544
1605
|
// Tool turn or FIRST user turn: keep the index fed off the response path
|
|
1545
1606
|
// and forward as-is (on the lane's memory route or the session's stable
|
|
1546
1607
|
// prefix when one applies) — except a tool turn whose estimated size
|
|
@@ -1556,7 +1617,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1556
1617
|
let routedTool = false;
|
|
1557
1618
|
let routedCandidate;
|
|
1558
1619
|
let routeMiss;
|
|
1559
|
-
if (isToolResultTurn || isContinuationTurn || isUserTurn) {
|
|
1620
|
+
if (!notConversation && (isToolResultTurn || isContinuationTurn || isUserTurn)) {
|
|
1560
1621
|
// Reuse only this API session/lane's independently validated prefix.
|
|
1561
1622
|
const activeRoute = rideableCandidate;
|
|
1562
1623
|
if (activeRoute) {
|
|
@@ -1617,7 +1678,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1617
1678
|
rec.continuation = true;
|
|
1618
1679
|
// Cheap shape check: without an earlier real user message there is
|
|
1619
1680
|
// nothing MemTree could compress, so no attempt is worth making.
|
|
1620
|
-
const canCompress = hasEarlierNonToolUserMessage(messages);
|
|
1681
|
+
const canCompress = !notConversation && hasEarlierNonToolUserMessage(messages);
|
|
1621
1682
|
let toolRide = routedTool
|
|
1622
1683
|
? {
|
|
1623
1684
|
raw: routedBody,
|
|
@@ -1626,7 +1687,10 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1626
1687
|
}
|
|
1627
1688
|
: undefined;
|
|
1628
1689
|
let toolNeedsOriginal = false;
|
|
1629
|
-
if (
|
|
1690
|
+
if (notConversation) {
|
|
1691
|
+
// A fragment: forwarded as sent (no route, no stable prefix, no compaction).
|
|
1692
|
+
}
|
|
1693
|
+
else if (opts.toolRouteRecovery === false) {
|
|
1630
1694
|
// Kill switch: no size check and no compress call on any tool turn.
|
|
1631
1695
|
// A route still rides; a miss forwards whole and records "disabled".
|
|
1632
1696
|
if ((routeMiss === "missing" || routeMiss === "rejected") && canCompress) {
|
|
@@ -1803,8 +1867,10 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1803
1867
|
requestSendPolicies.get(req)?.allowed.add(toolRide.raw);
|
|
1804
1868
|
const sendBody = toolRide?.raw ?? (toolNeedsOriginal ? rawBody : forwardBody);
|
|
1805
1869
|
recordTurn(rec, toolRide ? toolRide.turnType : isUserTurn ? "first-user" : "tool", sendBody);
|
|
1806
|
-
|
|
1807
|
-
|
|
1870
|
+
if (!notConversation) {
|
|
1871
|
+
indexTurn({ opts, state, req, rec, hash, msgsForMemtree, modelContextLimit,
|
|
1872
|
+
sessionId: toolSessionId, clientMeta });
|
|
1873
|
+
}
|
|
1808
1874
|
capture(opts, toolRide ? "anthropic-request-memory-tool" : "anthropic-request", sendBody);
|
|
1809
1875
|
// The size Anthropic reports is the next budget check's anchor: a ride
|
|
1810
1876
|
// reports to its prefix (or route), a whole request to the session's (or
|
|
@@ -6761,4 +6827,23 @@ function transcriptTimesFor(opts, sessionId, agentId) {
|
|
|
6761
6827
|
return (messages) => source.timesFor(sessionId, messages, agentId);
|
|
6762
6828
|
}
|
|
6763
6829
|
export const __testHoldingLane = holdingLane;
|
|
6830
|
+
/**
|
|
6831
|
+
* The inner-proxy headers (ProxyOptions.innerToken): checked, then stripped so nothing
|
|
6832
|
+
* forwards them. undefined: not the service.
|
|
6833
|
+
*/
|
|
6834
|
+
function acceptInnerRequest(req, token) {
|
|
6835
|
+
if (!sameSecret(req.headers[INNER_TOKEN_HEADER], token))
|
|
6836
|
+
return undefined;
|
|
6837
|
+
const intercepted = req.headers[INNER_INTERCEPTED_HEADER] === "1";
|
|
6838
|
+
delete req.headers[INNER_TOKEN_HEADER];
|
|
6839
|
+
delete req.headers[INNER_INTERCEPTED_HEADER];
|
|
6840
|
+
for (let i = req.rawHeaders.length - 2; i >= 0; i -= 2) {
|
|
6841
|
+
const name = req.rawHeaders[i].toLowerCase();
|
|
6842
|
+
if (name === INNER_TOKEN_HEADER || name === INNER_INTERCEPTED_HEADER)
|
|
6843
|
+
req.rawHeaders.splice(i, 2);
|
|
6844
|
+
}
|
|
6845
|
+
return { intercepted };
|
|
6846
|
+
}
|
|
6847
|
+
export const INNER_TOKEN_HEADER = "x-ccc-inner";
|
|
6848
|
+
export const INNER_INTERCEPTED_HEADER = "x-ccc-intercepted";
|
|
6764
6849
|
//# sourceMappingURL=proxy.js.map
|