@aragon-agent/cli 0.6.14 → 0.6.15
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 +14 -0
- package/README.md +250 -233
- package/dist/agent/controller.d.ts +27 -1
- package/dist/agent/controller.js +182 -45
- package/dist/agent/plan-prompt.d.ts +1 -1
- package/dist/agent/plan-prompt.js +4 -3
- package/dist/agent/project-guidance-prompt.d.ts +4 -0
- package/dist/agent/project-guidance-prompt.js +104 -0
- package/dist/agent/reducer.d.ts +2 -0
- package/dist/agent/reducer.js +4 -0
- package/dist/agent/system-prompt.js +4 -0
- package/dist/commands/builtins.js +2 -2
- package/dist/compaction/child.d.ts +9 -1
- package/dist/compaction/child.js +39 -13
- package/dist/compaction/command.js +20 -10
- package/dist/compaction/compactor.d.ts +33 -204
- package/dist/compaction/compactor.js +311 -726
- package/dist/compaction/digest.d.ts +15 -51
- package/dist/compaction/digest.js +113 -148
- package/dist/compaction/limits.d.ts +19 -142
- package/dist/compaction/limits.js +20 -147
- package/dist/compaction/memory-identity.d.ts +16 -0
- package/dist/compaction/memory-identity.js +63 -0
- package/dist/compaction/memory-input.d.ts +25 -0
- package/dist/compaction/memory-input.js +106 -0
- package/dist/compaction/memory-render.d.ts +4 -0
- package/dist/compaction/memory-render.js +47 -0
- package/dist/compaction/memory-validation.d.ts +21 -0
- package/dist/compaction/memory-validation.js +268 -0
- package/dist/compaction/memory.d.ts +79 -0
- package/dist/compaction/memory.js +118 -0
- package/dist/compaction/meter.d.ts +14 -10
- package/dist/compaction/meter.js +53 -19
- package/dist/compaction/operation.d.ts +18 -0
- package/dist/compaction/operation.js +68 -0
- package/dist/compaction/pressure.d.ts +5 -20
- package/dist/compaction/pressure.js +13 -23
- package/dist/compaction/summarize-call.d.ts +3 -16
- package/dist/compaction/summarize-call.js +20 -20
- package/dist/compaction/summary-prompt.d.ts +29 -66
- package/dist/compaction/summary-prompt.js +132 -178
- package/dist/compaction/types.d.ts +21 -3
- package/dist/compaction/wiring.d.ts +10 -25
- package/dist/compaction/wiring.js +56 -66
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +1 -1
- package/dist/exec/events.d.ts +8 -0
- package/dist/exec/index.js +13 -6
- package/dist/exec/runner.d.ts +8 -0
- package/dist/exec/runner.js +26 -2
- package/dist/session/persist.d.ts +3 -0
- package/dist/session/persist.js +9 -0
- package/dist/session/store.js +2 -0
- package/dist/session/validate-session.js +34 -0
- package/dist/team/subagent.js +8 -0
- package/dist/ui/App.js +218 -116
- package/dist/ui/BottomStatusRow.d.ts +5 -10
- package/dist/ui/BottomStatusRow.js +7 -11
- package/dist/ui/Composer.d.ts +4 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +6 -2
- package/dist/ui/Header.js +7 -6
- package/dist/ui/PromptInput.d.ts +3 -1
- package/dist/ui/PromptInput.js +32 -41
- package/dist/ui/StatusBar.d.ts +6 -0
- package/dist/ui/StatusBar.js +8 -7
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +3 -1
- package/dist/ui/entries/CompactionCard.js +7 -5
- package/dist/ui/frame-differ.js +27 -0
- package/dist/ui/frame-observer.js +18 -5
- package/dist/ui/interaction-copy.d.ts +73 -73
- package/dist/ui/interaction-copy.js +30 -38
- package/dist/ui/interaction-hints.d.ts +8 -1
- package/dist/ui/interaction-hints.js +56 -69
- package/dist/ui/layout/AppShell.d.ts +6 -2
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/OverlayFrame.d.ts +2 -0
- package/dist/ui/layout/OverlayFrame.js +23 -14
- package/dist/ui/layout/ScrollViewport.d.ts +3 -1
- package/dist/ui/layout/ScrollViewport.js +10 -2
- package/dist/ui/layout/budget.d.ts +25 -26
- package/dist/ui/layout/budget.js +29 -22
- package/dist/ui/layout/document-layout.d.ts +2 -2
- package/dist/ui/layout/document-layout.js +2 -3
- package/dist/ui/layout/queue-layout.js +4 -4
- package/dist/ui/layout/status-detail-layout.d.ts +9 -0
- package/dist/ui/layout/status-detail-layout.js +56 -0
- package/dist/ui/layout/status-layout.d.ts +19 -2
- package/dist/ui/layout/status-layout.js +95 -84
- package/dist/ui/layout/wrap-rows.d.ts +2 -0
- package/dist/ui/layout/wrap-rows.js +21 -7
- package/dist/ui/overlays/ConfirmDialog.d.ts +2 -1
- package/dist/ui/overlays/ConfirmDialog.js +22 -3
- package/dist/ui/overlays/HelpOverlay.js +9 -2
- package/dist/ui/overlays/ModelPicker.d.ts +5 -8
- package/dist/ui/overlays/ModelPicker.js +44 -10
- package/dist/ui/overlays/ModelProfileEditor.js +2 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +1 -0
- package/dist/ui/overlays/ModelProfilePicker.js +4 -2
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +2 -1
- package/dist/ui/overlays/PlanReviewOverlay.js +7 -4
- package/dist/ui/overlays/QuestionOverlay.d.ts +2 -1
- package/dist/ui/overlays/QuestionOverlay.js +45 -32
- package/dist/ui/overlays/QueueOverlay.js +2 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +1 -0
- package/dist/ui/overlays/SettingsScreen.js +10 -3
- package/dist/ui/run-metrics.d.ts +23 -0
- package/dist/ui/run-metrics.js +27 -0
- package/dist/ui/status-feedback.d.ts +33 -0
- package/dist/ui/status-feedback.js +36 -0
- package/dist/ui/stdout-frame-writer.js +28 -0
- package/package.json +2 -2
|
@@ -1,62 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope.
|
|
6
|
-
*
|
|
7
|
-
* PURE. Two reasons this is a digest rather than raw messages, and the second
|
|
8
|
-
* one is the one that bites:
|
|
9
|
-
*
|
|
10
|
-
* - Tool results are the bulk. Four 60 KB file reads are 240 KB of body that
|
|
11
|
-
* says almost nothing about what was decided.
|
|
12
|
-
* - THE SUMMARIZER MAY HAVE A SMALLER CONTEXT WINDOW THAN THE MODEL BEING
|
|
13
|
-
* COMPACTED. The whole point of using a cheap model is that it is cheap, and
|
|
14
|
-
* cheap models are exactly the ones with 32 k windows. Sending 180 k of
|
|
15
|
-
* history to summarize would fail with the same error we are recovering from
|
|
16
|
-
* (R-4).
|
|
17
|
-
*/
|
|
18
|
-
import { type Message } from '@aragon-agent/core';
|
|
19
|
-
/**
|
|
20
|
-
* Is this `user` message a previous `<compacted_context>` block?
|
|
21
|
-
*
|
|
22
|
-
* Recognized on the OPENING TAG rather than a full parse: the block carries
|
|
23
|
-
* attributes (`version`, `replaced`, `turns`, `anchor`, `generation`) that this
|
|
24
|
-
* module has no business knowing about, and a stricter match would fail on the
|
|
25
|
-
* first attribute anybody adds.
|
|
26
|
-
*/
|
|
27
|
-
export declare function isPriorSummaryBlock(message: Message): boolean;
|
|
1
|
+
/** Complete transcript input with bounded tool-only clipping and trusted sources. */
|
|
2
|
+
import { type Message, type UserMessage } from '@aragon-agent/core';
|
|
3
|
+
import { type CompactionMemory, type MemoryCoverage, type MemorySource } from './memory.js';
|
|
28
4
|
export interface DigestInput {
|
|
29
|
-
/** The head — `messages[protectedPrefix..cutIndex)`. */
|
|
30
5
|
head: readonly Message[];
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* NOT the lead model's. This is the whole reason a digest exists rather than
|
|
35
|
-
* raw messages (R-4).
|
|
36
|
-
*/
|
|
6
|
+
/** Retained original task is context only, never current acceptance evidence. */
|
|
7
|
+
anchor?: UserMessage;
|
|
37
8
|
summarizerWindow?: number;
|
|
9
|
+
generation?: number;
|
|
10
|
+
startIndex?: number;
|
|
11
|
+
priorMemory?: CompactionMemory;
|
|
12
|
+
legacySummary?: string;
|
|
38
13
|
}
|
|
39
14
|
export interface DigestResult {
|
|
40
15
|
text: string;
|
|
41
|
-
/** Entries the budget forced out. `> 0` means the marker is present. */
|
|
42
16
|
omitted: number;
|
|
43
|
-
/** The budget actually used, after the window derivation. */
|
|
44
17
|
budgetChars: number;
|
|
18
|
+
sourceMap: Map<string, MemorySource>;
|
|
19
|
+
coverage: MemoryCoverage;
|
|
45
20
|
}
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* `min(digestMaxChars, (summarizerWindow - summarizerReserveTokens) * CHARS_PER_TOKEN)`.
|
|
50
|
-
* `CHARS_PER_TOKEN` comes from core (P2-3) rather than being re-spelled here: two
|
|
51
|
-
* chars-per-token constants that must agree and nothing checking that they do is
|
|
52
|
-
* how a digest budget and an occupancy estimate drift apart.
|
|
53
|
-
*/
|
|
21
|
+
/** Text alone cannot identify a host summary; use verified identity at the caller. */
|
|
22
|
+
export declare function isPriorSummaryBlock(_message: Message): boolean;
|
|
23
|
+
/** Derive the digest ceiling; known windows below reserve have no input room. */
|
|
54
24
|
export declare function digestBudgetChars(summarizerWindow?: number): number;
|
|
55
|
-
/**
|
|
56
|
-
* Render the head into a bounded transcript.
|
|
57
|
-
*
|
|
58
|
-
* OLDEST ENTRIES ARE DROPPED FIRST, except the pinned `PRIOR SUMMARY`, which is
|
|
59
|
-
* evicted last of all. A single omission marker is prepended so the summary can
|
|
60
|
-
* say that it is incomplete instead of pretending.
|
|
61
|
-
*/
|
|
25
|
+
/** Build every head entry without mutating messages; rejects instead of omitting. */
|
|
62
26
|
export declare function buildDigest(input: DigestInput): DigestResult;
|
|
@@ -1,164 +1,129 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The head of the history, rendered as a bounded plain-text transcript
|
|
3
|
-
* (context-auto-compaction §3.6.3).
|
|
4
|
-
*
|
|
5
|
-
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope.
|
|
6
|
-
*
|
|
7
|
-
* PURE. Two reasons this is a digest rather than raw messages, and the second
|
|
8
|
-
* one is the one that bites:
|
|
9
|
-
*
|
|
10
|
-
* - Tool results are the bulk. Four 60 KB file reads are 240 KB of body that
|
|
11
|
-
* says almost nothing about what was decided.
|
|
12
|
-
* - THE SUMMARIZER MAY HAVE A SMALLER CONTEXT WINDOW THAN THE MODEL BEING
|
|
13
|
-
* COMPACTED. The whole point of using a cheap model is that it is cheap, and
|
|
14
|
-
* cheap models are exactly the ones with 32 k windows. Sending 180 k of
|
|
15
|
-
* history to summarize would fail with the same error we are recovering from
|
|
16
|
-
* (R-4).
|
|
17
|
-
*/
|
|
1
|
+
/** Complete transcript input with bounded tool-only clipping and trusted sources. */
|
|
18
2
|
import { CHARS_PER_TOKEN } from '@aragon-agent/core';
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
return text;
|
|
25
|
-
return `${text.slice(0, max)}...`;
|
|
3
|
+
import { COMPACTION_LIMITS as LIMITS } from './limits.js';
|
|
4
|
+
import { isLegacyMemoryText, MemoryError, serializeMemory, } from './memory.js';
|
|
5
|
+
/** Text alone cannot identify a host summary; use verified identity at the caller. */
|
|
6
|
+
export function isPriorSummaryBlock(_message) {
|
|
7
|
+
return false;
|
|
26
8
|
}
|
|
27
|
-
/**
|
|
28
|
-
function
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.join('\n');
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Is this `user` message a previous `<compacted_context>` block?
|
|
39
|
-
*
|
|
40
|
-
* Recognized on the OPENING TAG rather than a full parse: the block carries
|
|
41
|
-
* attributes (`version`, `replaced`, `turns`, `anchor`, `generation`) that this
|
|
42
|
-
* module has no business knowing about, and a stricter match would fail on the
|
|
43
|
-
* first attribute anybody adds.
|
|
44
|
-
*/
|
|
45
|
-
export function isPriorSummaryBlock(message) {
|
|
46
|
-
if (message.role !== 'user')
|
|
47
|
-
return false;
|
|
48
|
-
const text = partsToText(message.content).trimStart();
|
|
49
|
-
return text.startsWith(BLOCK_TAG_OPEN);
|
|
9
|
+
/** Derive the digest ceiling; known windows below reserve have no input room. */
|
|
10
|
+
export function digestBudgetChars(summarizerWindow) {
|
|
11
|
+
if (summarizerWindow === undefined ||
|
|
12
|
+
!Number.isFinite(summarizerWindow) ||
|
|
13
|
+
summarizerWindow <= 0) {
|
|
14
|
+
return LIMITS.digestMaxChars;
|
|
15
|
+
}
|
|
16
|
+
return Math.min(LIMITS.digestMaxChars, Math.max(0, (summarizerWindow - LIMITS.summarizerReserveTokens) * CHARS_PER_TOKEN));
|
|
50
17
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
18
|
+
function contentText(content) {
|
|
19
|
+
if (typeof content === 'string')
|
|
20
|
+
return { text: content, visible: [content] };
|
|
21
|
+
return {
|
|
22
|
+
text: content
|
|
23
|
+
.map((part) => part.type === 'text' ? part.text : '[image unavailable to summarizer]')
|
|
24
|
+
.join('\n'),
|
|
25
|
+
visible: content.flatMap((part) => part.type === 'text' ? [part.text] : []),
|
|
26
|
+
};
|
|
60
27
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// CARRIED WHOLE, up to `summaryMaxChars` (D-22 / P1-9).
|
|
65
|
-
//
|
|
66
|
-
// A `<compacted_context>` block IS a `user` message (D-5, deliberately),
|
|
67
|
-
// so a per-kind table that clipped it to `userChars` (2 000) would discard
|
|
68
|
-
// up to two thirds of the previous summary - which §5.4 permits to be
|
|
69
|
-
// 6 000 long - BEFORE summarizing it, and then report `summarized`. Every
|
|
70
|
-
// compaction after the first would decay the record silently.
|
|
71
|
-
const body = stripBlockTags(partsToText(message.content));
|
|
72
|
-
return [{ text: `PRIOR SUMMARY: ${clip(body, COMPACTION_LIMITS.summaryMaxChars)}`, pinned: true }];
|
|
73
|
-
}
|
|
74
|
-
return [
|
|
75
|
-
{ text: `USER: ${clip(partsToText(message.content), COMPACTION_LIMITS.userChars)}`, pinned: false },
|
|
76
|
-
];
|
|
77
|
-
}
|
|
78
|
-
if (message.role === 'tool_result') {
|
|
79
|
-
const status = message.isError ? 'error' : 'ok';
|
|
80
|
-
const body = clip(partsToText(message.content), COMPACTION_LIMITS.toolResultChars);
|
|
81
|
-
return [{ text: `RESULT[${status}] ${body}`, pinned: false }];
|
|
82
|
-
}
|
|
83
|
-
// Assistant: one entry per block, so a turn's text and its calls can be
|
|
84
|
-
// evicted independently.
|
|
85
|
-
const out = [];
|
|
28
|
+
function renderAssistant(message, calls) {
|
|
29
|
+
const text = [];
|
|
30
|
+
const visible = [];
|
|
86
31
|
for (const block of message.content) {
|
|
87
32
|
if (block.type === 'text') {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
out.push({ text: `ASSISTANT: ${clip(block.text, COMPACTION_LIMITS.assistantChars)}`, pinned: false });
|
|
33
|
+
text.push(block.text);
|
|
34
|
+
visible.push(block.text);
|
|
91
35
|
}
|
|
92
|
-
|
|
93
|
-
const args =
|
|
94
|
-
|
|
36
|
+
if (block.type === 'tool_call') {
|
|
37
|
+
const args = JSON.stringify(block.args);
|
|
38
|
+
text.push(`CALL ${block.toolName} toolCallId=${block.toolCallId}\n${args}`);
|
|
39
|
+
visible.push(args);
|
|
40
|
+
calls.set(block.toolCallId, block.toolName);
|
|
95
41
|
}
|
|
96
|
-
// `thinking` blocks are DROPPED ENTIRELY (D-8): they are the model's private
|
|
97
|
-
// scratch, they are the single largest contributor at `thinkingLevel: high`,
|
|
98
|
-
// and nothing after the turn depends on them.
|
|
99
42
|
}
|
|
100
|
-
return
|
|
43
|
+
return { text: text.join('\n'), visible };
|
|
101
44
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
45
|
+
function renderEntry(message, messageId, calls, result) {
|
|
46
|
+
const rendered = message.role === 'assistant'
|
|
47
|
+
? renderAssistant(message, calls)
|
|
48
|
+
: contentText(message.content);
|
|
49
|
+
const source = { role: message.role, visibleText: rendered.visible };
|
|
50
|
+
let heading = `[${messageId}] ${message.role}`;
|
|
51
|
+
if (isLegacyMemoryText(message)) {
|
|
52
|
+
source.isLegacyReference = true;
|
|
53
|
+
heading += ' (legacy read-only reference, not current user acceptance)';
|
|
54
|
+
}
|
|
55
|
+
if (message.role === 'tool_result') {
|
|
56
|
+
source.toolCallId = message.toolCallId;
|
|
57
|
+
source.toolName = calls.get(message.toolCallId);
|
|
58
|
+
calls.delete(message.toolCallId);
|
|
59
|
+
source.isError = message.isError;
|
|
60
|
+
heading += ` tool=${source.toolName ?? 'unknown'} toolCallId=${message.toolCallId}`
|
|
61
|
+
+ ` isError=${message.isError === true}`;
|
|
62
|
+
const edge = LIMITS.toolResultEdgeChars;
|
|
63
|
+
if (rendered.text.length > 2 * edge) {
|
|
64
|
+
const omittedChars = rendered.text.length - 2 * edge;
|
|
65
|
+
const first = rendered.text.slice(0, edge);
|
|
66
|
+
const last = rendered.text.slice(-edge);
|
|
67
|
+
source.visibleText = clippedVisibleSections(rendered.visible, rendered.text, edge);
|
|
68
|
+
rendered.text = `${first}\n[clipped originalChars=${rendered.text.length}`
|
|
69
|
+
+ ` omittedChars=${omittedChars}]\n${last}`;
|
|
70
|
+
result.coverage.clippedToolResults.push({ messageId, omittedChars });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
result.sourceMap.set(messageId, source);
|
|
74
|
+
if (message.role !== 'user')
|
|
75
|
+
result.coverage.summarizedMessages += 1;
|
|
76
|
+
return `${heading}\n${rendered.text}`;
|
|
77
|
+
}
|
|
78
|
+
function clippedVisibleSections(parts, rendered, edge) {
|
|
79
|
+
const visible = [];
|
|
80
|
+
let from = 0;
|
|
81
|
+
for (const part of parts) {
|
|
82
|
+
const start = rendered.indexOf(part, from);
|
|
83
|
+
const end = start + part.length;
|
|
84
|
+
if (start < edge)
|
|
85
|
+
visible.push(part.slice(0, Math.max(0, edge - start)));
|
|
86
|
+
if (end > rendered.length - edge) {
|
|
87
|
+
visible.push(part.slice(Math.max(0, rendered.length - edge - start)));
|
|
88
|
+
}
|
|
89
|
+
from = end;
|
|
90
|
+
}
|
|
91
|
+
return visible.filter(Boolean);
|
|
118
92
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Render the head into a bounded transcript.
|
|
121
|
-
*
|
|
122
|
-
* OLDEST ENTRIES ARE DROPPED FIRST, except the pinned `PRIOR SUMMARY`, which is
|
|
123
|
-
* evicted last of all. A single omission marker is prepended so the summary can
|
|
124
|
-
* say that it is incomplete instead of pretending.
|
|
125
|
-
*/
|
|
93
|
+
/** Build every head entry without mutating messages; rejects instead of omitting. */
|
|
126
94
|
export function buildDigest(input) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return { text: entries.map((e) => e.text).join(sep), omitted: 0, budgetChars };
|
|
95
|
+
const generation = input.generation ?? 1;
|
|
96
|
+
const start = input.startIndex ?? 0;
|
|
97
|
+
if (!Number.isSafeInteger(generation) || generation < 1 ||
|
|
98
|
+
!Number.isSafeInteger(start) || start < 0) {
|
|
99
|
+
throw new MemoryError('invalid_memory_delta', '$.sourceIds');
|
|
133
100
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
omitted += 1;
|
|
101
|
+
const result = {
|
|
102
|
+
text: '',
|
|
103
|
+
omitted: 0,
|
|
104
|
+
budgetChars: digestBudgetChars(input.summarizerWindow),
|
|
105
|
+
sourceMap: new Map(),
|
|
106
|
+
coverage: { summarizedMessages: 0, clippedToolResults: [] },
|
|
107
|
+
};
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (input.anchor) {
|
|
110
|
+
parts.push('ORIGINAL TASK (read-only reference, not current evidence):\n'
|
|
111
|
+
+ contentText(input.anchor.content).text);
|
|
146
112
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (total + reserve > budgetChars) {
|
|
150
|
-
const room = Math.max(0, budgetChars - reserve);
|
|
151
|
-
const joined = kept.filter((e) => e.text.length > 0).map((e) => e.text).join(sep);
|
|
152
|
-
return {
|
|
153
|
-
text: `${OMISSION_MARKER(omitted)}${sep}${joined.slice(0, room)}`,
|
|
154
|
-
omitted,
|
|
155
|
-
budgetChars,
|
|
156
|
-
};
|
|
113
|
+
if (input.priorMemory) {
|
|
114
|
+
parts.push(`PRIOR MEMORY (read-only):\n${serializeMemory(input.priorMemory)}`);
|
|
157
115
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
116
|
+
if (input.legacySummary && !input.priorMemory?.legacySummary) {
|
|
117
|
+
parts.push(`LEGACY SUMMARY (read-only, incomplete provenance):\n${input.legacySummary}`);
|
|
118
|
+
}
|
|
119
|
+
const calls = new Map();
|
|
120
|
+
input.head.forEach((message, index) => parts.push(renderEntry(message, `g${generation}:m${start + index}`, calls, result)));
|
|
121
|
+
result.text = parts.join('\n\n');
|
|
122
|
+
if (result.text.length > result.budgetChars) {
|
|
123
|
+
throw new MemoryError('digest_budget_exceeded', '$.digest');
|
|
124
|
+
}
|
|
125
|
+
if (result.coverage.clippedToolResults.length > LIMITS.coverageClips) {
|
|
126
|
+
throw new MemoryError('protected_memory_too_large', '$.coverage');
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
164
129
|
}
|
|
@@ -1,165 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* COMPACTION_LIMITS — the single authority on every bound context compaction
|
|
3
|
-
* enforces (context-auto-compaction §5.4).
|
|
4
|
-
*
|
|
5
|
-
* ASCII ONLY: `src/compaction/**` is inside the glyph scanner's scope
|
|
6
|
-
* (`glyphs.test.ts::inScope`), so no literal in this tree may hold a non-ASCII
|
|
7
|
-
* byte. That regex is a HARDCODED DIRECTORY LIST, which is why adding this tree
|
|
8
|
-
* and adding `compaction` to that list are the SAME change (C-7) - a scanner
|
|
9
|
-
* that silently stops scanning is worse than no scanner. `fast/limits.ts`
|
|
10
|
-
* records the identical trap one feature earlier.
|
|
11
|
-
*
|
|
12
|
-
* TWO KINDS OF NUMBER, AND THEY ARE NOT INTERCHANGEABLE - the distinction
|
|
13
|
-
* `TEAM_LIMITS`, `TODO_LIMITS` and `FAST_LIMITS` all state:
|
|
14
|
-
*
|
|
15
|
-
* - The entries below are STRUCTURAL. They describe what a digest, a
|
|
16
|
-
* summarization call and a card can physically carry. A user has no business
|
|
17
|
-
* tuning them.
|
|
18
|
-
* - The six `compaction.*` keys in `config/schema.ts` are POLICY: when to fire,
|
|
19
|
-
* how much to keep, which model, what to do on failure. Those are clamped,
|
|
20
|
-
* persisted and user-facing.
|
|
21
|
-
*/
|
|
1
|
+
/** Structural limits for v2 compaction. All character counts are UTF-16 units. */
|
|
22
2
|
export declare const COMPACTION_LIMITS: {
|
|
23
|
-
/** Ceiling on the rendered digest, before the summarizer-window check. ~30k tokens. */
|
|
24
3
|
readonly digestMaxChars: 120000;
|
|
25
|
-
/** Output + system prompt + framing, kept out of the digest's share of the window. */
|
|
26
4
|
readonly summarizerReserveTokens: 8000;
|
|
27
|
-
readonly
|
|
28
|
-
readonly
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
readonly
|
|
43
|
-
/** What the summarizer prompt asks for, and what the splice clips to. */
|
|
44
|
-
readonly summaryMaxChars: 6000;
|
|
45
|
-
readonly summaryOutputTokens: 2048;
|
|
46
|
-
/**
|
|
47
|
-
* Wall-clock budget for ONE summarization call.
|
|
48
|
-
*
|
|
49
|
-
* Longer than the fast reviewer's 20 s because the input is ~30x larger and the
|
|
50
|
-
* output ~4x larger, and because the cost of a timeout here is a DEGRADED RUN
|
|
51
|
-
* rather than a missing critique.
|
|
52
|
-
*
|
|
53
|
-
* COUPLED TO THE ENGINE'S `COMPACTION_HARD_TIMEOUT_MS` (120 s). The worst legal
|
|
54
|
-
* host budget is two calls at this value back to back plus the digest render,
|
|
55
|
-
* so `2 * callTimeoutMs` must stay strictly under the engine's ceiling. Raising
|
|
56
|
-
* this without raising that makes the §3.7 ladder unreachable and turns every
|
|
57
|
-
* slow-but-correct summarization into `manager_timeout`.
|
|
58
|
-
*/
|
|
5
|
+
readonly summaryMaxChars: 32000;
|
|
6
|
+
readonly summaryOutputTokens: 4096;
|
|
7
|
+
readonly memoryJsonChars: 32000;
|
|
8
|
+
readonly memoryEnvelopeChars: 1024;
|
|
9
|
+
readonly protectedMemoryChars: 48000;
|
|
10
|
+
readonly memoryItems: 200;
|
|
11
|
+
readonly protectedUserMessages: 200;
|
|
12
|
+
readonly memoryAdditions: 64;
|
|
13
|
+
readonly coverageClips: 200;
|
|
14
|
+
readonly jsonDepth: 32;
|
|
15
|
+
readonly instructionsChars: 4000;
|
|
16
|
+
readonly itemTextChars: 1200;
|
|
17
|
+
readonly sourceExcerptChars: 512;
|
|
18
|
+
readonly itemSources: 8;
|
|
19
|
+
readonly toolResultEdgeChars: 1500;
|
|
20
|
+
readonly operationTimeoutMs: 120000;
|
|
59
21
|
readonly callTimeoutMs: 45000;
|
|
60
|
-
/** Turns between compactions. Never two in a row: a compaction that just ran
|
|
61
|
-
* needs a real turn before its effect is measurable, because `lastUsage` is
|
|
62
|
-
* still the pre-compaction number until the next `turn_end`. */
|
|
63
22
|
readonly minTurnsBetween: 2;
|
|
64
|
-
/** Compactions STARTED per run, both triggers, hard. */
|
|
65
23
|
readonly maxPerRun: 5;
|
|
66
|
-
/** A splice must project occupancy below `threshold - this`, or it bought two turns. */
|
|
67
24
|
readonly minReclaimRatio: 0.15;
|
|
68
|
-
/** Consecutive no-progress PRESSURE compactions before the proactive trigger self-disables. */
|
|
69
25
|
readonly stuckLimit: 2;
|
|
70
|
-
/**
|
|
71
|
-
* Clip bound on an oversized `tool_result` body inside the RETAINED tail.
|
|
72
|
-
*
|
|
73
|
-
* 2 000 rather than `toolResultChars`' 600, and the two are not the same
|
|
74
|
-
* question. That one bounds a message in the DIGEST, which the summarizer
|
|
75
|
-
* reads once and which exists to be lossy. This one bounds a message the model
|
|
76
|
-
* keeps WORKING FROM: it is the last two-to-four turns, and the run is about to
|
|
77
|
-
* continue on them. Relief only ever runs when the alternative is a request the
|
|
78
|
-
* provider cannot accept.
|
|
79
|
-
*/
|
|
80
26
|
readonly tailToolResultChars: 2000;
|
|
81
|
-
/**
|
|
82
|
-
* Room reserved for the `<compacted_context>` block in the tail-budget
|
|
83
|
-
* projection.
|
|
84
|
-
*
|
|
85
|
-
* THE BLOCK IS WRITTEN AFTER THE PROJECTION, which is why this exists at all:
|
|
86
|
-
* relief measures a spliced history whose summary body is already in it, but
|
|
87
|
-
* the `plan === null` path measures one where nothing has been written yet, and
|
|
88
|
-
* a budget with no allowance would let the block push the result back over.
|
|
89
|
-
*/
|
|
90
27
|
readonly blockAllowanceTokens: 2000;
|
|
91
|
-
/**
|
|
92
|
-
* Files kept for ONE `runId`, newest first.
|
|
93
|
-
*
|
|
94
|
-
* PER RUN, NOT PER DIRECTORY (RV-6). `<home>/compaction/` is shared by every
|
|
95
|
-
* `aragon` on the machine — `file-sink.ts:94-95` already treats concurrent
|
|
96
|
-
* processes as normal — so a global count-based prune would delete a live
|
|
97
|
-
* session's archives out from under it.
|
|
98
|
-
*/
|
|
99
28
|
readonly archiveMaxFiles: 20;
|
|
100
|
-
/**
|
|
101
|
-
* The cross-run sweep: 7 days.
|
|
102
|
-
*
|
|
103
|
-
* AGE RATHER THAN COUNT, for the reason above. Age can only ever delete files
|
|
104
|
-
* whose run finished a week ago; a count can delete the newest file another
|
|
105
|
-
* process wrote a second ago.
|
|
106
|
-
*/
|
|
107
29
|
readonly archiveMaxAgeMs: 604800000;
|
|
108
|
-
/**
|
|
109
|
-
* Per-file ceiling before dropped message bodies are clipped from the OLDEST
|
|
110
|
-
* end. The metadata and the summary are never clipped: a truncated archive
|
|
111
|
-
* that still says what happened beats none.
|
|
112
|
-
*/
|
|
113
30
|
readonly archiveMaxBytes: 8000000;
|
|
114
|
-
/** A child's `keepRecentTurns`, delivered through its config view. */
|
|
115
31
|
readonly childKeepRecentTurns: 2;
|
|
116
|
-
/**
|
|
117
|
-
* A child's guard-2 ceiling, delivered through `Compactor`'s instance bound.
|
|
118
|
-
*
|
|
119
|
-
* HERE RATHER THAN AS A `compaction.*` KEY, and the placement is the whole
|
|
120
|
-
* argument: `maxPerRun` is STRUCTURAL (see this file's header), there is no
|
|
121
|
-
* config path to it by design, and a child needs a tighter one. Both numbers
|
|
122
|
-
* therefore stay in the one file that owns structural bounds.
|
|
123
|
-
*/
|
|
124
32
|
readonly childMaxPerRun: 2;
|
|
125
|
-
/** Rows the collapsed transcript card renders; Ctrl+O expands. */
|
|
126
33
|
readonly cardTextRows: 6;
|
|
127
|
-
/**
|
|
128
|
-
* Below this many columns the status chip drops.
|
|
129
|
-
*
|
|
130
|
-
* 100, matching all four existing peers - `retry-view.ts`, `fast/limits.ts`,
|
|
131
|
-
* `team/limits.ts`, `todo/limits.ts` (P2-2). A status-bar degradation ladder
|
|
132
|
-
* that is nearly-but-not-quite uniform is a reading hazard bought for nothing.
|
|
133
|
-
* The guaranteed reporting surface is `/compact status`, which is what makes
|
|
134
|
-
* dropping the chip acceptable at all.
|
|
135
|
-
*/
|
|
136
34
|
readonly statusCompactCols: 100;
|
|
137
35
|
};
|
|
138
|
-
|
|
139
|
-
* Bumped whenever the wording of the `<compacted_context>` block or the
|
|
140
|
-
* summarizer system prompt changes, so a behaviour report can be tied to a block
|
|
141
|
-
* revision with one grep - exactly what `FAST_BLOCK_VERSION` and
|
|
142
|
-
* `TEAM_BLOCK_VERSION` are for.
|
|
143
|
-
*/
|
|
144
|
-
export declare const COMPACTION_BLOCK_VERSION = "v1-2026-08";
|
|
145
|
-
/** Opening tag of the verbatim goal anchor (§3.6.5). */
|
|
36
|
+
export declare const COMPACTION_BLOCK_VERSION = "v2-2026-10";
|
|
146
37
|
export declare const ANCHOR_TAG_OPEN = "<original_task>";
|
|
147
38
|
export declare const ANCHOR_TAG_CLOSE = "</original_task>";
|
|
148
|
-
/** Opening tag of the spliced summary block. Recognized on the NEXT compaction. */
|
|
149
39
|
export declare const BLOCK_TAG_OPEN = "<compacted_context";
|
|
150
40
|
export declare const BLOCK_TAG_CLOSE = "</compacted_context>";
|
|
151
|
-
/**
|
|
152
|
-
export declare const ANCHOR_TRUNCATION_MARK = " [truncated]";
|
|
153
|
-
/**
|
|
154
|
-
* What tail relief writes where it removed text (hardening §3.3.2 rule 4).
|
|
155
|
-
*
|
|
156
|
-
* IN THE HISTORY THE MODEL READS, at the exact place the data went missing. A
|
|
157
|
-
* model that sees this knows the output is partial and can re-run the tool; a
|
|
158
|
-
* model handed a silently truncated file cannot tell the difference between "the
|
|
159
|
-
* file ends here" and "the file was cut here".
|
|
160
|
-
*
|
|
161
|
-
* THE HOST OWNS IT, not core: `engine/compaction.ts` has no glyph vocabulary and
|
|
162
|
-
* must not learn one, so `relieveTail` takes this as a callback. ASCII, per this
|
|
163
|
-
* file's header.
|
|
164
|
-
*/
|
|
41
|
+
/** Retained legacy helper for explicit callers; v2 compression never clips the tail. */
|
|
165
42
|
export declare function tailClipMarker(removed: number): string;
|