claude-code-infinite 1.0.24 → 1.0.25

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/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,7 +56,7 @@ 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";
@@ -303,7 +303,9 @@ export function startProxy(opts) {
303
303
  const shutdownAbort = new AbortController();
304
304
  const state = {
305
305
  paymentNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
306
+ pausedNearWindowWarned: new Set(),
306
307
  unpaidDetail: () => opts.memtree.paymentRequiredDetail,
308
+ pausedDetail: () => opts.memtree.pausedDetail,
307
309
  usageUrl: opts.memtree.usageDashboardUrl,
308
310
  sessionStartNotice: opts.sessionStartNotice,
309
311
  diagnostics: opts.diagnostics,
@@ -1717,11 +1719,20 @@ async function handleMessages(req, res, opts, upstream, state) {
1717
1719
  capture(opts, "anthropic-request", forwardBody);
1718
1720
  const paymentDetail = opts.memtree.paymentRequiredDetail;
1719
1721
  const mayQueueNotice = displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration;
1720
- if (mayQueueNotice && paymentDetail !== null && state.paymentNotice.due()) {
1721
- state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link), () => {
1722
+ const overBudget = reachedBudget(rec.compaction);
1723
+ if (mayQueueNotice && paymentDetail !== null &&
1724
+ (state.paymentNotice.due() || (overBudget && !nearWindowWarned(state, sessionId)))) {
1725
+ // A paused refusal of this very request withdraws it (refuseWholeRequest),
1726
+ // and any later one withdraws its near-window line.
1727
+ const handle = state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link, { overBudget }), () => {
1722
1728
  state.paymentNotice.markShown();
1729
+ if (overBudget && sessionId !== undefined)
1730
+ addNearWindowWarned(state, sessionId);
1723
1731
  state.diagnostics?.record("paused_notice", { trigger: "turn" });
1724
1732
  }, noticePromptId);
1733
+ pausedNotices.set(rec, handle);
1734
+ if (overBudget)
1735
+ state.nearWindowNotice = handle;
1725
1736
  }
1726
1737
  else if (mayQueueNotice && paymentDetail === null) {
1727
1738
  state.notices.queueSuffix(DEGRADED_NOTICE, undefined, noticePromptId);
@@ -1976,10 +1987,15 @@ function joinNoticeLines(...parts) {
1976
1987
  export const MEMTREE_COMPACT_MIN_TOKENS = 20_000;
1977
1988
  /** The reply to a ccc slash command, or undefined for an ordinary prompt. */
1978
1989
  function sessionCommandReply(state, sessionId, prompt) {
1990
+ const unpaid = state.unpaidDetail();
1991
+ // Paused, compaction needs the MemTree it cannot reach: say how to resume instead.
1992
+ const paused = unpaid === null ? undefined
1993
+ : pausedCompactionText(viewUrl(subscribeUrlFromDetail(unpaid)));
1979
1994
  if (sessionCommandArgs(prompt, MEMTREE_HELP_COMMAND) !== undefined) {
1980
1995
  return [
1981
1996
  "• /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)`,
1997
+ paused !== undefined ? `• ${paused}`
1998
+ : `• /memtree-compact [tokens | off] · compact this session on your next message (default half the budget, at least ${MEMTREE_COMPACT_MIN_TOKENS / 1000}k)`,
1983
1999
  usageLines(state),
1984
2000
  ].filter((line) => line !== undefined).join("\n");
1985
2001
  }
@@ -2000,6 +2016,8 @@ function sessionCommandReply(state, sessionId, prompt) {
2000
2016
  state.storedPrefixLookups.set(sessionId, null);
2001
2017
  return `${TRAILER_LABEL} compaction off: the conversation is sent whole, and MemTree compresses only when it outgrows the model's budget.`;
2002
2018
  }
2019
+ if (paused !== undefined)
2020
+ return `${TRAILER_LABEL} ${paused}`;
2003
2021
  if (args === "") {
2004
2022
  // Back to the automatic target (half the budget, or CCC_COMPACT_TARGET).
2005
2023
  // Under CCC_COMPACT_TARGET=off the default is off, so pin the automatic
@@ -2082,7 +2100,9 @@ function viewUrl(url) {
2082
2100
  */
2083
2101
  function sessionLink(state, sessionId, source) {
2084
2102
  const newest = state.newestTrees?.peek(sessionId);
2085
- const note = source && !source.compressed ? { note: NOT_COMPRESSED_NOTE } : {};
2103
+ // The note suggests /memtree-compact, which cannot run while MemTree is paused.
2104
+ const note = source && !source.compressed && state.unpaidDetail() === null
2105
+ ? { note: NOT_COMPRESSED_NOTE } : {};
2086
2106
  if (newest)
2087
2107
  return { key: newest.key, link: newest.url, ...note };
2088
2108
  return source ? { key: source.index, link: source.url, ...note } : undefined;
@@ -2193,6 +2213,9 @@ function noteMemtreeHealth(state, compression) {
2193
2213
  }
2194
2214
  if (compression.liveHealth === "success") {
2195
2215
  state.toolRecoveryCooldownUntil = 0;
2216
+ // Paid again: a later pause (next month's limit) warns near the window afresh.
2217
+ if (state.unpaidDetail() === null)
2218
+ state.pausedNearWindowWarned.clear();
2196
2219
  }
2197
2220
  }
2198
2221
  /**
@@ -3915,6 +3938,7 @@ async function selectForwardCandidate(args) {
3915
3938
  args.rec.overBudgetForward = { reason: measurement.source === "exact"
3916
3939
  ? "exact-count-fits-window" : measurement.source === "bytes"
3917
3940
  ? "bytes-fallback-fits-window" : "estimate-fits-window" };
3941
+ warnPausedNearWindow(args.state, args.rec, requestSessionId(args.req));
3918
3942
  }
3919
3943
  return changed ? candidate.body : intended;
3920
3944
  }
@@ -3934,7 +3958,9 @@ async function wholeRequestRefused(sizing, candidate, limits) {
3934
3958
  }
3935
3959
  /** Write the bounded fallback refusal (route-fallback.ts); always false. */
3936
3960
  function refuseWholeRequest(args, inputTokens, budgetTokens) {
3937
- const failure = args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
3961
+ const unpaid = args.state.pausedDetail();
3962
+ const failure = unpaid !== null ? pausedRefusal(pausedRefusalMessage(unpaid))
3963
+ : args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
3938
3964
  args.rec.forwardedBytes = 0;
3939
3965
  args.rec.approxInputTokens = 0;
3940
3966
  args.state.diagnostics?.record("refusal", {
@@ -3946,14 +3972,89 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
3946
3972
  lane: args.routeKey, attempt: failure.attempt, status: failure.status,
3947
3973
  inputTokens, outputTokens: args.body.max_tokens ?? 0,
3948
3974
  outputReservation: bodyOutputReservation(args.body), indexBehind: args.rec.indexBehind === true,
3949
- budgetTokens, modelContextLimit: args.modelContextLimit,
3975
+ budgetTokens, modelContextLimit: args.modelContextLimit, ...(unpaid !== null ? { paused: true } : {}),
3950
3976
  };
3977
+ if (unpaid !== null)
3978
+ cancelPausedNotice(args.state, args.rec);
3951
3979
  if (args.res.headersSent || args.res.destroyed)
3952
3980
  return false;
3953
3981
  args.res.writeHead(failure.status, { "content-type": "application/json", ...failure.headers });
3954
3982
  args.res.end(JSON.stringify(failure.body));
3983
+ if (unpaid !== null) {
3984
+ notePausedRefusal(args.state, args.rec, args.routeKey, requestSessionId(args.req));
3985
+ }
3955
3986
  return false;
3956
3987
  }
3988
+ /**
3989
+ * The refusal text already says MemTree is paused and how to resume, so it
3990
+ * counts as the paused notice: the throttle restarts, and this request's
3991
+ * queued notice is withdrawn rather than shown a second time after it.
3992
+ */
3993
+ function notePausedRefusal(state, rec, lane, sessionId) {
3994
+ // A subagent's or hidden away-summary's refusal is not on the user's screen.
3995
+ if (rec.routeLane === "main") {
3996
+ state.paymentNotice.markShown();
3997
+ // Past the window /compact no longer fits: withdraw an undelivered
3998
+ // near-window line and show none later.
3999
+ state.notices.cancel(state.nearWindowNotice);
4000
+ if (sessionId !== undefined)
4001
+ addNearWindowWarned(state, sessionId);
4002
+ }
4003
+ // A tool turn in the outage cooldown or its lane's growth backoff refuses on
4004
+ // the last 402 without asking. End both so the next request asks MemTree
4005
+ // again (a 402 answers fast) and paying takes effect with no restart. A
4006
+ // concurrent attempt still compressing (no backoff yet) keeps its slot.
4007
+ state.toolRecoveryCooldownUntil = 0;
4008
+ const attempt = state.toolRecoveryAttemptedLanes.get(lane);
4009
+ if (attempt && (!attempt.inFlight || attempt.retryAtTokens !== undefined)) {
4010
+ state.toolRecoveryAttemptedLanes.delete(lane);
4011
+ }
4012
+ }
4013
+ /** Withdraw the paused notice this request queued: its refusal says the same. */
4014
+ function cancelPausedNotice(state, rec) {
4015
+ state.notices.cancel(pausedNotices.get(rec));
4016
+ pausedNotices.delete(rec);
4017
+ }
4018
+ /**
4019
+ * A paused main-lane request over the budget, still fitting the window (often a
4020
+ * tool round): once per session, say /compact still works now. A turn that
4021
+ * already queued the paused notice carries the line itself.
4022
+ */
4023
+ function warnPausedNearWindow(state, rec, sessionId) {
4024
+ const detail = state.unpaidDetail();
4025
+ // Paid again: a later pause (next month's limit) warns afresh.
4026
+ if (detail === null)
4027
+ state.pausedNearWindowWarned.clear();
4028
+ // Never replace another queued notice (or this one again): a later request tries.
4029
+ if (detail === null || rec.routeLane !== "main" || sessionId === undefined ||
4030
+ pausedNotices.has(rec) || nearWindowWarned(state, sessionId) ||
4031
+ state.notices.hasPending(state.mainPromptId))
4032
+ return;
4033
+ state.nearWindowNotice = state.notices.queueSuffix(paymentNoticeText(detail, undefined, { overBudget: true }), () => {
4034
+ state.paymentNotice.markShown();
4035
+ addNearWindowWarned(state, sessionId);
4036
+ state.diagnostics?.record("paused_notice", { trigger: "turn" });
4037
+ }, state.mainPromptId);
4038
+ }
4039
+ /** Remember a warned session, keeping the set bounded (oldest first out). */
4040
+ function addNearWindowWarned(state, sessionId) {
4041
+ state.pausedNearWindowWarned.delete(sessionId);
4042
+ state.pausedNearWindowWarned.add(sessionId);
4043
+ if (state.pausedNearWindowWarned.size > MEMTREE_LINKS_MAX_SESSIONS) {
4044
+ const oldest = state.pausedNearWindowWarned.values().next().value;
4045
+ if (oldest !== undefined)
4046
+ state.pausedNearWindowWarned.delete(oldest);
4047
+ }
4048
+ }
4049
+ function nearWindowWarned(state, sessionId) {
4050
+ return sessionId !== undefined && state.pausedNearWindowWarned.has(sessionId);
4051
+ }
4052
+ /** Whether this turn's own size estimate reached its whole-request budget. */
4053
+ function reachedBudget(compaction) {
4054
+ return compaction !== undefined && (compaction.estimatedTokens ?? 0) >= compaction.budgetTokens;
4055
+ }
4056
+ /** The paused notice a degraded request queued, until it is refused or settles. */
4057
+ const pausedNotices = new WeakMap();
3957
4058
  const changedDeliveryRecords = new WeakSet();
3958
4059
  const selectedCandidates = new WeakMap();
3959
4060
  const candidateSizeOwners = new WeakMap();