@cerebriline/core 4.100.264 → 4.100.266

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.
@@ -261,6 +261,13 @@ export interface CompactionSummaryMetadata {
261
261
  * exists nowhere else once the messages are gone.
262
262
  */
263
263
  userRequests?: string[];
264
+ /**
265
+ * The files a call has put in front of the model, over every generation
266
+ * so far. A summary line about a file outside it states what nobody read
267
+ * (`unread-files.ts`); carried because the next fold's calls are not the
268
+ * only ones that opened files.
269
+ */
270
+ seenFiles?: string[];
264
271
  }
265
272
  export type EstimateMessageTokens = (message: MessageWithMetadata) => number;
266
273
  /**
@@ -317,7 +324,9 @@ export declare function formatToolInput(input: Record<string, unknown>): string;
317
324
  * says how much of it was left out.
318
325
  */
319
326
  export declare function formatToolInputForTranscript(input: Record<string, unknown>): string;
320
- export declare function serializeMessage(message: MessageWithMetadata, format?: ToolResultFormat): string;
327
+ export declare function serializeMessage(message: MessageWithMetadata, format?: ToolResultFormat,
328
+ /** Each call's number in the tool ledger's key, by call id. */
329
+ callNumbers?: ReadonlyMap<string, number>): string;
321
330
  /**
322
331
  * Render the discarded reasoning against what it produced.
323
332
  *
@@ -332,7 +341,7 @@ export declare function serializeMessage(message: MessageWithMetadata, format?:
332
341
  * was built against the results are most of the bytes.
333
342
  */
334
343
  export declare function serializeReasoningWithOutcomes(messages: MessageWithMetadata[], format?: ToolResultFormat): string;
335
- export declare function serializeConversation(messages: MessageWithMetadata[], format?: ToolResultFormat): string;
344
+ export declare function serializeConversation(messages: MessageWithMetadata[], format?: ToolResultFormat, callNumbers?: ReadonlyMap<string, number>): string;
336
345
  export declare function createTokenEstimator(): EstimateMessageTokens;
337
346
  export declare function isCompactionSummaryMessage(message: MessageWithMetadata): boolean;
338
347
  /**
@@ -730,6 +739,8 @@ export declare function buildSummaryMessage(options: {
730
739
  userRequests?: string[];
731
740
  /** How much room the quoted requests may take, all together. */
732
741
  userRequestBudgetChars?: number;
742
+ /** The files calls have opened, for the next generation. */
743
+ seenFiles?: string[];
733
744
  }): MessageWithMetadata;
734
745
  /**
735
746
  * Recover the token calibration from a transcript that was written by an
@@ -202,6 +202,8 @@ export declare function runCouncilReview(input: {
202
202
  messages: readonly MessageWithMetadata[];
203
203
  /** How the conversation's tool results read; the critics see the same. */
204
204
  toolResultFormat?: ToolResultFormat;
205
+ /** Each call's number in the ledger key, so the critics read it as the writer did. */
206
+ callNumbers?: ReadonlyMap<string, number>;
205
207
  /**
206
208
  * One model call. Throwing is allowed and is handled as a decline.
207
209
  *
@@ -105,6 +105,31 @@ export interface ToolLedgerOptions {
105
105
  * track, which is most of them — an untracked file simply does not appear.
106
106
  */
107
107
  export type RevisionSpanLookup = (path: string) => string | undefined;
108
+ export interface NormalisedCall {
109
+ id: string;
110
+ name: string;
111
+ input: unknown;
112
+ }
113
+ export interface NormalisedResult {
114
+ id: string;
115
+ output: unknown;
116
+ isError?: boolean;
117
+ serialized?: string;
118
+ }
119
+ /**
120
+ * One call, whichever of the two block shapes it arrived in.
121
+ *
122
+ * This repository carries both, and which one a caller holds is not something
123
+ * the caller can be expected to know: the agent runtime works in the AI SDK's
124
+ * `tool-call` / `toolCallId` / `input`, and the compaction pipeline works in
125
+ * the Anthropic-style `tool_use` / `id` / `input` with results keyed by
126
+ * `tool_use_id`. A ledger that reads only one of them is not half-working; it
127
+ * silently reports that no tools were called, which is the most damaging thing
128
+ * it could say. So it reads both and says so here.
129
+ */
130
+ export declare function normaliseCall(part: unknown): NormalisedCall | undefined;
131
+ /** One result, in either shape. See {@link normaliseCall}. */
132
+ export declare function normaliseResult(part: unknown): NormalisedResult | undefined;
108
133
  /**
109
134
  * Pair the calls in a stretch of messages with their results.
110
135
  *
@@ -114,6 +139,17 @@ export type RevisionSpanLookup = (path: string) => string | undefined;
114
139
  * something it never returned.
115
140
  */
116
141
  export declare function buildToolLedger(messages: readonly ToolLedgerMessage[], options?: ToolLedgerOptions): ToolLedgerEntry[];
142
+ /**
143
+ * The number each call of `messages` has in the key that
144
+ * `mergeToolLedger(previous, buildToolLedger(messages))` makes, by call id.
145
+ *
146
+ * For the writer's transcript: with the calls numbered where they happen,
147
+ * the writer cites `#7` by reading it, not by counting tool calls down the
148
+ * conversation, and counting was where its citations went wrong
149
+ * (claude-hooks 0e7310e, compaction-v7). A run of repeats is one entry, so
150
+ * all its calls carry the one number.
151
+ */
152
+ export declare function numberToolCalls(previous: readonly ToolLedgerEntry[], messages: readonly ToolLedgerMessage[], options?: ToolLedgerOptions): Map<string, number>;
117
153
  /**
118
154
  * Concatenate a carried ledger with this compaction's own, as one run.
119
155
  *
@@ -167,6 +203,31 @@ export declare function renderToolLedgerEntry(entry: ToolLedgerEntry): string;
167
203
  * deliberately cut.
168
204
  */
169
205
  export declare function renderToolLedgerKey(entries: readonly ToolLedgerEntry[], maxChars?: number): string;
206
+ /**
207
+ * The call numbers `text` cites past a key of `calls` entries: calls that
208
+ * did not happen in the span, so the sentence that cites them is invented.
209
+ *
210
+ * Measured by claude-hooks on compaction-v7-cite with qwen3.6 27B: a writer
211
+ * whose span ended mid-task carried on in the present tense and cited up to
212
+ * #174 against a 55-call record -- a forecast written as a record.
213
+ */
214
+ export declare function citedPastKey(text: string, calls: number): number[];
215
+ /**
216
+ * `text` without its narration of calls past the key.
217
+ *
218
+ * A citation follows the clause that narrates its call, so a citation naming
219
+ * only calls past the key goes with the clause before it: back to the end of
220
+ * the previous citation, never across a paragraph. One that also names real
221
+ * calls keeps its clause; the splice drops just its bad numbers.
222
+ *
223
+ * The splice alone dropped the number and kept the sentence, which is the
224
+ * half of the failure a reader can see -- the invented step is still there,
225
+ * now uncited.
226
+ */
227
+ export declare function dropPastKey(text: string, calls: number): {
228
+ text: string;
229
+ dropped: number;
230
+ };
170
231
  export interface LedgerSpliceResult {
171
232
  text: string;
172
233
  /** Entries the replay placed itself, in the order it placed them. */
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cut the summary lines that state facts about files no call opened.
3
+ *
4
+ * Measured by claude-hooks on compaction-v7-g2 / -cite (qwen3.6 27B,
5
+ * 2026-10-24): on every first fold, in both flows, the writer or a reviewer
6
+ * completed the module table the task asked for -- fields for 66 to 118
7
+ * modules not yet read, one line each, uncited, and 1 of 98 right. The
8
+ * summary is the session's only record of the fold, so a session that
9
+ * trusts it works from invented facts.
10
+ *
11
+ * Deterministic: the record knows which files each call put in front of the
12
+ * model, so a line whose every file is one no call opened says nothing that
13
+ * was read, whatever it claims.
14
+ *
15
+ * A file counts as seen when a call that shows content named it, in its
16
+ * arguments (a `read_files` path, a command that prints it) or in its result
17
+ * (a search match). A listing proves a file exists, not what it holds, so it
18
+ * never makes one seen. A file the user's own requests name is never cut:
19
+ * an instruction that names it is not an invention. The names the cut lines
20
+ * mentioned are kept, as one note, so a legitimate list of what is still to
21
+ * read survives as names.
22
+ *
23
+ * Ported from claude-hooks consultants/engine/unread_files.py (209185b).
24
+ */
25
+ import { type ToolLedgerMessage } from "./tool-ledger";
26
+ /** Tools whose results name files without showing what they contain. */
27
+ export declare const LISTING_TOOLS: Set<string>;
28
+ /** The most seen files carried to the next generation. */
29
+ export declare const MAX_SEEN_FILES = 5000;
30
+ export declare const UNREAD_NOTE_HEAD = "Not yet read";
31
+ /** The file paths `text` names, in order, once each. */
32
+ export declare function filesIn(text: string): string[];
33
+ /** Files a call put in front of the model, in its arguments or its result. */
34
+ export declare function seenFiles(messages: readonly ToolLedgerMessage[]): Set<string>;
35
+ /**
36
+ * `text` without the lines whose every named file is unseen, the names those
37
+ * lines mentioned, and how many lines went. The files `exempt` names (the
38
+ * user's requests) are never cut, and fenced code is left alone.
39
+ */
40
+ export declare function dropUnreadLines(text: string, seen: ReadonlySet<string>, exempt?: string): {
41
+ text: string;
42
+ names: string[];
43
+ cut: number;
44
+ };
45
+ /** What the cut leaves in the summary: the names, never the claims. */
46
+ export declare function unreadNote(names: readonly string[], cut: number): string;
47
+ /** The seen set for the summary's metadata, newest kept when over the bound. */
48
+ export declare function carrySeenFiles(previous: readonly string[], next: Iterable<string>): string[];
@@ -36,7 +36,7 @@ export declare const CHECK_FILE_TOOL_NAME = "check_file";
36
36
  * otherwise type are named here, in the description of the tool that replaces
37
37
  * them, which is where the model is standing when it is about to type one.
38
38
  */
39
- export declare const CHECK_FILE_TOOL_DESCRIPTION = "Check files for syntax errors and unbalanced brackets. **This is the syntax check.** Whichever word the question uses \u2014 parse error, syntax error, \"is it valid\", \"is it clean now\", \"what is still broken\" \u2014 this is the tool that answers it, for the files you name, in milliseconds, without running anything.\n\nAsk this instead of running `node --check`, `python -m py_compile`, `tsc --noEmit` or a bracket-counting script of your own. It parses the file without executing it, so it is safe on a page that would otherwise start a game or a server.\n\nWhen to call it:\n- After editing a file, to confirm the edit is valid before moving on.\n- Before reporting a task finished, on every file you changed. An edit you have not looked at is not a change you can report as done.\n- On a file you are about to change, to see what was already wrong with it.\n\nPass every file you want checked in one call.\n\nFor `.html` files each `<script>` block is parsed as JavaScript and the line numbers reported are the line numbers in the HTML file, not in the block.\n\nRead the bound carefully. This reports **syntax**, not meaning: a file that parses may still call a function that does not exist, and this will not say so. It is not a type checker, it does not run tests, and it does not know your project's lint rules \u2014 for those, use `run_commands`.\n\nOutput: JSON, in the shape a language server answers in. `files` holds one entry per path you named, each with `uri`, `checked` and `diagnostics`. There is no `success` field \u2014 diagnostics being listed is this tool working, not failing.\n\n`checked` says which check actually ran, and you have to read it before calling a file clean: `parsed` a real parser was satisfied, `delimiters` only the bracket scan reads this language so anything past brackets is unchecked, `none` nothing here reads this file type at all \u2014 empty `diagnostics` there means nothing was checked, not that the file is sound.\n\nEach diagnostic carries `range` (zero-based line and character), `severity` (1 = error) and `message`. `located: false` means the parser could not name a position, so the range is a placeholder rather than a claim about the top of the file. `complexity` appears on a file big enough to be worth a word about \u2014 a note to you, not a diagnostic.\n\nWhen a file's brackets do not match, a diagnostic names the line to edit and how many brackets that line is out by, one line per place the trouble starts \u2014 a file can be broken in several spots at once, so work through every line it lists before re-checking rather than re-checking after each one. Edit the lines it names; do not re-derive the balance by hand or with a script you write, which costs more thinking than you have and counts brackets inside strings, comments and regex literals that this scan skips. A parse error is always reported where the parser gave up, which is the closing bracket; the line named here is the one the error cannot name.";
39
+ export declare const CHECK_FILE_TOOL_DESCRIPTION = "Check files for syntax errors and unbalanced brackets. **This is the syntax check.** Whichever word the question uses \u2014 parse error, syntax error, \"is it valid\", \"is it clean now\", \"what is still broken\" \u2014 this is the tool that answers it, for the files you name, in milliseconds, without running anything.\n\nAsk this instead of running `node --check`, `python -m py_compile`, `tsc --noEmit` or a bracket-counting script of your own. It parses the file without executing it, so it is safe on a page that would otherwise start a game or a server.\n\nWhen to call it:\n- After editing a file, to confirm the edit is valid before moving on.\n- Before reporting a task finished, on every file you changed. An edit you have not looked at is not a change you can report as done.\n- On a file you are about to change, to see what was already wrong with it.\n\nPass every file you want checked in one call.\n\nFor `.html` files each `<script>` block is parsed as JavaScript and the line numbers reported are the line numbers in the HTML file, not in the block.\n\nRead the bound carefully. This reports **syntax**, not meaning: a file that parses may still call a function that does not exist, and this will not say so. It is not a type checker, it does not run tests, and it does not know your project's lint rules \u2014 for those, use `run_commands`.\n\nOutput: JSON, in the shape a language server answers in. `files` holds one entry per path you named, each with `uri`, `checked` and `diagnostics`. There is no `success` field \u2014 diagnostics being listed is this tool working, not failing.\n\n`checked` says which check actually ran, and you have to read it before calling a file clean: `parsed` a real parser was satisfied, `delimiters` only the bracket scan reads this language so anything past brackets is unchecked, `none` nothing here reads this file type at all \u2014 empty `diagnostics` there means nothing was checked, not that the file is sound.\n\nEach diagnostic carries `range` (zero-based line and character), `severity` (1 = error) and `message`. `located: false` means the parser could not name a position, so the range is a placeholder rather than a claim about the top of the file. `complexity` appears on a file big enough to be worth a word about \u2014 a note to you, not a diagnostic.\n\nWhen a file's brackets do not match, a diagnostic names the line to edit and how many brackets that line is out by, one line per place the trouble starts \u2014 a file can be broken in several spots at once, so work through every line it lists before re-checking rather than re-checking after each one. Edit the lines it names; do not re-derive the balance by hand or with a script you write, which costs more thinking than you have and counts brackets inside strings, comments and regex literals that this scan skips. A parse error is always reported where the parser gave up, which is the closing bracket; the line named here is the one the error cannot name. The scan reads the whole file from line 1, and names each bracket by its line, its column and the text just before it: find it by that text rather than by counting columns.";
40
40
  /**
41
41
  * The description when the host has a real checker to point at.
42
42
  *
@@ -89,6 +89,18 @@ export interface ReadReceipts {
89
89
  * get different advice.
90
90
  */
91
91
  retire(filePath: string): void;
92
+ /**
93
+ * Record that the session's own machinery rewrote the file: a
94
+ * `restore_file`, a transaction rollback, an expert's edits.
95
+ *
96
+ * Every line may have moved, so the spans retire, and the stamp goes so
97
+ * the rewrite is not later reported as something outside the session.
98
+ * That the model read the file stays known. {@link forget} erased it, and
99
+ * a text-matched edit sent straight after a restore was refused with "has
100
+ * not been read in this session", of a file read four times and edited
101
+ * twice (pandorum g5tmt, calls 24 and 88).
102
+ */
103
+ noteRewrite(filePath: string): void;
92
104
  /** Drop everything known about a file. */
93
105
  forget(filePath: string): void;
94
106
  /**
@@ -11,7 +11,7 @@
11
11
  * read from, a memory the user did not allow this workspace.
12
12
  */
13
13
  import { type AgentTool, type MemorySettings } from "@cerebriline/shared";
14
- import { type Memory, type MemoryEndpoints } from "../../services/retrieval/memory";
14
+ import { type Memory, type MemoryEndpoints, type MemoryItem } from "../../services/retrieval/memory";
15
15
  export declare const MEMORY_TOOL_NAMES: readonly ["remember", "recall", "forget"];
16
16
  export type MemoryToolName = (typeof MEMORY_TOOL_NAMES)[number];
17
17
  export interface MemoryToolsConfig extends MemoryEndpoints {
@@ -37,5 +37,9 @@ export declare function resolveMemoryAccess(memory: Memory, settings: MemorySett
37
37
  recall: string[];
38
38
  storeFellBack?: string;
39
39
  };
40
+ /** A note as the model reads it, with what became of its places in this workspace. */
41
+ export declare function describeNoteHere(memory: Memory, item: MemoryItem & {
42
+ relevance?: number;
43
+ }, cwd: string): string;
40
44
  /** Memory's tools, when Memory is on at session start. */
41
45
  export declare function createMemoryTools(options: CreateMemoryToolsOptions): AgentTool[];