claude-code-infinite 1.0.27 → 1.0.28
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 +25 -18
- package/dist/cli-args.d.ts +5 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +9 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-main.js +135 -70
- package/dist/cli-main.js.map +1 -1
- package/dist/hooks.d.ts +8 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +57 -25
- package/dist/hooks.js.map +1 -1
- package/dist/key-page.d.ts +17 -0
- package/dist/key-page.d.ts.map +1 -0
- package/dist/key-page.js +32 -0
- package/dist/key-page.js.map +1 -0
- package/dist/key-status.d.ts +79 -0
- package/dist/key-status.d.ts.map +1 -0
- package/dist/key-status.js +143 -0
- package/dist/key-status.js.map +1 -0
- package/dist/memtree.d.ts +23 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +39 -3
- package/dist/memtree.js.map +1 -1
- package/dist/notices.d.ts +5 -0
- package/dist/notices.d.ts.map +1 -1
- package/dist/notices.js +13 -0
- package/dist/notices.js.map +1 -1
- package/dist/paused-session.d.ts +29 -0
- package/dist/paused-session.d.ts.map +1 -0
- package/dist/paused-session.js +148 -0
- package/dist/paused-session.js.map +1 -0
- package/dist/payment-gate.d.ts +44 -13
- package/dist/payment-gate.d.ts.map +1 -1
- package/dist/payment-gate.js +82 -23
- package/dist/payment-gate.js.map +1 -1
- package/dist/payment-link.d.ts +68 -0
- package/dist/payment-link.d.ts.map +1 -0
- package/dist/payment-link.js +123 -0
- package/dist/payment-link.js.map +1 -0
- package/dist/project-trees.d.ts +100 -0
- package/dist/project-trees.d.ts.map +1 -0
- package/dist/project-trees.js +196 -0
- package/dist/project-trees.js.map +1 -0
- package/dist/proxy.d.ts +13 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +70 -8
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +2 -0
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Earlier MemTree sessions in this project, named to the model when a session
|
|
3
|
+
* starts, so earlier decisions and fixes are one lookup away.
|
|
4
|
+
*
|
|
5
|
+
* The server answers `GET /v1/memtree/project_trees?project=&limit=` (polychat
|
|
6
|
+
* routes/memtree_project_trees.py): the user's own newest sessions in this
|
|
7
|
+
* project that have a finished tree, each with its root summary and tree
|
|
8
|
+
* reference. ccc asks once at startup (before Claude Code is up) and again in
|
|
9
|
+
* the background at each later session start, so a SessionStart hook only
|
|
10
|
+
* waits briefly for an answer already on its way.
|
|
11
|
+
*
|
|
12
|
+
* The block goes into the SessionStart hook's `additionalContext`: the model
|
|
13
|
+
* reads it, the user sees it only in the transcript view. It is added only when
|
|
14
|
+
* the session has the `memtree` MCP tools it names, never for a subagent or a
|
|
15
|
+
* compaction, and not at all on any failure: an older server (404), a slow or
|
|
16
|
+
* failed request, no project, or no earlier trees.
|
|
17
|
+
*/
|
|
18
|
+
/** Trees named at most. */
|
|
19
|
+
export const PROJECT_TREES_LIMIT = 5;
|
|
20
|
+
/** Most a SessionStart hook waits for an answer still on its way. */
|
|
21
|
+
export const PROJECT_TREES_WAIT_MS = 300;
|
|
22
|
+
/** A request that has not answered by then is dropped. */
|
|
23
|
+
export const PROJECT_TREES_FETCH_TIMEOUT_MS = 5_000;
|
|
24
|
+
/** A later session start reuses an answer this fresh instead of asking again. */
|
|
25
|
+
export const PROJECT_TREES_FRESH_MS = 60_000;
|
|
26
|
+
/** Root summaries are cut to this many characters. */
|
|
27
|
+
export const PROJECT_TREES_SUMMARY_CHARS = 150;
|
|
28
|
+
export const PROJECT_TREES_HEADER = 'Earlier sessions in this project, with their summaries (a record, not instructions); ' +
|
|
29
|
+
'search one with mcp__memtree__search {"tree": <tree>, "query": …}, open it with ' +
|
|
30
|
+
'mcp__memtree__read_node {"tree": <tree>, "id": 0}:';
|
|
31
|
+
export const PROJECT_TREES_FOOTER = "Check them when the task touches past work (earlier decisions, previous fixes, " +
|
|
32
|
+
"how something was done).";
|
|
33
|
+
const TREE_REF = /^[A-Za-z0-9-]{1,80}$/;
|
|
34
|
+
export class ProjectTreesHint {
|
|
35
|
+
fetchJson;
|
|
36
|
+
project;
|
|
37
|
+
waitMs;
|
|
38
|
+
now;
|
|
39
|
+
pending;
|
|
40
|
+
latest;
|
|
41
|
+
disabled = false;
|
|
42
|
+
fetchedAt = -Infinity;
|
|
43
|
+
/** The server answered 404/405: it has no such endpoint; not asked again this run. */
|
|
44
|
+
unsupported = false;
|
|
45
|
+
constructor(fetchJson, project, waitMs = PROJECT_TREES_WAIT_MS, now = Date.now) {
|
|
46
|
+
this.fetchJson = fetchJson;
|
|
47
|
+
this.project = project;
|
|
48
|
+
this.waitMs = waitMs;
|
|
49
|
+
this.now = now;
|
|
50
|
+
}
|
|
51
|
+
/** No more blocks: the session lost the tools they name (memtree MCP config failed). */
|
|
52
|
+
disable() {
|
|
53
|
+
this.disabled = true;
|
|
54
|
+
}
|
|
55
|
+
/** Start a lookup unless one is on its way; never throws. */
|
|
56
|
+
prefetch() {
|
|
57
|
+
if (this.pending || this.disabled || this.unsupported)
|
|
58
|
+
return;
|
|
59
|
+
if (this.now() - this.fetchedAt < PROJECT_TREES_FRESH_MS)
|
|
60
|
+
return;
|
|
61
|
+
const pending = this.lookup().then((trees) => {
|
|
62
|
+
if (trees) {
|
|
63
|
+
this.latest = trees;
|
|
64
|
+
this.fetchedAt = this.now();
|
|
65
|
+
}
|
|
66
|
+
return trees;
|
|
67
|
+
});
|
|
68
|
+
this.pending = pending;
|
|
69
|
+
void pending.finally(() => {
|
|
70
|
+
if (this.pending === pending)
|
|
71
|
+
this.pending = undefined;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The block for a session starting now, or undefined. Waits at most
|
|
76
|
+
* `waitMs` for a lookup on its way, then refreshes in the background for
|
|
77
|
+
* the next session start (an in-app `/clear` or `/resume`).
|
|
78
|
+
*/
|
|
79
|
+
async contextFor(sessionId) {
|
|
80
|
+
if (this.disabled)
|
|
81
|
+
return undefined;
|
|
82
|
+
const pending = this.pending;
|
|
83
|
+
const trees = pending ? await withinMs(pending, this.waitMs) : undefined;
|
|
84
|
+
const known = trees ?? this.latest;
|
|
85
|
+
this.prefetch();
|
|
86
|
+
return known ? formatProjectTrees(known, sessionId) : undefined;
|
|
87
|
+
}
|
|
88
|
+
async lookup() {
|
|
89
|
+
const query = new URLSearchParams({
|
|
90
|
+
project: this.project,
|
|
91
|
+
// One more, so leaving out the current session still names five.
|
|
92
|
+
limit: String(PROJECT_TREES_LIMIT + 1),
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
const response = await this.fetchJson(`/v1/memtree/project_trees?${query.toString()}`, AbortSignal.timeout(PROJECT_TREES_FETCH_TIMEOUT_MS));
|
|
96
|
+
if (response.status === 404 || response.status === 405)
|
|
97
|
+
this.unsupported = true;
|
|
98
|
+
if (response.status !== 200)
|
|
99
|
+
return undefined;
|
|
100
|
+
return parseProjectTrees(JSON.parse(response.body.toString("utf-8")));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The project name the server matches: the `owner/repo` of the `origin`
|
|
109
|
+
* remote. A directory name alone ("app", "src") would also match unrelated
|
|
110
|
+
* directories and repos of that name, so without a remote there is no hint.
|
|
111
|
+
*/
|
|
112
|
+
export function projectIdentity(meta) {
|
|
113
|
+
return meta?.git_repo || undefined;
|
|
114
|
+
}
|
|
115
|
+
/** The server's `trees`, keeping only well-formed entries. */
|
|
116
|
+
export function parseProjectTrees(body) {
|
|
117
|
+
const trees = body?.trees;
|
|
118
|
+
if (!Array.isArray(trees))
|
|
119
|
+
return undefined;
|
|
120
|
+
const parsed = [];
|
|
121
|
+
for (const item of trees) {
|
|
122
|
+
const tree = parseTree(item);
|
|
123
|
+
if (tree)
|
|
124
|
+
parsed.push(tree);
|
|
125
|
+
}
|
|
126
|
+
return parsed;
|
|
127
|
+
}
|
|
128
|
+
/** The context block, or undefined when no tree but the current session's is left. */
|
|
129
|
+
export function formatProjectTrees(trees, currentSessionId) {
|
|
130
|
+
const shown = trees
|
|
131
|
+
.filter((tree) => !currentSessionId || tree.session_id !== currentSessionId)
|
|
132
|
+
.slice(0, PROJECT_TREES_LIMIT);
|
|
133
|
+
if (!shown.length)
|
|
134
|
+
return undefined;
|
|
135
|
+
const lines = shown.map((tree) => {
|
|
136
|
+
const date = /^\d{4}-\d{2}-\d{2}/.exec(tree.last_at ?? "")?.[0];
|
|
137
|
+
const summary = shorten(tree.title, PROJECT_TREES_SUMMARY_CHARS);
|
|
138
|
+
return `- ${date ? `${date}: ` : ""}${summary} (tree ${tree.ref})`;
|
|
139
|
+
});
|
|
140
|
+
return [PROJECT_TREES_HEADER, ...lines, PROJECT_TREES_FOOTER].join("\n");
|
|
141
|
+
}
|
|
142
|
+
function parseTree(item) {
|
|
143
|
+
if (!item || typeof item !== "object")
|
|
144
|
+
return undefined;
|
|
145
|
+
const raw = item;
|
|
146
|
+
const tree = raw.tree;
|
|
147
|
+
const ref = typeof tree?.ref === "string" ? tree.ref : tree?.request_id;
|
|
148
|
+
if (typeof ref !== "string" || !TREE_REF.test(ref))
|
|
149
|
+
return undefined;
|
|
150
|
+
if (typeof raw.title !== "string" || !raw.title.trim())
|
|
151
|
+
return undefined;
|
|
152
|
+
return {
|
|
153
|
+
session_id: typeof raw.session_id === "string" ? raw.session_id : null,
|
|
154
|
+
last_at: typeof raw.last_at === "string" ? raw.last_at : null,
|
|
155
|
+
title: raw.title,
|
|
156
|
+
ref,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function shorten(text, limit) {
|
|
160
|
+
// One line: a summary is model-written and must not add lines of its own.
|
|
161
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
162
|
+
return flat.length <= limit ? flat : `${flat.slice(0, limit - 1).trimEnd()}…`;
|
|
163
|
+
}
|
|
164
|
+
/** `promise`'s value, or undefined if it takes longer than `ms`. */
|
|
165
|
+
async function withinMs(promise, ms) {
|
|
166
|
+
let timer;
|
|
167
|
+
const timeout = new Promise((resolve) => {
|
|
168
|
+
timer = setTimeout(() => resolve(undefined), ms);
|
|
169
|
+
timer.unref?.();
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
return await Promise.race([promise, timeout]);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
if (timer)
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The hint for this run, or undefined: interactive sessions only (print runs
|
|
181
|
+
* keep Claude Code's output as it is) with MemTree on (a valid key, not
|
|
182
|
+
* continued past the payment gate: the lookup could only fail), with both
|
|
183
|
+
* `search` and `read_node` (the block names them), a known project, and not
|
|
184
|
+
* `CCC_PROJECT_TREES=0`.
|
|
185
|
+
*/
|
|
186
|
+
export function projectTreesHintFor(s) {
|
|
187
|
+
if (!s.interactive || s.env.CCC_PROJECT_TREES === "0")
|
|
188
|
+
return undefined;
|
|
189
|
+
if (s.keyCheck === "invalid" || s.memtreeOff)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (!s.tools.includes("search") || !s.tools.includes("read_node"))
|
|
192
|
+
return undefined;
|
|
193
|
+
const project = projectIdentity(s.meta);
|
|
194
|
+
return project ? new ProjectTreesHint(s.fetchJson, project) : undefined;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=project-trees.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-trees.js","sourceRoot":"","sources":["../src/project-trees.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ import type { StablePrefixStore } from "./prefix-store.js";
|
|
|
48
48
|
import type { NewestTreeLookup } from "./memtree-newest.js";
|
|
49
49
|
import type { TranscriptUsageSource } from "./transcript-usage.js";
|
|
50
50
|
import type { ProjectMeta } from "./project-meta.js";
|
|
51
|
+
import type { ProjectTreesHint } from "./project-trees.js";
|
|
51
52
|
import type { DiagnosticsRecorder } from "./diagnostics.js";
|
|
52
53
|
import { type MessagesRecord, type RequestLogSink } from "./reqlog.js";
|
|
53
54
|
export interface ProxyOptions {
|
|
@@ -185,6 +186,11 @@ export interface ProxyOptions {
|
|
|
185
186
|
* provider decides how often it has something to say.
|
|
186
187
|
*/
|
|
187
188
|
sessionStartNotice?: () => string | undefined;
|
|
189
|
+
/**
|
|
190
|
+
* Earlier MemTree sessions in this project, added to a main session's
|
|
191
|
+
* SessionStart as model context (project-trees.ts). Omitted means none.
|
|
192
|
+
*/
|
|
193
|
+
projectTrees?: ProjectTreesHint;
|
|
188
194
|
/**
|
|
189
195
|
* Client diagnostics (diagnostics.ts): session starts, paused notices shown,
|
|
190
196
|
* and refusals, as allowlisted enums and numbers. Never content.
|
|
@@ -239,7 +245,14 @@ export declare function fallbackBudgetRatio(model: unknown, modelContextLimit: n
|
|
|
239
245
|
export declare function startProxy(opts: ProxyOptions): Promise<RunningProxy>;
|
|
240
246
|
/** The page id in a server-stamped link (`…/m/<id>` or `…/usage/memtree/<id>`). */
|
|
241
247
|
export declare function memtreePageId(pageUrl: string): string | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* The SessionStart hook's answer: the banner line for the user
|
|
250
|
+
* (`systemMessage`), model context (`additionalContext`), or both.
|
|
251
|
+
*/
|
|
252
|
+
export declare function sessionStartOutput(line: string | undefined, context: string | undefined): Record<string, unknown>;
|
|
242
253
|
export declare const MEMTREE_COMPACT_MIN_TOKENS = 20000;
|
|
254
|
+
/** How long a paused turn waits for the short checkout link before its notice (once). */
|
|
255
|
+
export declare const PAYMENT_LINK_NOTICE_WAIT_MS = 1000;
|
|
243
256
|
export declare const SUBAGENT_VIEW_NOTE = "(main session's page; subagent pages aren't linked yet)";
|
|
244
257
|
export declare const SUBAGENT_COMPACT_REPLY = "MemTree compact applies to the main session only.";
|
|
245
258
|
/** "50k", "50000", "1.5m" → tokens; undefined when not a positive count. */
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAUvD,OAAO,EAAiB,qBAAqB,EACD,MAAM,qBAAqB,CAAC;AAiBxE,OAAO,EAKL,aAAa,EAMd,MAAM,cAAc,CAAC;AAwBtB,OAAO,EAYL,KAAK,cAAc,EAGpB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy.js
CHANGED
|
@@ -56,6 +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 { keyInvalidNoticeText, keyInvalidRefusalMessage } from "./key-status.js";
|
|
59
60
|
import { PaymentNoticeThrottle, pausedCompactionText, pausedRefusalMessage, paymentNoticeText, subscribeUrlFromDetail } from "./payment-notice.js";
|
|
60
61
|
import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
|
|
61
62
|
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
|
|
@@ -304,11 +305,15 @@ export function startProxy(opts) {
|
|
|
304
305
|
const shutdownAbort = new AbortController();
|
|
305
306
|
const state = {
|
|
306
307
|
paymentNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
|
|
308
|
+
keyNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
|
|
309
|
+
keyInvalid: () => opts.memtree.keyInvalid,
|
|
310
|
+
keyPageUrl: opts.memtree.keyPageUrl,
|
|
307
311
|
pausedNearWindowWarned: new Set(),
|
|
308
312
|
unpaidDetail: () => opts.memtree.paymentRequiredDetail,
|
|
309
313
|
pausedDetail: () => opts.memtree.pausedDetail,
|
|
310
314
|
usageUrl: opts.memtree.usageDashboardUrl,
|
|
311
315
|
sessionStartNotice: opts.sessionStartNotice,
|
|
316
|
+
projectTrees: opts.projectTrees,
|
|
312
317
|
diagnostics: opts.diagnostics,
|
|
313
318
|
notices: new NoticeDeliveryQueue(),
|
|
314
319
|
mainNoticePending: false,
|
|
@@ -726,17 +731,20 @@ onMainStop) {
|
|
|
726
731
|
recordSessionStart(state, parsed.source);
|
|
727
732
|
noteMainSessionStart(state, parsed.source, parsed.session_id);
|
|
728
733
|
}
|
|
734
|
+
// Earlier trees in this project: asked now, so its wait overlaps the resume's.
|
|
735
|
+
const contextPromise = projectTreesContext(state.projectTrees, parsed);
|
|
729
736
|
// A resume waits briefly for the newest tree; a fresh session has none to wait for.
|
|
730
737
|
if (parsed.agent_id === undefined && parsed.source === "resume") {
|
|
731
738
|
await state.newestTrees?.settle(parsed.session_id);
|
|
732
739
|
}
|
|
733
740
|
const line = joinNoticeLines(resumeLinkLine(state, parsed), resumePaymentNotice(state, parsed), extraSessionStartLine(state.sessionStartNotice, parsed));
|
|
734
|
-
|
|
741
|
+
const context = await contextPromise;
|
|
742
|
+
if (!line && !context) {
|
|
735
743
|
res.writeHead(204);
|
|
736
744
|
res.end();
|
|
737
745
|
return;
|
|
738
746
|
}
|
|
739
|
-
const body = Buffer.from(JSON.stringify(
|
|
747
|
+
const body = Buffer.from(JSON.stringify(sessionStartOutput(line, context)), "utf-8");
|
|
740
748
|
res.writeHead(200, {
|
|
741
749
|
"content-type": "application/json",
|
|
742
750
|
"content-length": String(body.length),
|
|
@@ -1697,6 +1705,13 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1697
1705
|
// it pending for the next turn.
|
|
1698
1706
|
if (result && sessionId !== undefined)
|
|
1699
1707
|
state.compactNow.delete(sessionId);
|
|
1708
|
+
// A first 402 starts fetching the short checkout link (memtree.ts): give the
|
|
1709
|
+
// paused notice below a moment to show it instead of the long one. Only a turn
|
|
1710
|
+
// that can show the notice waits, and only while that fetch is in flight.
|
|
1711
|
+
if (!result && displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration &&
|
|
1712
|
+
state.paymentNotice.due() && opts.memtree.paymentRequiredDetail !== null) {
|
|
1713
|
+
await opts.memtree.paymentLinkSettled?.(PAYMENT_LINK_NOTICE_WAIT_MS);
|
|
1714
|
+
}
|
|
1700
1715
|
if (downstreamClosedDuringCompression ||
|
|
1701
1716
|
(res.destroyed && !res.writableFinished)) {
|
|
1702
1717
|
releaseUncommittedRouteDecision();
|
|
@@ -1724,7 +1739,15 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1724
1739
|
const paymentDetail = opts.memtree.paymentRequiredDetail;
|
|
1725
1740
|
const mayQueueNotice = displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration;
|
|
1726
1741
|
const overBudget = reachedBudget(rec.compaction);
|
|
1727
|
-
if (
|
|
1742
|
+
if (state.keyInvalid()) {
|
|
1743
|
+
// A refused key (401/403) says why MemTree is off and how to fix it;
|
|
1744
|
+
// throttled like the payment notice, and quiet in between.
|
|
1745
|
+
if (mayQueueNotice && state.keyNotice.due()) {
|
|
1746
|
+
// A refusal of this very request says the same and withdraws it.
|
|
1747
|
+
pausedNotices.set(rec, state.notices.queueSuffix(keyInvalidNoticeText(state.keyPageUrl), () => state.keyNotice.markShown(), noticePromptId));
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
else if (mayQueueNotice && paymentDetail !== null &&
|
|
1728
1751
|
(state.paymentNotice.due() || (overBudget && !nearWindowWarned(state, sessionId)))) {
|
|
1729
1752
|
// A paused refusal of this very request withdraws it (refuseWholeRequest),
|
|
1730
1753
|
// and any later one withdraws its near-window line.
|
|
@@ -1984,11 +2007,39 @@ function extraSessionStartLine(provider, input) {
|
|
|
1984
2007
|
return undefined;
|
|
1985
2008
|
}
|
|
1986
2009
|
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Earlier sessions in this project (project-trees.ts) for a main session's
|
|
2012
|
+
* start: fresh, resumed, cleared or forked, never a compaction. Never throws.
|
|
2013
|
+
*/
|
|
2014
|
+
async function projectTreesContext(hint, input) {
|
|
2015
|
+
if (!hint || input.agent_id !== undefined || input.source === "compact")
|
|
2016
|
+
return undefined;
|
|
2017
|
+
try {
|
|
2018
|
+
return await hint.contextFor(input.session_id);
|
|
2019
|
+
}
|
|
2020
|
+
catch {
|
|
2021
|
+
return undefined;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* The SessionStart hook's answer: the banner line for the user
|
|
2026
|
+
* (`systemMessage`), model context (`additionalContext`), or both.
|
|
2027
|
+
*/
|
|
2028
|
+
export function sessionStartOutput(line, context) {
|
|
2029
|
+
return {
|
|
2030
|
+
...(line ? { systemMessage: line } : {}),
|
|
2031
|
+
...(context
|
|
2032
|
+
? { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: context } }
|
|
2033
|
+
: {}),
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
1987
2036
|
function joinNoticeLines(...parts) {
|
|
1988
2037
|
const present = parts.filter((part) => !!part);
|
|
1989
2038
|
return present.length ? present.join("\n") : undefined;
|
|
1990
2039
|
}
|
|
1991
2040
|
export const MEMTREE_COMPACT_MIN_TOKENS = 20_000;
|
|
2041
|
+
/** How long a paused turn waits for the short checkout link before its notice (once). */
|
|
2042
|
+
export const PAYMENT_LINK_NOTICE_WAIT_MS = 1_000;
|
|
1992
2043
|
/** The reply to a ccc slash command, or undefined for an ordinary prompt. */
|
|
1993
2044
|
function sessionCommandReply(state, sessionId, prompt) {
|
|
1994
2045
|
const unpaid = state.unpaidDetail();
|
|
@@ -3554,7 +3605,9 @@ async function recoverToolRouteMiss(args) {
|
|
|
3554
3605
|
// a later turn. An unpaid key (402, possibly set by this very compress)
|
|
3555
3606
|
// or a shutting-down proxy gets no retry.
|
|
3556
3607
|
if (!state.shutdownSignal.aborted &&
|
|
3557
|
-
opts.memtree.paymentRequiredDetail === null
|
|
3608
|
+
opts.memtree.paymentRequiredDetail === null &&
|
|
3609
|
+
!opts.memtree.keyInvalid // a refused key refuses the index too
|
|
3610
|
+
) {
|
|
3558
3611
|
opts.memtree.indexInBackground(hash, msgsForMemtree, modelContextLimit, requestSessionId(req), args.clientMeta, transcriptTimesFor(opts, requestSessionId(req), agentAttributionId(req)));
|
|
3559
3612
|
}
|
|
3560
3613
|
return forwardOriginal();
|
|
@@ -3962,9 +4015,12 @@ async function wholeRequestRefused(sizing, candidate, limits) {
|
|
|
3962
4015
|
}
|
|
3963
4016
|
/** Write the bounded fallback refusal (route-fallback.ts); always false. */
|
|
3964
4017
|
function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
3965
|
-
|
|
3966
|
-
const
|
|
3967
|
-
|
|
4018
|
+
// A refused key or a paused one: a retry cannot fix either, so it is final at once.
|
|
4019
|
+
const keyInvalid = args.state.keyInvalid();
|
|
4020
|
+
const unpaid = keyInvalid ? null : args.state.pausedDetail();
|
|
4021
|
+
const failure = keyInvalid ? pausedRefusal(keyInvalidRefusalMessage(args.state.keyPageUrl))
|
|
4022
|
+
: unpaid !== null ? pausedRefusal(pausedRefusalMessage(unpaid))
|
|
4023
|
+
: args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
|
|
3968
4024
|
args.rec.forwardedBytes = 0;
|
|
3969
4025
|
args.rec.approxInputTokens = 0;
|
|
3970
4026
|
args.state.diagnostics?.record("refusal", {
|
|
@@ -3977,8 +4033,9 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
3977
4033
|
inputTokens, outputTokens: args.body.max_tokens ?? 0,
|
|
3978
4034
|
outputReservation: bodyOutputReservation(args.body), indexBehind: args.rec.indexBehind === true,
|
|
3979
4035
|
budgetTokens, modelContextLimit: args.modelContextLimit, ...(unpaid !== null ? { paused: true } : {}),
|
|
4036
|
+
...(keyInvalid ? { keyInvalid: true } : {}),
|
|
3980
4037
|
};
|
|
3981
|
-
if (unpaid !== null)
|
|
4038
|
+
if (unpaid !== null || keyInvalid)
|
|
3982
4039
|
cancelPausedNotice(args.state, args.rec);
|
|
3983
4040
|
if (args.res.headersSent || args.res.destroyed)
|
|
3984
4041
|
return false;
|
|
@@ -3987,6 +4044,9 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
3987
4044
|
if (unpaid !== null) {
|
|
3988
4045
|
notePausedRefusal(args.state, args.rec, args.routeKey, requestSessionId(args.req));
|
|
3989
4046
|
}
|
|
4047
|
+
// The refusal named the key: it counts as the key notice on the user's screen.
|
|
4048
|
+
if (keyInvalid && args.rec.routeLane === "main")
|
|
4049
|
+
args.state.keyNotice.markShown();
|
|
3990
4050
|
return false;
|
|
3991
4051
|
}
|
|
3992
4052
|
/**
|
|
@@ -4025,6 +4085,8 @@ function cancelPausedNotice(state, rec) {
|
|
|
4025
4085
|
* already queued the paused notice carries the line itself.
|
|
4026
4086
|
*/
|
|
4027
4087
|
function warnPausedNearWindow(state, rec, sessionId) {
|
|
4088
|
+
if (state.keyInvalid())
|
|
4089
|
+
return; // a refused key, not a pause: the key notice says why
|
|
4028
4090
|
const detail = state.unpaidDetail();
|
|
4029
4091
|
// Paid again: a later pause (next month's limit) warns afresh.
|
|
4030
4092
|
if (detail === null)
|