claude-code-infinite 1.0.24 → 1.0.26

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 (75) hide show
  1. package/README.md +36 -5
  2. package/dist/auto-update-runner.d.ts +13 -0
  3. package/dist/auto-update-runner.d.ts.map +1 -1
  4. package/dist/auto-update-runner.js +51 -7
  5. package/dist/auto-update-runner.js.map +1 -1
  6. package/dist/auto-update-setup.d.ts +19 -3
  7. package/dist/auto-update-setup.d.ts.map +1 -1
  8. package/dist/auto-update-setup.js +42 -16
  9. package/dist/auto-update-setup.js.map +1 -1
  10. package/dist/auto-update.d.ts +59 -6
  11. package/dist/auto-update.d.ts.map +1 -1
  12. package/dist/auto-update.js +207 -40
  13. package/dist/auto-update.js.map +1 -1
  14. package/dist/claude-auth.d.ts +47 -0
  15. package/dist/claude-auth.d.ts.map +1 -0
  16. package/dist/claude-auth.js +171 -0
  17. package/dist/claude-auth.js.map +1 -0
  18. package/dist/claude-locate.d.ts +62 -0
  19. package/dist/claude-locate.d.ts.map +1 -0
  20. package/dist/claude-locate.js +224 -0
  21. package/dist/claude-locate.js.map +1 -0
  22. package/dist/cli-main.js +103 -15
  23. package/dist/cli-main.js.map +1 -1
  24. package/dist/cli.js +7 -1
  25. package/dist/cli.js.map +1 -1
  26. package/dist/diagnostics-schema.d.ts +9 -1
  27. package/dist/diagnostics-schema.d.ts.map +1 -1
  28. package/dist/diagnostics-schema.js +26 -1
  29. package/dist/diagnostics-schema.js.map +1 -1
  30. package/dist/diagnostics.d.ts +5 -1
  31. package/dist/diagnostics.d.ts.map +1 -1
  32. package/dist/diagnostics.js +17 -1
  33. package/dist/diagnostics.js.map +1 -1
  34. package/dist/hooks.d.ts +6 -1
  35. package/dist/hooks.d.ts.map +1 -1
  36. package/dist/hooks.js +12 -0
  37. package/dist/hooks.js.map +1 -1
  38. package/dist/memtree-mcp.d.ts.map +1 -1
  39. package/dist/memtree-mcp.js +8 -5
  40. package/dist/memtree-mcp.js.map +1 -1
  41. package/dist/memtree.d.ts +9 -0
  42. package/dist/memtree.d.ts.map +1 -1
  43. package/dist/memtree.js +18 -0
  44. package/dist/memtree.js.map +1 -1
  45. package/dist/notices.d.ts.map +1 -1
  46. package/dist/notices.js +3 -2
  47. package/dist/notices.js.map +1 -1
  48. package/dist/payment-notice.d.ts +19 -1
  49. package/dist/payment-notice.d.ts.map +1 -1
  50. package/dist/payment-notice.js +25 -2
  51. package/dist/payment-notice.js.map +1 -1
  52. package/dist/proxy.d.ts.map +1 -1
  53. package/dist/proxy.js +116 -8
  54. package/dist/proxy.js.map +1 -1
  55. package/dist/reqlog.d.ts +9 -0
  56. package/dist/reqlog.d.ts.map +1 -1
  57. package/dist/reqlog.js.map +1 -1
  58. package/dist/resume-repair.d.ts +2 -0
  59. package/dist/resume-repair.d.ts.map +1 -1
  60. package/dist/resume-repair.js +7 -5
  61. package/dist/resume-repair.js.map +1 -1
  62. package/dist/route-fallback.d.ts +6 -0
  63. package/dist/route-fallback.d.ts.map +1 -1
  64. package/dist/route-fallback.js +9 -0
  65. package/dist/route-fallback.js.map +1 -1
  66. package/dist/update-check.d.ts +2 -0
  67. package/dist/update-check.d.ts.map +1 -1
  68. package/dist/update-check.js.map +1 -1
  69. package/dist/update-lock.js +3 -1
  70. package/dist/update-lock.js.map +1 -1
  71. package/dist/update-wait.d.ts +25 -0
  72. package/dist/update-wait.d.ts.map +1 -0
  73. package/dist/update-wait.js +63 -0
  74. package/dist/update-wait.js.map +1 -0
  75. package/package.json +1 -1
package/dist/proxy.js CHANGED
@@ -40,7 +40,7 @@
40
40
  */
41
41
  import { capCacheBreakpoints } from "./route-cache.js";
42
42
  import { PromptAccounting } from "./prompt-accounting.js";
43
- import { INDEX_WAIT_RETRY_AFTER_SECONDS, RouteFallbackFailures } from "./route-fallback.js";
43
+ import { INDEX_WAIT_RETRY_AFTER_SECONDS, RouteFallbackFailures, pausedRefusal } from "./route-fallback.js";
44
44
  import { createCountTokensSession, CountTokensCooldowns, plausibleSample, } from "./count-tokens.js";
45
45
  import { RequestSizing, estimateRequestTokens, fitsMeasured, fitsNative, outputReservation } from "./request-sizing.js";
46
46
  import { routeMessageHash, stablePrefixMessageHash } from "./route-identity.js";
@@ -56,11 +56,12 @@ import { COMPRESSED_NOTICE, compressedTotalsText, DEGRADED_NOTICE, NOT_COMPRESSE
56
56
  import { NoticeDeliveryQueue, MEMTREE_COMPACT_COMMAND, MEMTREE_HELP_COMMAND, TRAILER_LABEL, LINK_LABEL, linkLines, urlLine, terminalSupportsHyperlinks, isMemtreeViewCommand, sessionCommandArgs, parseNoticeHookInput, } from "./hooks.js";
57
57
  import { USAGE_LABEL, USAGE_SIGN_IN_NOTE } from "./usage-link.js";
58
58
  import { hyperlink } from "./payment-gate.js";
59
- import { PaymentNoticeThrottle, paymentNoticeText } from "./payment-notice.js";
59
+ import { PaymentNoticeThrottle, pausedCompactionText, pausedRefusalMessage, paymentNoticeText, subscribeUrlFromDetail } from "./payment-notice.js";
60
60
  import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
61
61
  import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
62
62
  import { isResumableSessionId } from "./resume-hint.js";
63
63
  import { conservativeBodyTokens } from "./token-estimate.js";
64
+ import { claudeAuthFromHeaders, upstreamErrorCode } from "./claude-auth.js";
64
65
  import { approxTokensFromBytes, mergeUsageFromJsonBody, mergeUsageFromSseEvent, } from "./reqlog.js";
65
66
  const DEFAULT_UPSTREAM = "https://api.anthropic.com";
66
67
  const HOOK_BODY_LIMIT = 64 * 1024;
@@ -303,7 +304,9 @@ export function startProxy(opts) {
303
304
  const shutdownAbort = new AbortController();
304
305
  const state = {
305
306
  paymentNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
307
+ pausedNearWindowWarned: new Set(),
306
308
  unpaidDetail: () => opts.memtree.paymentRequiredDetail,
309
+ pausedDetail: () => opts.memtree.pausedDetail,
307
310
  usageUrl: opts.memtree.usageDashboardUrl,
308
311
  sessionStartNotice: opts.sessionStartNotice,
309
312
  diagnostics: opts.diagnostics,
@@ -882,6 +885,7 @@ async function handleMessages(req, res, opts, upstream, state) {
882
885
  kind: "messages",
883
886
  turnType: "unparseable",
884
887
  requestBytes: rawBody.length,
888
+ claudeAuth: claudeAuthFromHeaders(req.headers),
885
889
  };
886
890
  const logged = async (forward) => {
887
891
  try {
@@ -1013,6 +1017,8 @@ async function handleMessages(req, res, opts, upstream, state) {
1013
1017
  // strips the `[1m]` model suffix on the wire). Current native-1M models send
1014
1018
  // neither, so contextLimitForModel also needs the launcher's native setting.
1015
1019
  const modelContextLimit = contextLimitForModel(body.model, headerText(req.headers, "anthropic-beta"), opts.nativeOneMillionContext !== false);
1020
+ if (isMainRequest)
1021
+ rec.contextWindow = modelContextLimit;
1016
1022
  // Before anything reads the size sample: replace an unknown or stale
1017
1023
  // estimate with an exact count where it would decide the outcome.
1018
1024
  const requestSessionKey = requestSessionId(req);
@@ -1717,11 +1723,20 @@ async function handleMessages(req, res, opts, upstream, state) {
1717
1723
  capture(opts, "anthropic-request", forwardBody);
1718
1724
  const paymentDetail = opts.memtree.paymentRequiredDetail;
1719
1725
  const mayQueueNotice = displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration;
1720
- if (mayQueueNotice && paymentDetail !== null && state.paymentNotice.due()) {
1721
- state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link), () => {
1726
+ const overBudget = reachedBudget(rec.compaction);
1727
+ if (mayQueueNotice && paymentDetail !== null &&
1728
+ (state.paymentNotice.due() || (overBudget && !nearWindowWarned(state, sessionId)))) {
1729
+ // A paused refusal of this very request withdraws it (refuseWholeRequest),
1730
+ // and any later one withdraws its near-window line.
1731
+ const handle = state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link, { overBudget }), () => {
1722
1732
  state.paymentNotice.markShown();
1733
+ if (overBudget && sessionId !== undefined)
1734
+ addNearWindowWarned(state, sessionId);
1723
1735
  state.diagnostics?.record("paused_notice", { trigger: "turn" });
1724
1736
  }, noticePromptId);
1737
+ pausedNotices.set(rec, handle);
1738
+ if (overBudget)
1739
+ state.nearWindowNotice = handle;
1725
1740
  }
1726
1741
  else if (mayQueueNotice && paymentDetail === null) {
1727
1742
  state.notices.queueSuffix(DEGRADED_NOTICE, undefined, noticePromptId);
@@ -1976,10 +1991,15 @@ function joinNoticeLines(...parts) {
1976
1991
  export const MEMTREE_COMPACT_MIN_TOKENS = 20_000;
1977
1992
  /** The reply to a ccc slash command, or undefined for an ordinary prompt. */
1978
1993
  function sessionCommandReply(state, sessionId, prompt) {
1994
+ const unpaid = state.unpaidDetail();
1995
+ // Paused, compaction needs the MemTree it cannot reach: say how to resume instead.
1996
+ const paused = unpaid === null ? undefined
1997
+ : pausedCompactionText(viewUrl(subscribeUrlFromDetail(unpaid)));
1979
1998
  if (sessionCommandArgs(prompt, MEMTREE_HELP_COMMAND) !== undefined) {
1980
1999
  return [
1981
2000
  "• /memtree-view · show this session's MemTree page link",
1982
- `• /memtree-compact [tokens | off] · compact this session on your next message (default half the budget, at least ${MEMTREE_COMPACT_MIN_TOKENS / 1000}k)`,
2001
+ paused !== undefined ? `• ${paused}`
2002
+ : `• /memtree-compact [tokens | off] · compact this session on your next message (default half the budget, at least ${MEMTREE_COMPACT_MIN_TOKENS / 1000}k)`,
1983
2003
  usageLines(state),
1984
2004
  ].filter((line) => line !== undefined).join("\n");
1985
2005
  }
@@ -2000,6 +2020,8 @@ function sessionCommandReply(state, sessionId, prompt) {
2000
2020
  state.storedPrefixLookups.set(sessionId, null);
2001
2021
  return `${TRAILER_LABEL} compaction off: the conversation is sent whole, and MemTree compresses only when it outgrows the model's budget.`;
2002
2022
  }
2023
+ if (paused !== undefined)
2024
+ return `${TRAILER_LABEL} ${paused}`;
2003
2025
  if (args === "") {
2004
2026
  // Back to the automatic target (half the budget, or CCC_COMPACT_TARGET).
2005
2027
  // Under CCC_COMPACT_TARGET=off the default is off, so pin the automatic
@@ -2082,7 +2104,9 @@ function viewUrl(url) {
2082
2104
  */
2083
2105
  function sessionLink(state, sessionId, source) {
2084
2106
  const newest = state.newestTrees?.peek(sessionId);
2085
- const note = source && !source.compressed ? { note: NOT_COMPRESSED_NOTE } : {};
2107
+ // The note suggests /memtree-compact, which cannot run while MemTree is paused.
2108
+ const note = source && !source.compressed && state.unpaidDetail() === null
2109
+ ? { note: NOT_COMPRESSED_NOTE } : {};
2086
2110
  if (newest)
2087
2111
  return { key: newest.key, link: newest.url, ...note };
2088
2112
  return source ? { key: source.index, link: source.url, ...note } : undefined;
@@ -2193,6 +2217,9 @@ function noteMemtreeHealth(state, compression) {
2193
2217
  }
2194
2218
  if (compression.liveHealth === "success") {
2195
2219
  state.toolRecoveryCooldownUntil = 0;
2220
+ // Paid again: a later pause (next month's limit) warns near the window afresh.
2221
+ if (state.unpaidDetail() === null)
2222
+ state.pausedNearWindowWarned.clear();
2196
2223
  }
2197
2224
  }
2198
2225
  /**
@@ -3915,6 +3942,7 @@ async function selectForwardCandidate(args) {
3915
3942
  args.rec.overBudgetForward = { reason: measurement.source === "exact"
3916
3943
  ? "exact-count-fits-window" : measurement.source === "bytes"
3917
3944
  ? "bytes-fallback-fits-window" : "estimate-fits-window" };
3945
+ warnPausedNearWindow(args.state, args.rec, requestSessionId(args.req));
3918
3946
  }
3919
3947
  return changed ? candidate.body : intended;
3920
3948
  }
@@ -3934,7 +3962,9 @@ async function wholeRequestRefused(sizing, candidate, limits) {
3934
3962
  }
3935
3963
  /** Write the bounded fallback refusal (route-fallback.ts); always false. */
3936
3964
  function refuseWholeRequest(args, inputTokens, budgetTokens) {
3937
- const failure = args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
3965
+ const unpaid = args.state.pausedDetail();
3966
+ const failure = unpaid !== null ? pausedRefusal(pausedRefusalMessage(unpaid))
3967
+ : args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
3938
3968
  args.rec.forwardedBytes = 0;
3939
3969
  args.rec.approxInputTokens = 0;
3940
3970
  args.state.diagnostics?.record("refusal", {
@@ -3946,14 +3976,89 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
3946
3976
  lane: args.routeKey, attempt: failure.attempt, status: failure.status,
3947
3977
  inputTokens, outputTokens: args.body.max_tokens ?? 0,
3948
3978
  outputReservation: bodyOutputReservation(args.body), indexBehind: args.rec.indexBehind === true,
3949
- budgetTokens, modelContextLimit: args.modelContextLimit,
3979
+ budgetTokens, modelContextLimit: args.modelContextLimit, ...(unpaid !== null ? { paused: true } : {}),
3950
3980
  };
3981
+ if (unpaid !== null)
3982
+ cancelPausedNotice(args.state, args.rec);
3951
3983
  if (args.res.headersSent || args.res.destroyed)
3952
3984
  return false;
3953
3985
  args.res.writeHead(failure.status, { "content-type": "application/json", ...failure.headers });
3954
3986
  args.res.end(JSON.stringify(failure.body));
3987
+ if (unpaid !== null) {
3988
+ notePausedRefusal(args.state, args.rec, args.routeKey, requestSessionId(args.req));
3989
+ }
3955
3990
  return false;
3956
3991
  }
3992
+ /**
3993
+ * The refusal text already says MemTree is paused and how to resume, so it
3994
+ * counts as the paused notice: the throttle restarts, and this request's
3995
+ * queued notice is withdrawn rather than shown a second time after it.
3996
+ */
3997
+ function notePausedRefusal(state, rec, lane, sessionId) {
3998
+ // A subagent's or hidden away-summary's refusal is not on the user's screen.
3999
+ if (rec.routeLane === "main") {
4000
+ state.paymentNotice.markShown();
4001
+ // Past the window /compact no longer fits: withdraw an undelivered
4002
+ // near-window line and show none later.
4003
+ state.notices.cancel(state.nearWindowNotice);
4004
+ if (sessionId !== undefined)
4005
+ addNearWindowWarned(state, sessionId);
4006
+ }
4007
+ // A tool turn in the outage cooldown or its lane's growth backoff refuses on
4008
+ // the last 402 without asking. End both so the next request asks MemTree
4009
+ // again (a 402 answers fast) and paying takes effect with no restart. A
4010
+ // concurrent attempt still compressing (no backoff yet) keeps its slot.
4011
+ state.toolRecoveryCooldownUntil = 0;
4012
+ const attempt = state.toolRecoveryAttemptedLanes.get(lane);
4013
+ if (attempt && (!attempt.inFlight || attempt.retryAtTokens !== undefined)) {
4014
+ state.toolRecoveryAttemptedLanes.delete(lane);
4015
+ }
4016
+ }
4017
+ /** Withdraw the paused notice this request queued: its refusal says the same. */
4018
+ function cancelPausedNotice(state, rec) {
4019
+ state.notices.cancel(pausedNotices.get(rec));
4020
+ pausedNotices.delete(rec);
4021
+ }
4022
+ /**
4023
+ * A paused main-lane request over the budget, still fitting the window (often a
4024
+ * tool round): once per session, say /compact still works now. A turn that
4025
+ * already queued the paused notice carries the line itself.
4026
+ */
4027
+ function warnPausedNearWindow(state, rec, sessionId) {
4028
+ const detail = state.unpaidDetail();
4029
+ // Paid again: a later pause (next month's limit) warns afresh.
4030
+ if (detail === null)
4031
+ state.pausedNearWindowWarned.clear();
4032
+ // Never replace another queued notice (or this one again): a later request tries.
4033
+ if (detail === null || rec.routeLane !== "main" || sessionId === undefined ||
4034
+ pausedNotices.has(rec) || nearWindowWarned(state, sessionId) ||
4035
+ state.notices.hasPending(state.mainPromptId))
4036
+ return;
4037
+ state.nearWindowNotice = state.notices.queueSuffix(paymentNoticeText(detail, undefined, { overBudget: true }), () => {
4038
+ state.paymentNotice.markShown();
4039
+ addNearWindowWarned(state, sessionId);
4040
+ state.diagnostics?.record("paused_notice", { trigger: "turn" });
4041
+ }, state.mainPromptId);
4042
+ }
4043
+ /** Remember a warned session, keeping the set bounded (oldest first out). */
4044
+ function addNearWindowWarned(state, sessionId) {
4045
+ state.pausedNearWindowWarned.delete(sessionId);
4046
+ state.pausedNearWindowWarned.add(sessionId);
4047
+ if (state.pausedNearWindowWarned.size > MEMTREE_LINKS_MAX_SESSIONS) {
4048
+ const oldest = state.pausedNearWindowWarned.values().next().value;
4049
+ if (oldest !== undefined)
4050
+ state.pausedNearWindowWarned.delete(oldest);
4051
+ }
4052
+ }
4053
+ function nearWindowWarned(state, sessionId) {
4054
+ return sessionId !== undefined && state.pausedNearWindowWarned.has(sessionId);
4055
+ }
4056
+ /** Whether this turn's own size estimate reached its whole-request budget. */
4057
+ function reachedBudget(compaction) {
4058
+ return compaction !== undefined && (compaction.estimatedTokens ?? 0) >= compaction.budgetTokens;
4059
+ }
4060
+ /** The paused notice a degraded request queued, until it is refused or settles. */
4061
+ const pausedNotices = new WeakMap();
3957
4062
  const changedDeliveryRecords = new WeakSet();
3958
4063
  const selectedCandidates = new WeakMap();
3959
4064
  const candidateSizeOwners = new WeakMap();
@@ -4336,6 +4441,9 @@ function forwardAccepted(req, res, bodyBuffer, opts, upstream, shutdownSignal, r
4336
4441
  observed = decodeForObservation(Buffer.concat(observedChunks), contentEncoding);
4337
4442
  if (rec && observed && !isSse) {
4338
4443
  mergeUsageFromJsonBody(observed, rec);
4444
+ if ((upstreamRes.statusCode ?? 0) >= 400) {
4445
+ rec.upstreamError = upstreamErrorCode(observed);
4446
+ }
4339
4447
  }
4340
4448
  }
4341
4449
  if (isSse) {