pi-tuck 1.0.1 → 1.0.3
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/CHANGELOG.md +43 -0
- package/README.md +8 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -4
- package/dist/src/budget.d.ts +17 -4
- package/dist/src/budget.js +33 -10
- package/dist/src/build-id.d.ts +1 -1
- package/dist/src/build-id.js +1 -1
- package/dist/src/compaction.d.ts +4 -0
- package/dist/src/compaction.js +8 -5
- package/dist/src/diagnostics.d.ts +2 -1
- package/dist/src/omissions.d.ts +1 -1
- package/dist/src/omissions.js +8 -8
- package/dist/src/tuck/core/brief.d.ts +13 -43
- package/dist/src/tuck/core/brief.js +39 -255
- package/dist/src/tuck/core/format.d.ts +2 -6
- package/dist/src/tuck/core/format.js +4 -10
- package/dist/src/tuck/core/normalize.js +20 -43
- package/dist/src/tuck/core/shrink.d.ts +33 -0
- package/dist/src/tuck/core/shrink.js +221 -0
- package/dist/src/tuck/core/summarize.d.ts +15 -7
- package/dist/src/tuck/core/summarize.js +10 -23
- package/dist/src/tuck/core/transcript.d.ts +24 -13
- package/dist/src/tuck/core/transcript.js +243 -83
- package/dist/src/tuck/types.d.ts +4 -14
- package/dist/src/tuck.d.ts +8 -10
- package/dist/src/tuck.js +23 -25
- package/index.ts +8 -5
- package/package.json +1 -1
- package/src/budget.ts +29 -9
- package/src/build-id.ts +1 -1
- package/src/compaction.ts +11 -5
- package/src/diagnostics.ts +1 -1
- package/src/omissions.ts +8 -8
- package/src/tuck/core/brief.ts +46 -265
- package/src/tuck/core/format.ts +4 -11
- package/src/tuck/core/normalize.ts +20 -43
- package/src/tuck/core/shrink.ts +218 -0
- package/src/tuck/core/summarize.ts +20 -25
- package/src/tuck/core/transcript.ts +234 -82
- package/src/tuck/types.ts +7 -19
- package/src/tuck.ts +30 -32
- package/dist/src/tuck/core/brief-meter.d.ts +0 -42
- package/dist/src/tuck/core/brief-meter.js +0 -273
- package/dist/src/tuck/core/rank.d.ts +0 -42
- package/dist/src/tuck/core/rank.js +0 -292
- package/src/tuck/core/brief-meter.ts +0 -235
- package/src/tuck/core/rank.ts +0 -331
package/src/tuck/core/rank.ts
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import type { NormalizedBlock, FileOps } from "../types";
|
|
2
|
-
import { extractPath } from "./tool-args";
|
|
3
|
-
import { compileBrief, planBrief, heredocCloseIndex, isSegmentClosingAssistant, type ExcerptCache } from "./brief";
|
|
4
|
-
import { createBriefMeter, type BriefSize } from "./brief-meter";
|
|
5
|
-
import { TRANSCRIPT_HEADER, transcriptUnits, MAX_RECEIPT_CHARACTERS } from "./transcript";
|
|
6
|
-
|
|
7
|
-
export interface BriefRankingOptions {
|
|
8
|
-
/** Maximum normalized blocks used to build the brief transcript. */
|
|
9
|
-
maxBlocks?: number;
|
|
10
|
-
/** Optional complete serialized historical-view allowance. */
|
|
11
|
-
maxBriefUnits?: number;
|
|
12
|
-
/** Full sources rendered separately by the caller; retain their structural relationships. */
|
|
13
|
-
mandatorySourceIndices?: ReadonlySet<number>;
|
|
14
|
-
/** Always keep this many latest blocks to preserve local continuity. */
|
|
15
|
-
preserveRecentBlocks?: number;
|
|
16
|
-
/** Hook-provided file activity, used as structural signal instead of prose guessing. */
|
|
17
|
-
fileOps?: FileOps;
|
|
18
|
-
/**
|
|
19
|
-
* Optional size budget (in characters of rendered brief) for the selected
|
|
20
|
-
* blocks. When set, this is the PRIMARY limit: blocks are added by score
|
|
21
|
-
* until the budget is reached, and lower-value blocks are skipped rather
|
|
22
|
-
* than truncating the tail. maxBlocks still applies as a safety upper bound.
|
|
23
|
-
* Callers derive this from a token budget via charsPerToken.
|
|
24
|
-
* When maxBriefCharsCeiling + briefCharsPerBlock are also set, this acts as
|
|
25
|
-
* the FLOOR of a size-relative budget (see below).
|
|
26
|
-
*/
|
|
27
|
-
maxBriefChars?: number;
|
|
28
|
-
/**
|
|
29
|
-
* Optional upper bound for a size-relative char budget. When set together
|
|
30
|
-
* with maxBriefChars (floor) and briefCharsPerBlock (slope), the effective
|
|
31
|
-
* budget becomes clamp(briefCharsPerBlock * blockCount, maxBriefChars,
|
|
32
|
-
* maxBriefCharsCeiling): larger transcripts (which carry more high-value
|
|
33
|
-
* long-tail -- edits, commands, tests) get more brief budget, while small
|
|
34
|
-
* sessions stay at the floor and this hard ceiling prevents unbounded growth.
|
|
35
|
-
*/
|
|
36
|
-
maxBriefCharsCeiling?: number;
|
|
37
|
-
/** Per-block slope (chars) for the size-relative budget. Requires the ceiling. */
|
|
38
|
-
briefCharsPerBlock?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface RankedBlock {
|
|
42
|
-
block: NormalizedBlock;
|
|
43
|
-
index: number;
|
|
44
|
-
score: number;
|
|
45
|
-
reasons: string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const DEFAULT_MAX_BLOCKS = 80;
|
|
49
|
-
const DEFAULT_RECENT_BLOCKS = 16;
|
|
50
|
-
|
|
51
|
-
const EDIT_TOOL_RE = /^(edit|write|multiedit|quick_edit|target_edit|apply_patch)$/i;
|
|
52
|
-
const READ_TOOL_RE = /^(read|glob|grep|ls|find|semantic_query|semantic_grep|semantic_show)$/i;
|
|
53
|
-
const TEST_COMMAND_RE = /\b(?:bun|npm|pnpm|yarn|node|pytest|cargo|go|mvn|gradle)\b[^\n]*(?:test|spec|check|lint|build|tsc)/i;
|
|
54
|
-
// Durable workflow facts: which PR/issue was acted on, and git state changes.
|
|
55
|
-
// Structural (command shape), not prose — same spirit as TEST_COMMAND_RE.
|
|
56
|
-
const WORKFLOW_COMMAND_RE =
|
|
57
|
-
/(?:^|\s)(?:gh\s+(?:pr|issue)\s+[a-z-]+|git\s+(?:commit|push|merge|rebase|revert|cherry-pick|tag|reset|checkout|branch)\b)/i;
|
|
58
|
-
const MIN_SEGMENT_CLOSING_ASSISTANT_CHARS = 120;
|
|
59
|
-
|
|
60
|
-
// A bash block whose every meaningful line is pure scaffolding (set -e, cd,
|
|
61
|
-
// export, ls, echo, sleep, pwd, comments, heredoc bodies) carries no durable
|
|
62
|
-
// fact. Such blocks get a score penalty so the size-relative budget never
|
|
63
|
-
// pulls them in ahead of real edits/commands when spare chars appear.
|
|
64
|
-
const TRIVIAL_BASH_LINE_RE =
|
|
65
|
-
/^(?:set\s+[-+]|cd(?:\s+\S+)?$|export\s+\w+=|(?:source|\.)\s+\S+|pwd$|true$|:$|#|ls(?:\s|$)|echo\b|clear$|sleep\b)/;
|
|
66
|
-
const TRIVIAL_BASH_PENALTY = 16;
|
|
67
|
-
|
|
68
|
-
const isTrivialOnlyBash = (raw: string): boolean => {
|
|
69
|
-
const lines = raw.split("\n");
|
|
70
|
-
const kept: string[] = [];
|
|
71
|
-
for (let i = 0; i < lines.length; i++) {
|
|
72
|
-
kept.push(lines[i]);
|
|
73
|
-
// Skip heredoc bodies (they are content, not scaffolding) using the same
|
|
74
|
-
// hardened detection as brief.ts: only skip when the terminator exists
|
|
75
|
-
// downstream, so a stray `<<` never swallows a later real command.
|
|
76
|
-
const close = heredocCloseIndex(lines, i);
|
|
77
|
-
if (close !== -1) i = close;
|
|
78
|
-
}
|
|
79
|
-
const meaningful = kept.map((l) => l.trim()).filter(Boolean).filter((l) => !TRIVIAL_BASH_LINE_RE.test(l));
|
|
80
|
-
return meaningful.length === 0;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const asPathSet = (paths?: string[]): Set<string> => new Set((paths ?? []).filter(Boolean));
|
|
84
|
-
|
|
85
|
-
const bashCommandFromBlock = (block: NormalizedBlock): string | undefined => {
|
|
86
|
-
if (block.kind === "bash") return block.command;
|
|
87
|
-
if (block.kind === "tool_call" && /^bash$/i.test(block.name) && typeof block.args.command === "string") {
|
|
88
|
-
return block.args.command;
|
|
89
|
-
}
|
|
90
|
-
return undefined;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const pathFromBlock = (block: NormalizedBlock): string | undefined => {
|
|
94
|
-
if (block.kind === "tool_call") return extractPath(block.args) ?? undefined;
|
|
95
|
-
if (block.kind === "bash") {
|
|
96
|
-
const match = block.command.match(/(?:^|\s)([\w./-]+\.[\w-]+)(?:\s|$)/);
|
|
97
|
-
return match?.[1];
|
|
98
|
-
}
|
|
99
|
-
return undefined;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const add = (ranked: RankedBlock, points: number, reason: string) => {
|
|
103
|
-
ranked.score += points;
|
|
104
|
-
ranked.reasons.push(reason);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const scoreBlock = (
|
|
108
|
-
block: NormalizedBlock,
|
|
109
|
-
index: number,
|
|
110
|
-
total: number,
|
|
111
|
-
modifiedFiles: Set<string>,
|
|
112
|
-
readFiles: Set<string>,
|
|
113
|
-
): RankedBlock => {
|
|
114
|
-
const ranked: RankedBlock = { block, index, score: 0, reasons: [] };
|
|
115
|
-
const recency = total <= 1 ? 0 : Math.round((index / (total - 1)) * 12);
|
|
116
|
-
add(ranked, recency, "recency");
|
|
117
|
-
|
|
118
|
-
if (block.kind === "user") add(ranked, 18, "user-turn");
|
|
119
|
-
if (block.kind === "assistant") add(ranked, 10, "assistant-context");
|
|
120
|
-
if (block.kind === "notice") add(ranked, 10, "framework-notice");
|
|
121
|
-
if (block.kind === "tool_result") add(ranked, 1, "tool-result-low-value");
|
|
122
|
-
|
|
123
|
-
if (block.kind === "tool_call") {
|
|
124
|
-
const command = bashCommandFromBlock(block);
|
|
125
|
-
if (EDIT_TOOL_RE.test(block.name)) add(ranked, 34, "edit-tool");
|
|
126
|
-
else if (command && TEST_COMMAND_RE.test(command)) add(ranked, 26, "test-command");
|
|
127
|
-
else if (READ_TOOL_RE.test(block.name)) add(ranked, 6, "read-tool");
|
|
128
|
-
else add(ranked, 12, "tool-call");
|
|
129
|
-
if (command && WORKFLOW_COMMAND_RE.test(command)) add(ranked, 14, "workflow-command");
|
|
130
|
-
if (command && isTrivialOnlyBash(command)) add(ranked, -TRIVIAL_BASH_PENALTY, "trivial-bash");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (block.kind === "bash") {
|
|
134
|
-
add(ranked, 8, "bash");
|
|
135
|
-
if (block.exitCode != null && block.exitCode !== 0) add(ranked, 24, "nonzero-exit");
|
|
136
|
-
if (TEST_COMMAND_RE.test(block.command)) add(ranked, 22, "test-command");
|
|
137
|
-
if (WORKFLOW_COMMAND_RE.test(block.command)) add(ranked, 14, "workflow-command");
|
|
138
|
-
// Penalize scaffolding-only commands, but not ones that failed (a failed
|
|
139
|
-
// `ls`/`cd` still records a real state fact via nonzero-exit).
|
|
140
|
-
if (isTrivialOnlyBash(block.command) && !(block.exitCode != null && block.exitCode !== 0)) {
|
|
141
|
-
add(ranked, -TRIVIAL_BASH_PENALTY, "trivial-bash");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const path = pathFromBlock(block);
|
|
146
|
-
if (path) {
|
|
147
|
-
if (modifiedFiles.has(path)) add(ranked, 18, "hook-modified-file");
|
|
148
|
-
if (readFiles.has(path)) add(ranked, 6, "hook-read-file");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (block.kind === "tool_result" && block.text.length > 1000) add(ranked, -8, "long-tool-result");
|
|
152
|
-
return ranked;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const boostAdjacency = (ranked: RankedBlock[]) => {
|
|
156
|
-
const important = ranked
|
|
157
|
-
.filter((r) => r.score >= 34 || r.reasons.includes("edit-tool") || r.reasons.includes("test-command") || r.reasons.includes("nonzero-exit"))
|
|
158
|
-
.map((r) => r.index);
|
|
159
|
-
|
|
160
|
-
for (const idx of important) {
|
|
161
|
-
for (let i = idx - 1; i >= Math.max(0, idx - 8); i--) {
|
|
162
|
-
if (ranked[i].block.kind === "user") {
|
|
163
|
-
add(ranked[i], 10, "near-important-event");
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
for (let i = idx - 1; i >= Math.max(0, idx - 4); i--) {
|
|
168
|
-
if (ranked[i].block.kind === "assistant") {
|
|
169
|
-
add(ranked[i], 7, "near-important-event");
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
for (let i = idx + 1; i <= Math.min(ranked.length - 1, idx + 4); i++) {
|
|
174
|
-
if (ranked[i].block.kind === "assistant" || ranked[i].block.kind === "bash") {
|
|
175
|
-
add(ranked[i], 5, "after-important-event");
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const boostSegmentClosingAssistants = (ranked: RankedBlock[], blocks: NormalizedBlock[]) => {
|
|
183
|
-
for (const current of ranked) {
|
|
184
|
-
if (current.block.kind !== "assistant") continue;
|
|
185
|
-
if (current.block.text.trim().length < MIN_SEGMENT_CLOSING_ASSISTANT_CHARS) continue;
|
|
186
|
-
if (isSegmentClosingAssistant(blocks, current.index)) add(current, 14, "segment-closing-assistant");
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
export const rankBriefBlocks = (blocks: NormalizedBlock[], options: BriefRankingOptions = {}): RankedBlock[] => {
|
|
191
|
-
const modifiedFiles = asPathSet(options.fileOps?.modifiedFiles);
|
|
192
|
-
const readFiles = asPathSet(options.fileOps?.readFiles);
|
|
193
|
-
const ranked = blocks.map((block, index) => scoreBlock(block, index, blocks.length, modifiedFiles, readFiles));
|
|
194
|
-
boostAdjacency(ranked);
|
|
195
|
-
boostSegmentClosingAssistants(ranked, blocks);
|
|
196
|
-
return ranked;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
export const selectRankedBriefBlocks = (
|
|
200
|
-
blocks: NormalizedBlock[],
|
|
201
|
-
options: BriefRankingOptions = {},
|
|
202
|
-
): NormalizedBlock[] => {
|
|
203
|
-
// A rendered allowance owns capacity. An independent count must not discard
|
|
204
|
-
// fitting original dialogue; all blocks were already normalized in memory.
|
|
205
|
-
const maxBlocks = options.maxBlocks ?? (options.maxBriefChars != null ? blocks.length : DEFAULT_MAX_BLOCKS);
|
|
206
|
-
// Size-relative budget: when a ceiling + slope are provided, the effective
|
|
207
|
-
// char budget scales with transcript length (block count) between the floor
|
|
208
|
-
// (maxBriefChars) and the ceiling. Larger transcripts carry more high-value
|
|
209
|
-
// long-tail, so they earn more brief budget; small sessions stay at the floor.
|
|
210
|
-
const maxBriefChars =
|
|
211
|
-
options.maxBriefChars != null && options.maxBriefCharsCeiling != null && options.briefCharsPerBlock != null
|
|
212
|
-
? Math.round(
|
|
213
|
-
Math.min(
|
|
214
|
-
options.maxBriefCharsCeiling,
|
|
215
|
-
Math.max(options.maxBriefChars, options.briefCharsPerBlock * blocks.length),
|
|
216
|
-
),
|
|
217
|
-
)
|
|
218
|
-
: options.maxBriefChars;
|
|
219
|
-
// Fast path: nothing to trim by count and no char budget to enforce.
|
|
220
|
-
if (blocks.length <= maxBlocks && maxBriefChars == null && options.maxBriefUnits == null) return blocks;
|
|
221
|
-
|
|
222
|
-
const preserveRecentBlocks = Math.min(options.preserveRecentBlocks ?? DEFAULT_RECENT_BLOCKS, maxBlocks);
|
|
223
|
-
const ranked = rankBriefBlocks(blocks, options);
|
|
224
|
-
const selected = new Set<number>();
|
|
225
|
-
|
|
226
|
-
// Charge the actual candidate serialization. Adjacent-block truncation and
|
|
227
|
-
// tool omission notices can change its size; isolated-block estimates cannot certify it.
|
|
228
|
-
// The meter keeps that size current as blocks join; the final render below remains the authority.
|
|
229
|
-
const excerpts: ExcerptCache = new WeakMap();
|
|
230
|
-
const full = new Set<NormalizedBlock>();
|
|
231
|
-
const meter = createBriefMeter(blocks, options.mandatorySourceIndices, excerpts);
|
|
232
|
-
const within = (size: BriefSize) => (maxBriefChars == null || size.chars <= maxBriefChars) &&
|
|
233
|
-
(options.maxBriefUnits == null || size.units <= options.maxBriefUnits);
|
|
234
|
-
const admit = (indices: number[], preferFull = false): boolean => {
|
|
235
|
-
const added = indices.filter(i => !selected.has(i));
|
|
236
|
-
if (!added.length && !preferFull) return true;
|
|
237
|
-
if (selected.size + added.length > maxBlocks) return false;
|
|
238
|
-
const promoted = preferFull ? indices.filter(i => ["user", "assistant", "bash"].includes(blocks[i].kind) && !full.has(blocks[i])) : [];
|
|
239
|
-
// Notices are best effort: never overflow or displace otherwise fitting text.
|
|
240
|
-
const fits = () => { const { withNotices, withoutNotices } = meter.size(added); return within(withNotices) || within(withoutNotices); };
|
|
241
|
-
for (const i of added) meter.select(i);
|
|
242
|
-
for (const i of promoted) meter.promote(i);
|
|
243
|
-
if (!fits()) {
|
|
244
|
-
for (const i of promoted.splice(0)) meter.demote(i);
|
|
245
|
-
// A whole shell proposal that does not fit is withdrawn, not retried as an excerpt.
|
|
246
|
-
if ((preferFull && indices.some(i => blocks[i].kind === "bash")) || !fits()) { for (const i of added) meter.unselect(i); return false; }
|
|
247
|
-
}
|
|
248
|
-
for (const i of added) selected.add(i);
|
|
249
|
-
meter.commit(added);
|
|
250
|
-
for (const i of promoted) full.add(blocks[i]);
|
|
251
|
-
return true;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
// Keep the latest blocks to preserve local continuity, iterating NEWEST first
|
|
255
|
-
// so the most recent context is guaranteed. When a char budget is active these
|
|
256
|
-
// are charged against it too and over-budget blocks are skipped -- otherwise a
|
|
257
|
-
// run of large recent blocks could blow past maxBriefChars (the old bug on very
|
|
258
|
-
// long transcripts, where preserve-recent alone exceeded the budget).
|
|
259
|
-
for (let i = blocks.length - 1; i >= Math.max(0, blocks.length - preserveRecentBlocks); i--) {
|
|
260
|
-
// Historical action overviews compete after the conversational base. Fresh
|
|
261
|
-
// native rounds are protected by the adapter, outside this old-prefix view.
|
|
262
|
-
if (["tool_result", "tool_call", "bash"].includes(blocks[i].kind)) continue;
|
|
263
|
-
if (selected.has(i)) continue;
|
|
264
|
-
if (!admit([i])) continue;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Structural exchanges are derived from the original chronology. A user
|
|
268
|
-
// carries prose from the preceding assistant message, including prose before
|
|
269
|
-
// that message's trailing calls. Only explicit source identity crosses calls.
|
|
270
|
-
const groups = ranked.filter(r => r.block.kind === "user").map(item => {
|
|
271
|
-
let preceding = item.index - 1;
|
|
272
|
-
while (preceding >= 0 && blocks[preceding].kind === "tool_result") preceding--;
|
|
273
|
-
const previous = blocks[preceding];
|
|
274
|
-
const prose: number[] = [];
|
|
275
|
-
if (previous?.kind === "assistant" || previous?.kind === "tool_call") {
|
|
276
|
-
const source = previous.sourceIndex;
|
|
277
|
-
for (let i = preceding; i >= 0; i--) {
|
|
278
|
-
if (source === undefined ? i !== preceding : blocks[i].sourceIndex !== source) break;
|
|
279
|
-
if (blocks[i].kind === "assistant") prose.unshift(i);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return [...prose, item.index];
|
|
283
|
-
}).reverse();
|
|
284
|
-
// Admit the conversational base before spending room on any full proposal.
|
|
285
|
-
// Otherwise a newly fitting recent assistant can crowd out an older user.
|
|
286
|
-
for (const group of groups) {
|
|
287
|
-
if (!admit(group)) {
|
|
288
|
-
// Bounded atomicity: an oversized pair must not exclude a useful user turn.
|
|
289
|
-
admit([group.at(-1)!]);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
const ordered = [...ranked].sort((a, b) => b.score - a.score || b.index - a.index);
|
|
293
|
-
for (const item of ordered) {
|
|
294
|
-
if (selected.size >= maxBlocks) break;
|
|
295
|
-
if (selected.has(item.index)) continue;
|
|
296
|
-
if (item.block.kind === "tool_result") continue;
|
|
297
|
-
if (!admit([item.index])) continue;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Complete the existing coverage pass before expanding already represented
|
|
301
|
-
// user wording. Whole proposals/choices are still eligible in spare room.
|
|
302
|
-
for (const i of [...selected].sort((a, b) => b - a))
|
|
303
|
-
if (blocks[i].kind === "user") admit([i], true);
|
|
304
|
-
// Expand selected assistant prose only into spare capacity. A large recent
|
|
305
|
-
// message must not consume the budget before user exchanges are considered.
|
|
306
|
-
for (const i of [...selected].sort((a, b) => b - a))
|
|
307
|
-
if (blocks[i].kind === "assistant") admit([i], true);
|
|
308
|
-
|
|
309
|
-
// Cheap exact observations use only spare allowance after dialogue. Size
|
|
310
|
-
// and recency are signals, not proof of usefulness, freshness or success.
|
|
311
|
-
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
312
|
-
const block = blocks[i];
|
|
313
|
-
if (block.kind === "tool_result" && block.originalReceipt) admit([i]);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Enrich already retained shell events only after the unchanged base. Whole
|
|
317
|
-
// stored output may be truncated; it is historical evidence, not verification.
|
|
318
|
-
for (const i of [...selected].sort((a, b) => b - a)) {
|
|
319
|
-
const block = blocks[i];
|
|
320
|
-
if (block.kind === "bash" && block.sourceIndex !== undefined && block.output.length <= MAX_RECEIPT_CHARACTERS)
|
|
321
|
-
admit([i], true);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const result = [...selected].sort((a, b) => a - b).map((i) => blocks[i]);
|
|
325
|
-
planBrief(result, full, blocks, options.mandatorySourceIndices);
|
|
326
|
-
const rendered = TRANSCRIPT_HEADER + "\n" + compileBrief(result, excerpts);
|
|
327
|
-
if ((maxBriefChars != null && rendered.length > maxBriefChars) ||
|
|
328
|
-
(options.maxBriefUnits != null && transcriptUnits(rendered) > options.maxBriefUnits))
|
|
329
|
-
planBrief(result, full, blocks, options.mandatorySourceIndices, false);
|
|
330
|
-
return result;
|
|
331
|
-
};
|