billion-context-pi 0.1.43 → 0.1.46
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 +12 -0
- package/README.zh-CN.md +12 -0
- package/dist/compress-tool.d.ts +8 -5
- package/dist/index.js +1437 -1093
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +10 -5
- package/dist/setup-subagent-tools.d.ts +46 -3
- package/dist/update.d.ts +26 -0
- package/package.json +2 -8
package/dist/index.js
CHANGED
|
@@ -4,204 +4,555 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
// node_modules/acp-kernel/dist/
|
|
7
|
+
// node_modules/acp-kernel/dist/chunk-MWXUJVMN.js
|
|
8
8
|
import { createRequire } from "module";
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {
|
|
16
|
-
throw new RangeError(
|
|
17
|
-
`ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
return `m${String(index).padStart(REF_WIDTH, "0")}`;
|
|
21
|
-
}
|
|
22
|
-
function refToIndex(ref) {
|
|
23
|
-
const match = REF_PATTERN.exec(ref.trim().toLowerCase());
|
|
24
|
-
if (!match) return null;
|
|
25
|
-
const index = Number(match[1]);
|
|
26
|
-
if (index < MIN_INDEX || index > MAX_INDEX) return null;
|
|
27
|
-
return index;
|
|
28
|
-
}
|
|
29
|
-
function refForRaw(map, rawId) {
|
|
30
|
-
return map.byRaw[rawId] ?? null;
|
|
31
|
-
}
|
|
32
|
-
function assignRefs(messages, options) {
|
|
33
|
-
const map = {
|
|
34
|
-
byRaw: { ...options.existing.byRaw },
|
|
35
|
-
byRef: { ...options.existing.byRef }
|
|
36
|
-
};
|
|
37
|
-
let cursor = Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX ? options.nextIndex : MIN_INDEX;
|
|
38
|
-
let newlyAssigned = 0;
|
|
39
|
-
for (const message of messages) {
|
|
40
|
-
if (!message.id || options.shouldSkip?.(message)) continue;
|
|
41
|
-
if (map.byRaw[message.id]) continue;
|
|
42
|
-
if (options.isProtected?.(message)) {
|
|
43
|
-
map.byRaw[message.id] = BLOCKED_REF;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const ref = allocateFreeRef(map, cursor);
|
|
47
|
-
cursor = ref.index + 1;
|
|
48
|
-
map.byRaw[message.id] = ref.text;
|
|
49
|
-
map.byRef[ref.text] = message.id;
|
|
50
|
-
newlyAssigned++;
|
|
51
|
-
}
|
|
52
|
-
return { map, nextIndex: cursor, newlyAssigned };
|
|
9
|
+
var require2 = createRequire(import.meta.url);
|
|
10
|
+
function defaultCountTokens(text) {
|
|
11
|
+
if (!text) return 0;
|
|
12
|
+
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
13
|
+
const cjkCount = cjk?.length ?? 0;
|
|
14
|
+
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
53
15
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
16
|
+
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
17
|
+
- All compression serves the primary task, but be frugal.
|
|
18
|
+
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
19
|
+
- Compress by need, not by percentage.
|
|
20
|
+
- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
|
|
21
|
+
var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
|
|
22
|
+
|
|
23
|
+
When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
|
|
24
|
+
|
|
25
|
+
KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
|
|
26
|
+
- Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
|
|
27
|
+
- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
|
|
28
|
+
- Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
|
|
29
|
+
- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
|
|
30
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
|
|
31
|
+
- Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
|
|
32
|
+
- Exact values: versions, config keys, thresholds, magic numbers.
|
|
33
|
+
- User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
|
|
34
|
+
- The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
|
|
35
|
+
- Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
|
|
36
|
+
- Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
|
|
37
|
+
- Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
|
|
38
|
+
|
|
39
|
+
DROP \u2014 extract the signal, discard the vessel:
|
|
40
|
+
- Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
|
|
41
|
+
- Duplicate file reads once the needed content is recorded.
|
|
42
|
+
- Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
|
|
43
|
+
- Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
|
|
44
|
+
- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
|
|
45
|
+
- Repeated status checks (\`git status\`, \`ls\`) once state is known.
|
|
46
|
+
|
|
47
|
+
For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
|
|
48
|
+
|
|
49
|
+
PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
50
|
+
1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
|
|
51
|
+
2. Decisions and rationale.
|
|
52
|
+
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
53
|
+
4. Conclusions and key findings.
|
|
54
|
+
5. Lessons learned: what failed and why.
|
|
55
|
+
|
|
56
|
+
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
57
|
+
var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
58
|
+
|
|
59
|
+
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
60
|
+
|
|
61
|
+
KEEP \u2014 these are the only things that survive distillation:
|
|
62
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
63
|
+
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
64
|
+
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
65
|
+
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
66
|
+
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
67
|
+
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
68
|
+
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
69
|
+
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
70
|
+
|
|
71
|
+
DROP \u2014 these were useful during the work but are no longer needed:
|
|
72
|
+
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
73
|
+
- Build/deploy process details, test execution steps.
|
|
74
|
+
- Review process details (who reviewed, what rounds, test counts).
|
|
75
|
+
- Verbose logs, command output, intermediate debugging steps.
|
|
76
|
+
|
|
77
|
+
FORMAT:
|
|
78
|
+
- Start each distilled block with a source header line:
|
|
79
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
80
|
+
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
81
|
+
- 3-5 bullet points per source block, each a self-contained fact.
|
|
82
|
+
- Dense, scannable \u2014 no narrative prose.
|
|
83
|
+
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
84
|
+
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
85
|
+
|
|
86
|
+
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
|
|
87
|
+
var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
88
|
+
|
|
89
|
+
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
90
|
+
|
|
91
|
+
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
92
|
+
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
93
|
+
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
94
|
+
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
95
|
+
4. Critical constraints ("must support Node 22").
|
|
96
|
+
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
97
|
+
|
|
98
|
+
FORMAT:
|
|
99
|
+
- Start with a source header line:
|
|
100
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
101
|
+
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
102
|
+
- No explanations, no rationale, no process \u2014 just the fact.
|
|
103
|
+
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
104
|
+
- Merge related facts from different source blocks if they concern the same topic.
|
|
105
|
+
|
|
106
|
+
EXAMPLES:
|
|
107
|
+
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
108
|
+
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
109
|
+
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
110
|
+
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
111
|
+
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
112
|
+
|
|
113
|
+
DROP:
|
|
114
|
+
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
115
|
+
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
116
|
+
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
117
|
+
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
118
|
+
|
|
119
|
+
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
120
|
+
var defaultPrompts = Object.freeze({
|
|
121
|
+
compressPhilosophy: COMPRESS_PHILOSOPHY,
|
|
122
|
+
howToCompressRules: HOW_TO_COMPRESS_RULES,
|
|
123
|
+
tier2DistillRules: TIER2_DISTILL_RULES,
|
|
124
|
+
tier3CondenseRules: TIER3_CONDENSE_RULES
|
|
125
|
+
});
|
|
126
|
+
function resolvePrompts(overrides, options = {}) {
|
|
127
|
+
const clean = {};
|
|
128
|
+
if (overrides) {
|
|
129
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
clean[key] = value;
|
|
132
|
+
}
|
|
60
133
|
}
|
|
61
|
-
candidate++;
|
|
62
134
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let highest = 0;
|
|
69
|
-
for (const ref of Object.values(map.byRaw)) {
|
|
70
|
-
const index = ref === BLOCKED_REF ? null : refToIndex(ref);
|
|
71
|
-
if (index !== null && index > highest) highest = index;
|
|
135
|
+
const keys = Object.keys(clean);
|
|
136
|
+
if (keys.length > 0 && !options.acknowledgeRisk) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. Overridden keys: ${keys.join(", ")}. These rules are quality-critical (tuned over months of production use); changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`
|
|
139
|
+
);
|
|
72
140
|
}
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
function createInitialState() {
|
|
76
|
-
return {
|
|
77
|
-
blocks: [],
|
|
78
|
-
messageRefs: { byRaw: {}, byRef: {} },
|
|
79
|
-
tokenSnapshot: {},
|
|
80
|
-
nudge: {
|
|
81
|
-
lastPerMessageNudgeTokens: 0,
|
|
82
|
-
lastNudgeShownTokens: 0,
|
|
83
|
-
baselineTokens: 0,
|
|
84
|
-
anchors: {},
|
|
85
|
-
lastShownByTier: {}
|
|
86
|
-
},
|
|
87
|
-
stats: { tokensCompressed: 0, compressionCount: 0 },
|
|
88
|
-
nextBlockId: 1,
|
|
89
|
-
nextRunId: 1
|
|
90
|
-
};
|
|
141
|
+
return { ...defaultPrompts, ...clean };
|
|
91
142
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
143
|
+
function efficiencyNote(prompts) {
|
|
144
|
+
return `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
|
|
145
|
+
|
|
146
|
+
${prompts.compressPhilosophy}`;
|
|
96
147
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
148
|
+
function emergencyHeader(prompts) {
|
|
149
|
+
return `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
|
|
150
|
+
|
|
151
|
+
${prompts.compressPhilosophy}`;
|
|
101
152
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
153
|
+
function formatK(n) {
|
|
154
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
155
|
+
return `${n}`;
|
|
104
156
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
157
|
+
function formatBreakdown(bd) {
|
|
158
|
+
if (!bd) return "";
|
|
159
|
+
const parts = [];
|
|
160
|
+
if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);
|
|
161
|
+
if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);
|
|
162
|
+
if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);
|
|
163
|
+
if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);
|
|
164
|
+
if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);
|
|
165
|
+
const growth = bd.growth > 0 ? `
|
|
166
|
+
+${formatK(bd.growth)} since last nudge` : "";
|
|
167
|
+
return `Context breakdown: ${parts.join(" | ")}${growth}`;
|
|
107
168
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!block.active) continue;
|
|
112
|
-
for (const id of block.effectiveMessageIds) covered.add(id);
|
|
169
|
+
function formatTierTargetBlocks(blocks) {
|
|
170
|
+
if (blocks.length === 0) {
|
|
171
|
+
return "Target blocks: (none \u2014 no tier blocks found)";
|
|
113
172
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
block.generation = "old";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
var SUMMARY_HEADER = "[Compressed conversation section]";
|
|
126
|
-
function prune(messages, state, options = {}) {
|
|
127
|
-
const covered = coveredMessageIds(state);
|
|
128
|
-
if (covered.size === 0) return [...messages];
|
|
129
|
-
const inject = options.injectSummaries ?? true;
|
|
130
|
-
const firstUserIndex = messages.findIndex(
|
|
131
|
-
(message) => message.role === "user"
|
|
132
|
-
);
|
|
133
|
-
const indexById = /* @__PURE__ */ new Map();
|
|
134
|
-
messages.forEach((message, index) => indexById.set(message.id, index));
|
|
135
|
-
const anchors = inject ? collectSummaryAnchors(state, indexById) : [];
|
|
136
|
-
return stripOrphanedReasoning(
|
|
137
|
-
stripOrphanedToolResults(
|
|
138
|
-
stripOrphanedToolCalls(
|
|
139
|
-
rebuildMessages(messages, covered, firstUserIndex, anchors)
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
);
|
|
173
|
+
const lines = blocks.map((b) => {
|
|
174
|
+
const summaryTokens = Math.ceil((b.summary ?? "").length / 4);
|
|
175
|
+
const topic = b.topic ? ` "${b.topic}"` : "";
|
|
176
|
+
return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}\u2192${formatK(summaryTokens)}${topic}`;
|
|
177
|
+
});
|
|
178
|
+
return `Target ${blocks[0].tier === 1 ? "tier-1" : "tier-2"} blocks to distill (${blocks.length}):
|
|
179
|
+
${lines.join("\n")}`;
|
|
143
180
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
181
|
+
function formatRanges(compressible, protectedRanges) {
|
|
182
|
+
if (compressible.length === 0 && protectedRanges.length === 0) {
|
|
183
|
+
return "[No specific ranges detected \u2014 compress any consumed content.]";
|
|
184
|
+
}
|
|
185
|
+
const refNum2 = (ref) => {
|
|
186
|
+
const m = ref.match(/\d+/);
|
|
187
|
+
return m ? parseInt(m[0], 10) : 0;
|
|
188
|
+
};
|
|
189
|
+
const entries = [];
|
|
190
|
+
for (const r of compressible) {
|
|
191
|
+
entries.push({
|
|
192
|
+
startRef: r.startRef,
|
|
193
|
+
endRef: r.endRef,
|
|
194
|
+
startNum: refNum2(r.startRef),
|
|
195
|
+
endNum: refNum2(r.endRef),
|
|
196
|
+
count: r.count,
|
|
197
|
+
tokens: r.tokens,
|
|
198
|
+
toolPct: r.toolPct,
|
|
199
|
+
textPct: r.textPct,
|
|
200
|
+
compressibleTokens: r.tokens,
|
|
201
|
+
compressibleCount: r.count,
|
|
202
|
+
protectedTokens: 0,
|
|
203
|
+
protectedCount: 0,
|
|
204
|
+
protectedTools: [],
|
|
205
|
+
dangerous: r.dangerous ?? false
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
for (const r of protectedRanges) {
|
|
209
|
+
entries.push({
|
|
210
|
+
startRef: r.startRef,
|
|
211
|
+
endRef: r.endRef,
|
|
212
|
+
startNum: refNum2(r.startRef),
|
|
213
|
+
endNum: refNum2(r.endRef),
|
|
214
|
+
count: r.count,
|
|
215
|
+
tokens: r.tokens,
|
|
216
|
+
toolPct: 0,
|
|
217
|
+
textPct: 0,
|
|
218
|
+
compressibleTokens: 0,
|
|
219
|
+
compressibleCount: 0,
|
|
220
|
+
protectedTokens: r.tokens,
|
|
221
|
+
protectedCount: r.count,
|
|
222
|
+
protectedTools: [...r.tools],
|
|
223
|
+
dangerous: false
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
entries.sort((a, b) => a.startNum - b.startNum);
|
|
227
|
+
const merged = [];
|
|
228
|
+
for (const e of entries) {
|
|
229
|
+
const last = merged[merged.length - 1];
|
|
230
|
+
if (last && e.startNum <= last.endNum + 1) {
|
|
231
|
+
last.endRef = e.endRef;
|
|
232
|
+
last.endNum = Math.max(last.endNum, e.endNum);
|
|
233
|
+
last.count += e.count;
|
|
234
|
+
last.tokens += e.tokens;
|
|
235
|
+
last.compressibleTokens += e.compressibleTokens;
|
|
236
|
+
last.compressibleCount += e.compressibleCount;
|
|
237
|
+
last.protectedTokens += e.protectedTokens;
|
|
238
|
+
last.protectedCount += e.protectedCount;
|
|
239
|
+
if (e.dangerous) last.dangerous = true;
|
|
240
|
+
for (const t of e.protectedTools) {
|
|
241
|
+
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
152
242
|
}
|
|
243
|
+
} else {
|
|
244
|
+
merged.push({ ...e });
|
|
153
245
|
}
|
|
154
|
-
anchors.push({
|
|
155
|
-
blockId: block.blockId,
|
|
156
|
-
summary: block.summary,
|
|
157
|
-
topic: block.topic,
|
|
158
|
-
insertAt: earliest ?? 0
|
|
159
|
-
});
|
|
160
246
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const result = [];
|
|
166
|
-
const pending = [...anchors];
|
|
167
|
-
for (let index = 0; index < messages.length; index++) {
|
|
168
|
-
while (pending.length > 0 && pending[0].insertAt === index) {
|
|
169
|
-
result.push(renderSummary(pending.shift()));
|
|
247
|
+
const lines = merged.map((e) => {
|
|
248
|
+
const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
|
|
249
|
+
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
250
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
170
251
|
}
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
continue;
|
|
252
|
+
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
253
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
174
254
|
}
|
|
175
|
-
|
|
176
|
-
|
|
255
|
+
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
256
|
+
});
|
|
257
|
+
return `Compressible ranges (${merged.length}, oldest first):
|
|
258
|
+
${lines.join("\n")}`;
|
|
259
|
+
}
|
|
260
|
+
function renderNudgeText(decision, prompts = defaultPrompts) {
|
|
261
|
+
const breakdownStr = formatBreakdown(decision.contextBreakdown);
|
|
262
|
+
const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
|
|
263
|
+
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
264
|
+
if (decision.tier !== null && decision.tier >= 2) {
|
|
265
|
+
const isT2 = decision.tier === 2;
|
|
266
|
+
const targets = decision.tierTargetBlocks ?? [];
|
|
267
|
+
const blockList = formatTierTargetBlocks(targets);
|
|
268
|
+
const startId = targets[0]?.blockId ?? "b1";
|
|
269
|
+
const endId = targets[targets.length - 1]?.blockId ?? "b5";
|
|
270
|
+
const voice = isEmergency ? "emergency" : "gentle";
|
|
271
|
+
const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
|
|
272
|
+
return {
|
|
273
|
+
voice,
|
|
274
|
+
text: [
|
|
275
|
+
efficiencyNote(prompts),
|
|
276
|
+
"",
|
|
277
|
+
breakdownStr,
|
|
278
|
+
"",
|
|
279
|
+
triggerLine,
|
|
280
|
+
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
|
|
281
|
+
blockList,
|
|
282
|
+
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
283
|
+
"",
|
|
284
|
+
prompts.howToCompressRules,
|
|
285
|
+
"",
|
|
286
|
+
isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
|
|
287
|
+
].join("\n")
|
|
288
|
+
};
|
|
177
289
|
}
|
|
178
|
-
|
|
179
|
-
|
|
290
|
+
if (isEmergency) {
|
|
291
|
+
return {
|
|
292
|
+
voice: "emergency",
|
|
293
|
+
text: [
|
|
294
|
+
emergencyHeader(prompts),
|
|
295
|
+
"",
|
|
296
|
+
breakdownStr,
|
|
297
|
+
"",
|
|
298
|
+
prompts.howToCompressRules,
|
|
299
|
+
"",
|
|
300
|
+
`{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
|
|
301
|
+
"Only use IDs from visible messages above. Compress older work first.",
|
|
302
|
+
"",
|
|
303
|
+
rangesStr
|
|
304
|
+
].join("\n")
|
|
305
|
+
};
|
|
180
306
|
}
|
|
181
|
-
return
|
|
307
|
+
return {
|
|
308
|
+
voice: "gentle",
|
|
309
|
+
text: [
|
|
310
|
+
efficiencyNote(prompts),
|
|
311
|
+
"",
|
|
312
|
+
breakdownStr,
|
|
313
|
+
"",
|
|
314
|
+
prompts.howToCompressRules,
|
|
315
|
+
"",
|
|
316
|
+
rangesStr,
|
|
317
|
+
"",
|
|
318
|
+
`\u{1F4A1} Compress all ranges in one call (pass multiple content entries: \`content: [{...}, {...}]\`).`
|
|
319
|
+
].join("\n")
|
|
320
|
+
};
|
|
182
321
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
322
|
+
var VIABLE_RANGE_MIN_TOKENS = 200;
|
|
323
|
+
function viableRanges(ranges) {
|
|
324
|
+
return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// node_modules/acp-kernel/dist/chunk-DPH62BGM.js
|
|
328
|
+
function createInitialState() {
|
|
188
329
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
330
|
+
blocks: [],
|
|
331
|
+
messageRefs: { byRaw: {}, byRef: {} },
|
|
332
|
+
tokenSnapshot: {},
|
|
333
|
+
nudge: {
|
|
334
|
+
lastPerMessageNudgeTokens: 0,
|
|
335
|
+
lastNudgeShownTokens: 0,
|
|
336
|
+
baselineTokens: 0,
|
|
337
|
+
anchors: {},
|
|
338
|
+
lastShownByTier: {}
|
|
339
|
+
},
|
|
340
|
+
stats: { tokensCompressed: 0, compressionCount: 0 },
|
|
341
|
+
nextBlockId: 1,
|
|
342
|
+
nextRunId: 1
|
|
193
343
|
};
|
|
194
344
|
}
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
345
|
+
function allocateBlockId(state) {
|
|
346
|
+
const id = state.nextBlockId;
|
|
347
|
+
state.nextBlockId = Math.max(1, id) + 1;
|
|
348
|
+
return `b${id}`;
|
|
349
|
+
}
|
|
350
|
+
function allocateRunId(state) {
|
|
351
|
+
const id = state.nextRunId;
|
|
352
|
+
state.nextRunId = Math.max(1, id) + 1;
|
|
353
|
+
return `r${id}`;
|
|
354
|
+
}
|
|
355
|
+
function blockById(state, blockId) {
|
|
356
|
+
return state.blocks.find((block) => block.blockId === blockId);
|
|
357
|
+
}
|
|
358
|
+
function activeBlocks(state) {
|
|
359
|
+
return state.blocks.filter((block) => block.active);
|
|
360
|
+
}
|
|
361
|
+
function coveredMessageIds(state) {
|
|
362
|
+
const covered = /* @__PURE__ */ new Set();
|
|
363
|
+
for (const block of state.blocks) {
|
|
364
|
+
if (!block.active) continue;
|
|
365
|
+
for (const id of block.effectiveMessageIds) covered.add(id);
|
|
366
|
+
}
|
|
367
|
+
return covered;
|
|
368
|
+
}
|
|
369
|
+
function advanceSurvival(state, promotionThreshold) {
|
|
370
|
+
for (const block of state.blocks) {
|
|
371
|
+
if (!block.active) continue;
|
|
372
|
+
block.survivedCount += 1;
|
|
373
|
+
if (block.survivedCount >= promotionThreshold) {
|
|
374
|
+
block.generation = "old";
|
|
200
375
|
}
|
|
201
376
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// node_modules/acp-kernel/dist/index.js
|
|
380
|
+
var REF_WIDTH = 5;
|
|
381
|
+
var MIN_INDEX = 1;
|
|
382
|
+
var MAX_INDEX = 99999;
|
|
383
|
+
var REF_PATTERN = /^m0*(\d{1,5})$/;
|
|
384
|
+
var BLOCKED_REF = "BLOCKED";
|
|
385
|
+
function indexToRef(index) {
|
|
386
|
+
if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {
|
|
387
|
+
throw new RangeError(
|
|
388
|
+
`ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return `m${String(index).padStart(REF_WIDTH, "0")}`;
|
|
392
|
+
}
|
|
393
|
+
function refToIndex(ref) {
|
|
394
|
+
const match = REF_PATTERN.exec(ref.trim().toLowerCase());
|
|
395
|
+
if (!match) return null;
|
|
396
|
+
const index = Number(match[1]);
|
|
397
|
+
if (index < MIN_INDEX || index > MAX_INDEX) return null;
|
|
398
|
+
return index;
|
|
399
|
+
}
|
|
400
|
+
function refForRaw(map, rawId) {
|
|
401
|
+
return map.byRaw[rawId] ?? null;
|
|
402
|
+
}
|
|
403
|
+
function assignRefs(messages, options) {
|
|
404
|
+
const map = {
|
|
405
|
+
byRaw: { ...options.existing.byRaw },
|
|
406
|
+
byRef: { ...options.existing.byRef }
|
|
407
|
+
};
|
|
408
|
+
let cursor = Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX ? options.nextIndex : MIN_INDEX;
|
|
409
|
+
let newlyAssigned = 0;
|
|
410
|
+
for (const message of messages) {
|
|
411
|
+
if (!message.id || options.shouldSkip?.(message)) continue;
|
|
412
|
+
if (map.byRaw[message.id]) continue;
|
|
413
|
+
if (options.isProtected?.(message)) {
|
|
414
|
+
map.byRaw[message.id] = BLOCKED_REF;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const ref = allocateFreeRef(map, cursor);
|
|
418
|
+
cursor = ref.index + 1;
|
|
419
|
+
map.byRaw[message.id] = ref.text;
|
|
420
|
+
map.byRef[ref.text] = message.id;
|
|
421
|
+
newlyAssigned++;
|
|
422
|
+
}
|
|
423
|
+
return { map, nextIndex: cursor, newlyAssigned };
|
|
424
|
+
}
|
|
425
|
+
function allocateFreeRef(map, start) {
|
|
426
|
+
let candidate = Math.max(start, MIN_INDEX);
|
|
427
|
+
while (candidate <= MAX_INDEX) {
|
|
428
|
+
const text = indexToRef(candidate);
|
|
429
|
+
if (!map.byRef[text]) {
|
|
430
|
+
return { text, index: candidate };
|
|
431
|
+
}
|
|
432
|
+
candidate++;
|
|
433
|
+
}
|
|
434
|
+
throw new Error(
|
|
435
|
+
`ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
function highestUsedIndex(map) {
|
|
439
|
+
let highest = 0;
|
|
440
|
+
for (const ref of Object.values(map.byRaw)) {
|
|
441
|
+
const index = ref === BLOCKED_REF ? null : refToIndex(ref);
|
|
442
|
+
if (index !== null && index > highest) highest = index;
|
|
443
|
+
}
|
|
444
|
+
return highest;
|
|
445
|
+
}
|
|
446
|
+
var SUMMARY_HEADER = "[Compressed conversation section]";
|
|
447
|
+
var SUMMARY_ID_PREFIX = "acp_summary_";
|
|
448
|
+
function summaryMessageId(blockId) {
|
|
449
|
+
return `${SUMMARY_ID_PREFIX}${blockId}`;
|
|
450
|
+
}
|
|
451
|
+
function isSummaryMessageId(id) {
|
|
452
|
+
return id.startsWith(SUMMARY_ID_PREFIX);
|
|
453
|
+
}
|
|
454
|
+
function isRenderedSummaryMessage(message) {
|
|
455
|
+
return isSummaryMessageId(message.id) && message.role === "system" && message.contentType === "text";
|
|
456
|
+
}
|
|
457
|
+
function prune(messages, state, options = {}) {
|
|
458
|
+
const covered = coveredMessageIds(state);
|
|
459
|
+
if (covered.size === 0) return [...messages];
|
|
460
|
+
const inject = options.injectSummaries ?? true;
|
|
461
|
+
const firstUserIndex = messages.findIndex(
|
|
462
|
+
(message) => message.role === "user"
|
|
463
|
+
);
|
|
464
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
465
|
+
const summaryIndexById = /* @__PURE__ */ new Map();
|
|
466
|
+
messages.forEach((message, index) => {
|
|
467
|
+
indexById.set(message.id, index);
|
|
468
|
+
if (isRenderedSummaryMessage(message))
|
|
469
|
+
summaryIndexById.set(message.id, index);
|
|
470
|
+
});
|
|
471
|
+
const anchors = inject ? collectSummaryAnchors(state, indexById, summaryIndexById) : [];
|
|
472
|
+
return stripOrphanedReasoning(
|
|
473
|
+
stripOrphanedToolResults(
|
|
474
|
+
stripOrphanedToolCalls(
|
|
475
|
+
rebuildMessages(messages, covered, firstUserIndex, anchors)
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
function collectSummaryAnchors(state, indexById, summaryIndexById) {
|
|
481
|
+
const anchors = [];
|
|
482
|
+
for (const block of activeBlocks(state)) {
|
|
483
|
+
const existingIndex = summaryIndexById.get(summaryMessageId(block.blockId));
|
|
484
|
+
if (existingIndex !== void 0) {
|
|
485
|
+
anchors.push({
|
|
486
|
+
blockId: block.blockId,
|
|
487
|
+
summary: block.summary,
|
|
488
|
+
topic: block.topic,
|
|
489
|
+
insertAt: existingIndex
|
|
490
|
+
});
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
let earliest = null;
|
|
494
|
+
for (const id of block.effectiveMessageIds) {
|
|
495
|
+
const index = indexById.get(id);
|
|
496
|
+
if (index !== void 0 && (earliest === null || index < earliest)) {
|
|
497
|
+
earliest = index;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
anchors.push({
|
|
501
|
+
blockId: block.blockId,
|
|
502
|
+
summary: block.summary,
|
|
503
|
+
topic: block.topic,
|
|
504
|
+
insertAt: earliest ?? 0
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
anchors.sort((left, right) => left.insertAt - right.insertAt);
|
|
508
|
+
return anchors;
|
|
509
|
+
}
|
|
510
|
+
function rebuildMessages(messages, covered, firstUserIndex, anchors) {
|
|
511
|
+
const result = [];
|
|
512
|
+
const pending = [...anchors];
|
|
513
|
+
const anchoredSummaryIds = new Set(
|
|
514
|
+
anchors.map((anchor) => summaryMessageId(anchor.blockId))
|
|
515
|
+
);
|
|
516
|
+
for (let index = 0; index < messages.length; index++) {
|
|
517
|
+
while (pending.length > 0 && pending[0].insertAt === index) {
|
|
518
|
+
result.push(renderSummary(pending.shift()));
|
|
519
|
+
}
|
|
520
|
+
if (index === firstUserIndex && firstUserIndex >= 0) {
|
|
521
|
+
result.push(messages[index]);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (covered.has(messages[index].id)) continue;
|
|
525
|
+
if (isRenderedSummaryMessage(messages[index]) && anchoredSummaryIds.has(messages[index].id))
|
|
526
|
+
continue;
|
|
527
|
+
result.push(messages[index]);
|
|
528
|
+
}
|
|
529
|
+
while (pending.length > 0) {
|
|
530
|
+
result.push(renderSummary(pending.shift()));
|
|
531
|
+
}
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
function renderSummary(anchor) {
|
|
535
|
+
const body = anchor.summary.trim();
|
|
536
|
+
const topicLine = anchor.topic ? `${SUMMARY_HEADER} \u2014 ${anchor.topic}` : SUMMARY_HEADER;
|
|
537
|
+
const text = body.length === 0 ? topicLine : `${topicLine}
|
|
538
|
+
${body}`;
|
|
539
|
+
return {
|
|
540
|
+
id: summaryMessageId(anchor.blockId),
|
|
541
|
+
role: "system",
|
|
542
|
+
contentType: "text",
|
|
543
|
+
text
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function stripOrphanedToolResults(messages) {
|
|
547
|
+
const knownCallIds = /* @__PURE__ */ new Set();
|
|
548
|
+
for (const m of messages) {
|
|
549
|
+
if (m.contentType === "tool-call" && m.toolCallId) {
|
|
550
|
+
knownCallIds.add(m.toolCallId);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return messages.filter(
|
|
554
|
+
(m) => m.contentType !== "tool-result" || !m.toolCallId || knownCallIds.has(m.toolCallId)
|
|
555
|
+
);
|
|
205
556
|
}
|
|
206
557
|
function stripOrphanedToolCalls(messages) {
|
|
207
558
|
const knownResultIds = /* @__PURE__ */ new Set();
|
|
@@ -275,9 +626,7 @@ function syncBlocks(messages, state) {
|
|
|
275
626
|
continue;
|
|
276
627
|
}
|
|
277
628
|
block.active = true;
|
|
278
|
-
const stillPresent = block.effectiveMessageIds.some(
|
|
279
|
-
(id) => presentIds.has(id)
|
|
280
|
-
);
|
|
629
|
+
const stillPresent = block.effectiveMessageIds.some((id) => presentIds.has(id)) || presentIds.has(summaryMessageId(block.blockId));
|
|
281
630
|
if (!stillPresent) {
|
|
282
631
|
block.active = false;
|
|
283
632
|
deactivated.push(block.blockId);
|
|
@@ -285,13 +634,6 @@ function syncBlocks(messages, state) {
|
|
|
285
634
|
}
|
|
286
635
|
return { state: result, deactivated };
|
|
287
636
|
}
|
|
288
|
-
var require2 = createRequire(import.meta.url);
|
|
289
|
-
function defaultCountTokens(text) {
|
|
290
|
-
if (!text) return 0;
|
|
291
|
-
const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
|
|
292
|
-
const cjkCount = cjk?.length ?? 0;
|
|
293
|
-
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
294
|
-
}
|
|
295
637
|
function defaultConfig(modelContextLimit, overrides = {}) {
|
|
296
638
|
const base = {
|
|
297
639
|
tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },
|
|
@@ -397,14 +739,24 @@ function resolveBoundaries(input) {
|
|
|
397
739
|
`Invalid boundary ref(s): startId="${input.startRef}", endId="${input.endRef}". Use mNNNNN or bN.`
|
|
398
740
|
);
|
|
399
741
|
}
|
|
400
|
-
const
|
|
742
|
+
const indexByMessageId = /* @__PURE__ */ new Map();
|
|
401
743
|
input.messages.forEach(
|
|
402
|
-
(message, index) =>
|
|
744
|
+
(message, index) => indexByMessageId.set(message.id, index)
|
|
403
745
|
);
|
|
404
746
|
let snappedBoundaries = [];
|
|
405
|
-
const startAnchor = resolveAnchorIndex(
|
|
747
|
+
const startAnchor = resolveAnchorIndex(
|
|
748
|
+
start,
|
|
749
|
+
input.state,
|
|
750
|
+
indexByMessageId,
|
|
751
|
+
"start"
|
|
752
|
+
);
|
|
406
753
|
if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
|
|
407
|
-
const endAnchor = resolveAnchorIndex(
|
|
754
|
+
const endAnchor = resolveAnchorIndex(
|
|
755
|
+
end,
|
|
756
|
+
input.state,
|
|
757
|
+
indexByMessageId,
|
|
758
|
+
"end"
|
|
759
|
+
);
|
|
408
760
|
if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
|
|
409
761
|
let startIndex = startAnchor.index;
|
|
410
762
|
let endIndex = endAnchor.index;
|
|
@@ -414,14 +766,14 @@ function resolveBoundaries(input) {
|
|
|
414
766
|
const messageIds = [];
|
|
415
767
|
for (let index = startIndex; index <= endIndex; index++) {
|
|
416
768
|
const message = input.messages[index];
|
|
417
|
-
if (message
|
|
769
|
+
if (message && !isRenderedSummaryMessage(message))
|
|
770
|
+
messageIds.push(message.id);
|
|
418
771
|
}
|
|
419
772
|
const boundaryKind = start.kind === "block" || end.kind === "block" ? "block" : "message";
|
|
420
773
|
const nestedBlockIds = [];
|
|
421
774
|
const nestedSeen = /* @__PURE__ */ new Set();
|
|
422
775
|
for (const block of activeBlocks(input.state)) {
|
|
423
|
-
|
|
424
|
-
if (anchor !== null && anchor >= startIndex && anchor <= endIndex) {
|
|
776
|
+
if (blockVisibleInRange(block, indexByMessageId, startIndex, endIndex)) {
|
|
425
777
|
if (!nestedSeen.has(block.blockId)) {
|
|
426
778
|
nestedSeen.add(block.blockId);
|
|
427
779
|
nestedBlockIds.push(block.blockId);
|
|
@@ -439,7 +791,7 @@ function resolveBoundaries(input) {
|
|
|
439
791
|
snappedBoundaries
|
|
440
792
|
};
|
|
441
793
|
}
|
|
442
|
-
function resolveAnchorIndex(boundary, state,
|
|
794
|
+
function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
|
|
443
795
|
const label = endpoint === "start" ? "startId" : "endId";
|
|
444
796
|
if (boundary.kind === "message") {
|
|
445
797
|
const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
|
|
@@ -450,15 +802,15 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
|
|
|
450
802
|
`${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
|
|
451
803
|
);
|
|
452
804
|
}
|
|
453
|
-
const index =
|
|
805
|
+
const index = indexByMessageId.get(rawId);
|
|
454
806
|
if (index !== void 0) {
|
|
455
807
|
return { index, snapped: null };
|
|
456
808
|
}
|
|
457
|
-
const owner2 = activeOwnerAnchor(state, [rawId],
|
|
809
|
+
const owner2 = activeOwnerAnchor(state, [rawId], indexByMessageId);
|
|
458
810
|
if (owner2 !== null) {
|
|
459
811
|
return {
|
|
460
812
|
index: owner2,
|
|
461
|
-
snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to
|
|
813
|
+
snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
|
|
462
814
|
};
|
|
463
815
|
}
|
|
464
816
|
throw new BoundaryNotFoundError(
|
|
@@ -476,12 +828,16 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
|
|
|
476
828
|
);
|
|
477
829
|
}
|
|
478
830
|
if (block.active) {
|
|
479
|
-
const anchor =
|
|
831
|
+
const anchor = visibleBlockAnchor(block, indexByMessageId);
|
|
480
832
|
if (anchor !== null) {
|
|
481
833
|
return { index: anchor, snapped: null };
|
|
482
834
|
}
|
|
483
835
|
}
|
|
484
|
-
const owner = activeOwnerAnchor(
|
|
836
|
+
const owner = activeOwnerAnchor(
|
|
837
|
+
state,
|
|
838
|
+
block.effectiveMessageIds,
|
|
839
|
+
indexByMessageId
|
|
840
|
+
);
|
|
485
841
|
if (owner !== null) {
|
|
486
842
|
return {
|
|
487
843
|
index: owner,
|
|
@@ -498,31 +854,64 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
|
|
|
498
854
|
throw new BoundaryNotFoundError(
|
|
499
855
|
"consumed",
|
|
500
856
|
endpoint,
|
|
501
|
-
`${label}="b${boundary.numericId}"
|
|
857
|
+
`${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is visible in the current context \u2014 run acp_status to verify.`
|
|
502
858
|
);
|
|
503
859
|
}
|
|
504
|
-
function activeOwnerAnchor(state, ownedIds,
|
|
860
|
+
function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
|
|
505
861
|
if (ownedIds.length === 0) return null;
|
|
506
862
|
const owned = new Set(ownedIds);
|
|
507
863
|
let best = null;
|
|
508
864
|
for (const block of state.blocks) {
|
|
509
865
|
if (!block.active) continue;
|
|
510
|
-
const
|
|
866
|
+
const inherited = inheritedContentIds(state, block);
|
|
867
|
+
let ownsInherited = false;
|
|
868
|
+
for (const id of owned) {
|
|
869
|
+
if (inherited.has(id)) {
|
|
870
|
+
ownsInherited = true;
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (!ownsInherited) continue;
|
|
875
|
+
const anchor = visibleBlockAnchor(block, indexByMessageId);
|
|
511
876
|
if (anchor === null) continue;
|
|
512
|
-
|
|
513
|
-
if (ownsContent && (best === null || anchor < best)) {
|
|
877
|
+
if (best === null || anchor < best) {
|
|
514
878
|
best = anchor;
|
|
515
879
|
}
|
|
516
880
|
}
|
|
517
881
|
return best;
|
|
518
882
|
}
|
|
883
|
+
function inheritedContentIds(state, block) {
|
|
884
|
+
const ids = /* @__PURE__ */ new Set();
|
|
885
|
+
for (const childId of block.directBlockIds) {
|
|
886
|
+
const child = blockById(state, childId);
|
|
887
|
+
if (!child) continue;
|
|
888
|
+
for (const id of child.effectiveMessageIds) ids.add(id);
|
|
889
|
+
}
|
|
890
|
+
return ids;
|
|
891
|
+
}
|
|
519
892
|
function formatPaddedRef(index) {
|
|
520
893
|
return `m${String(index).padStart(5, "0")}`;
|
|
521
894
|
}
|
|
522
|
-
function
|
|
895
|
+
function visibleBlockAnchor(block, indexByMessageId) {
|
|
896
|
+
const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
|
|
897
|
+
if (summaryIndex !== void 0) return summaryIndex;
|
|
898
|
+
return earliestIndexOfIds(block.effectiveMessageIds, indexByMessageId);
|
|
899
|
+
}
|
|
900
|
+
function blockVisibleInRange(block, indexByMessageId, startIndex, endIndex) {
|
|
901
|
+
const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
|
|
902
|
+
if (summaryIndex !== void 0 && summaryIndex >= startIndex && summaryIndex <= endIndex) {
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
const rawIndex = earliestIndexOfIds(
|
|
906
|
+
block.effectiveMessageIds,
|
|
907
|
+
indexByMessageId
|
|
908
|
+
);
|
|
909
|
+
return rawIndex !== null && rawIndex >= startIndex && rawIndex <= endIndex;
|
|
910
|
+
}
|
|
911
|
+
function earliestIndexOfIds(ids, indexByMessageId) {
|
|
523
912
|
let earliest = null;
|
|
524
913
|
for (const id of ids) {
|
|
525
|
-
const index =
|
|
914
|
+
const index = indexByMessageId.get(id);
|
|
526
915
|
if (index !== void 0 && (earliest === null || index < earliest)) {
|
|
527
916
|
earliest = index;
|
|
528
917
|
}
|
|
@@ -580,7 +969,7 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
|
|
|
580
969
|
);
|
|
581
970
|
return { messages: updated, truncatedCount, savedTokens };
|
|
582
971
|
}
|
|
583
|
-
var KEEP_LAST_ORPHANED =
|
|
972
|
+
var KEEP_LAST_ORPHANED = 2;
|
|
584
973
|
function rangeKey(startRef, endRef) {
|
|
585
974
|
return `${startRef}::${endRef}`;
|
|
586
975
|
}
|
|
@@ -1148,7 +1537,12 @@ function createCore(ports = {}) {
|
|
|
1148
1537
|
let tokensCompressed = 0;
|
|
1149
1538
|
const errors = [];
|
|
1150
1539
|
const warnings = [];
|
|
1151
|
-
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(
|
|
1540
|
+
const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(
|
|
1541
|
+
input.messages,
|
|
1542
|
+
input.state,
|
|
1543
|
+
input.config,
|
|
1544
|
+
countTokens
|
|
1545
|
+
);
|
|
1152
1546
|
const preExistingCoverage = collectCoverage(state);
|
|
1153
1547
|
const classifications = /* @__PURE__ */ new Map();
|
|
1154
1548
|
const classificationErrors = [];
|
|
@@ -1179,7 +1573,10 @@ function createCore(ports = {}) {
|
|
|
1179
1573
|
error: error instanceof Error ? error : new Error(String(error))
|
|
1180
1574
|
});
|
|
1181
1575
|
classificationErrors.push(
|
|
1182
|
-
rangeError(
|
|
1576
|
+
rangeError(
|
|
1577
|
+
spec,
|
|
1578
|
+
error instanceof Error ? error.message : String(error)
|
|
1579
|
+
)
|
|
1183
1580
|
);
|
|
1184
1581
|
}
|
|
1185
1582
|
}
|
|
@@ -1190,32 +1587,27 @@ function createCore(ports = {}) {
|
|
|
1190
1587
|
if (resolution.status === "ok") resolvableCount++;
|
|
1191
1588
|
else if (resolution.status === "unknown") unknownCount++;
|
|
1192
1589
|
}
|
|
1193
|
-
const
|
|
1590
|
+
const rangeSpans = [];
|
|
1194
1591
|
for (const [spec, resolution] of classifications) {
|
|
1195
1592
|
if (resolution.status !== "ok") continue;
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
const bMin = b.indices.length > 0 ? Math.min(...b.indices) : Infinity;
|
|
1204
|
-
return aMin - bMin;
|
|
1205
|
-
});
|
|
1593
|
+
rangeSpans.push({
|
|
1594
|
+
spec,
|
|
1595
|
+
start: resolution.resolved.startIndex,
|
|
1596
|
+
end: resolution.resolved.endIndex
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
const sortedRanges = [...rangeSpans].sort((a, b) => a.start - b.start);
|
|
1206
1600
|
const skipSpecs = /* @__PURE__ */ new Set();
|
|
1207
1601
|
let acceptedMaxIndex = -1;
|
|
1208
1602
|
for (const entry of sortedRanges) {
|
|
1209
|
-
|
|
1210
|
-
const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;
|
|
1211
|
-
if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {
|
|
1603
|
+
if (entry.start <= acceptedMaxIndex) {
|
|
1212
1604
|
skipSpecs.add(entry.spec);
|
|
1213
1605
|
warnings.push(
|
|
1214
1606
|
`Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) \u2014 overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`
|
|
1215
1607
|
);
|
|
1216
1608
|
continue;
|
|
1217
1609
|
}
|
|
1218
|
-
if (
|
|
1610
|
+
if (entry.end > acceptedMaxIndex) acceptedMaxIndex = entry.end;
|
|
1219
1611
|
}
|
|
1220
1612
|
if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
|
|
1221
1613
|
let totalRangeChars = 0;
|
|
@@ -1278,7 +1670,12 @@ function createCore(ports = {}) {
|
|
|
1278
1670
|
tokensCompressed += outcome.tokens;
|
|
1279
1671
|
warnings.push(...outcome.warnings);
|
|
1280
1672
|
} catch (error) {
|
|
1281
|
-
errors.push(
|
|
1673
|
+
errors.push(
|
|
1674
|
+
rangeError(
|
|
1675
|
+
spec,
|
|
1676
|
+
error instanceof Error ? error.message : String(error)
|
|
1677
|
+
)
|
|
1678
|
+
);
|
|
1282
1679
|
}
|
|
1283
1680
|
}
|
|
1284
1681
|
state.stats.compressionCount += blocksCreated;
|
|
@@ -1288,12 +1685,17 @@ function createCore(ports = {}) {
|
|
|
1288
1685
|
state.nudge.lastNudgeShownTokens = 0;
|
|
1289
1686
|
state.nudge.lastShownByTier = {};
|
|
1290
1687
|
}
|
|
1291
|
-
return {
|
|
1688
|
+
return {
|
|
1689
|
+
state,
|
|
1690
|
+
result: { blocksCreated, tokensCompressed, errors, warnings }
|
|
1691
|
+
};
|
|
1292
1692
|
}
|
|
1293
1693
|
function processTurn(input) {
|
|
1294
1694
|
const configErrors = validateConfig(input.config);
|
|
1295
1695
|
if (configErrors.length > 0) {
|
|
1296
|
-
console.warn(
|
|
1696
|
+
console.warn(
|
|
1697
|
+
`[acp-kernel] Config validation warnings: ${configErrors.join("; ")}. Thresholds may not fire correctly.`
|
|
1698
|
+
);
|
|
1297
1699
|
}
|
|
1298
1700
|
const ctx = {
|
|
1299
1701
|
config: input.config,
|
|
@@ -1353,7 +1755,14 @@ function createCore(ports = {}) {
|
|
|
1353
1755
|
if (strategy === "none") return base;
|
|
1354
1756
|
return [...base, createRenderRefsNode(strategy)];
|
|
1355
1757
|
}
|
|
1356
|
-
return {
|
|
1758
|
+
return {
|
|
1759
|
+
processTurn,
|
|
1760
|
+
applyCompression,
|
|
1761
|
+
defaultNodes,
|
|
1762
|
+
decompress,
|
|
1763
|
+
search,
|
|
1764
|
+
status
|
|
1765
|
+
};
|
|
1357
1766
|
}
|
|
1358
1767
|
var assignRefsNode = {
|
|
1359
1768
|
name: "assign-refs",
|
|
@@ -1453,7 +1862,10 @@ var nudgeNode = {
|
|
|
1453
1862
|
if (nudge.shouldInject) {
|
|
1454
1863
|
stamped.lastNudgeShownTokens = ctx.tokenCount;
|
|
1455
1864
|
if (nudge.tier !== null) {
|
|
1456
|
-
stamped.lastShownByTier = {
|
|
1865
|
+
stamped.lastShownByTier = {
|
|
1866
|
+
...stamped.lastShownByTier,
|
|
1867
|
+
[nudge.tier]: ctx.tokenCount
|
|
1868
|
+
};
|
|
1457
1869
|
}
|
|
1458
1870
|
}
|
|
1459
1871
|
return {
|
|
@@ -1493,17 +1905,16 @@ function applySingleRange(input) {
|
|
|
1493
1905
|
const rangeMessageIds = applyPairBoundaryAdjustments(
|
|
1494
1906
|
resolved,
|
|
1495
1907
|
input.messages
|
|
1496
|
-
);
|
|
1908
|
+
).filter((id) => !isSummaryMessageId(id));
|
|
1497
1909
|
if (rangeMessageIds.length > resolved.messageIds.length) {
|
|
1498
|
-
const
|
|
1499
|
-
input.messages.forEach((m, i) =>
|
|
1500
|
-
const adjustedStart =
|
|
1501
|
-
const adjustedEnd =
|
|
1910
|
+
const indexByMessageId = /* @__PURE__ */ new Map();
|
|
1911
|
+
input.messages.forEach((m, i) => indexByMessageId.set(m.id, i));
|
|
1912
|
+
const adjustedStart = rangeMessageIds.length > 0 ? indexByMessageId.get(rangeMessageIds[0]) ?? resolved.startIndex : resolved.startIndex;
|
|
1913
|
+
const adjustedEnd = rangeMessageIds.length > 0 ? indexByMessageId.get(rangeMessageIds[rangeMessageIds.length - 1]) ?? resolved.endIndex : resolved.endIndex;
|
|
1502
1914
|
const nestedSeen = new Set(resolved.nestedBlockIds);
|
|
1503
1915
|
for (const block2 of activeBlocks(input.state)) {
|
|
1504
1916
|
if (nestedSeen.has(block2.blockId)) continue;
|
|
1505
|
-
|
|
1506
|
-
if (anchor !== null && anchor >= adjustedStart && anchor <= adjustedEnd) {
|
|
1917
|
+
if (blockVisibleInRange(block2, indexByMessageId, adjustedStart, adjustedEnd)) {
|
|
1507
1918
|
nestedSeen.add(block2.blockId);
|
|
1508
1919
|
resolved.nestedBlockIds.push(block2.blockId);
|
|
1509
1920
|
}
|
|
@@ -1566,6 +1977,15 @@ function applySingleRange(input) {
|
|
|
1566
1977
|
)} from compression range (recent/last-user zone).`
|
|
1567
1978
|
);
|
|
1568
1979
|
}
|
|
1980
|
+
if (!isBlockBoundary && filteredIds.length === 0 && consumedBlockIds.length > 0) {
|
|
1981
|
+
const first = consumedBlockIds[0];
|
|
1982
|
+
const last = consumedBlockIds[consumedBlockIds.length - 1];
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`Range ${input.spec.startRef}..${input.spec.endRef} contains no new compressible messages \u2014 every message in it is already covered by active block(s) ${consumedBlockIds.join(
|
|
1985
|
+
", "
|
|
1986
|
+
)}. Nothing was compressed. To rewrite or merge those blocks, reference them by block ID (${first}..${last}); otherwise run acp_status and compress a range it reports as compressible.`
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1569
1989
|
validateCompressionRange(input, filteredIds, consumedBlockIds.length);
|
|
1570
1990
|
let compressedTokens = 0;
|
|
1571
1991
|
for (const id of filteredIds) {
|
|
@@ -1718,12 +2138,21 @@ function pendingByTier(state, recommendation, countTokens, minCompressRange) {
|
|
|
1718
2138
|
const out = {};
|
|
1719
2139
|
const merged = recommendation?.recommendedRanges ?? [];
|
|
1720
2140
|
const effective = minCompressRange > 0 ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange) : merged;
|
|
1721
|
-
out[1] = {
|
|
2141
|
+
out[1] = {
|
|
2142
|
+
pending: effective.reduce((s, r) => s + r.tokens, 0),
|
|
2143
|
+
targetBlocks: []
|
|
2144
|
+
};
|
|
1722
2145
|
const active = activeBlocks(state);
|
|
1723
2146
|
const t1 = active.filter((b) => b.tier === 1);
|
|
1724
2147
|
const t2 = active.filter((b) => b.tier === 2);
|
|
1725
|
-
out[2] = {
|
|
1726
|
-
|
|
2148
|
+
out[2] = {
|
|
2149
|
+
pending: t1.reduce((s, b) => s + countTokens(b.summary), 0),
|
|
2150
|
+
targetBlocks: t1
|
|
2151
|
+
};
|
|
2152
|
+
out[3] = {
|
|
2153
|
+
pending: t2.reduce((s, b) => s + countTokens(b.summary), 0),
|
|
2154
|
+
targetBlocks: t2
|
|
2155
|
+
};
|
|
1727
2156
|
return out;
|
|
1728
2157
|
}
|
|
1729
2158
|
function decideNudge(input) {
|
|
@@ -1811,18 +2240,35 @@ function decideNudge(input) {
|
|
|
1811
2240
|
const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
|
|
1812
2241
|
const ready = eligible.filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens).map((t) => `T${t} ${tiers[t].pending}`);
|
|
1813
2242
|
const readyHint = ready.length > 0 ? `, ready: ${ready.join(", ")}` : "";
|
|
1814
|
-
const blocked = eligible.filter(
|
|
2243
|
+
const blocked = eligible.filter(
|
|
2244
|
+
(t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor
|
|
2245
|
+
).map((t) => `T${t} (cadence)`);
|
|
1815
2246
|
const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(", ")}` : "";
|
|
1816
|
-
const maxPending = Math.max(
|
|
2247
|
+
const maxPending = Math.max(
|
|
2248
|
+
0,
|
|
2249
|
+
...Object.values(tiers).map((t) => t.pending)
|
|
2250
|
+
);
|
|
1817
2251
|
const pendingShort = maxPending < nudgeGrowthTokens;
|
|
1818
2252
|
const growthShort = growthSinceReference < growthFloor;
|
|
1819
2253
|
const parts = [];
|
|
1820
|
-
if (pendingShort)
|
|
1821
|
-
|
|
1822
|
-
|
|
2254
|
+
if (pendingShort)
|
|
2255
|
+
parts.push(
|
|
2256
|
+
`max compressible ${maxPending} < threshold ${nudgeGrowthTokens}`
|
|
2257
|
+
);
|
|
2258
|
+
if (growthShort)
|
|
2259
|
+
parts.push(`growth ${growthSinceReference} < floor ${growthFloor}`);
|
|
2260
|
+
if (parts.length === 0)
|
|
2261
|
+
parts.push(
|
|
2262
|
+
`max compressible ${maxPending}, growth ${growthSinceReference}`
|
|
2263
|
+
);
|
|
1823
2264
|
reason = `${parts.join("; ")}${readyHint}${blockedHint}`;
|
|
1824
2265
|
}
|
|
1825
|
-
const ctxBreakdown = computeContextBreakdown(
|
|
2266
|
+
const ctxBreakdown = computeContextBreakdown(
|
|
2267
|
+
input.messages,
|
|
2268
|
+
tokenCount,
|
|
2269
|
+
growthSinceReference,
|
|
2270
|
+
countTokens
|
|
2271
|
+
);
|
|
1826
2272
|
return {
|
|
1827
2273
|
shouldInject,
|
|
1828
2274
|
reason,
|
|
@@ -1908,433 +2354,127 @@ function countOccurrences(haystack, needle) {
|
|
|
1908
2354
|
}
|
|
1909
2355
|
return count;
|
|
1910
2356
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
2. Decisions and rationale.
|
|
1947
|
-
3. Exact technical artifacts: paths, signatures, errors, values.
|
|
1948
|
-
4. Conclusions and key findings.
|
|
1949
|
-
5. Lessons learned: what failed and why.
|
|
1950
|
-
|
|
1951
|
-
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
1952
|
-
var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
1953
|
-
|
|
1954
|
-
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
1955
|
-
|
|
1956
|
-
KEEP \u2014 these are the only things that survive distillation:
|
|
1957
|
-
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
1958
|
-
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
1959
|
-
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
1960
|
-
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
1961
|
-
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
1962
|
-
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
1963
|
-
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
1964
|
-
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
1965
|
-
|
|
1966
|
-
DROP \u2014 these were useful during the work but are no longer needed:
|
|
1967
|
-
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
1968
|
-
- Build/deploy process details, test execution steps.
|
|
1969
|
-
- Review process details (who reviewed, what rounds, test counts).
|
|
1970
|
-
- Verbose logs, command output, intermediate debugging steps.
|
|
1971
|
-
|
|
1972
|
-
FORMAT:
|
|
1973
|
-
- Start each distilled block with a source header line:
|
|
1974
|
-
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
1975
|
-
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
1976
|
-
- 3-5 bullet points per source block, each a self-contained fact.
|
|
1977
|
-
- Dense, scannable \u2014 no narrative prose.
|
|
1978
|
-
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
1979
|
-
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
1980
|
-
|
|
1981
|
-
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
|
|
1982
|
-
var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
1983
|
-
|
|
1984
|
-
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
1985
|
-
|
|
1986
|
-
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
1987
|
-
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
1988
|
-
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
1989
|
-
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
1990
|
-
4. Critical constraints ("must support Node 22").
|
|
1991
|
-
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
1992
|
-
|
|
1993
|
-
FORMAT:
|
|
1994
|
-
- Start with a source header line:
|
|
1995
|
-
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
1996
|
-
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
1997
|
-
- No explanations, no rationale, no process \u2014 just the fact.
|
|
1998
|
-
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
1999
|
-
- Merge related facts from different source blocks if they concern the same topic.
|
|
2000
|
-
|
|
2001
|
-
EXAMPLES:
|
|
2002
|
-
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
2003
|
-
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
2004
|
-
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
2005
|
-
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
2006
|
-
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
2007
|
-
|
|
2008
|
-
DROP:
|
|
2009
|
-
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
2010
|
-
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
2011
|
-
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
2012
|
-
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
2013
|
-
|
|
2014
|
-
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
2015
|
-
var defaultPrompts = Object.freeze({
|
|
2016
|
-
compressPhilosophy: COMPRESS_PHILOSOPHY,
|
|
2017
|
-
howToCompressRules: HOW_TO_COMPRESS_RULES,
|
|
2018
|
-
tier2DistillRules: TIER2_DISTILL_RULES,
|
|
2019
|
-
tier3CondenseRules: TIER3_CONDENSE_RULES
|
|
2020
|
-
});
|
|
2021
|
-
function resolvePrompts(overrides, options = {}) {
|
|
2022
|
-
const clean = {};
|
|
2023
|
-
if (overrides) {
|
|
2024
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
2025
|
-
if (typeof value === "string") {
|
|
2026
|
-
clean[key] = value;
|
|
2027
|
-
}
|
|
2357
|
+
function parseBlockIdArg(arg) {
|
|
2358
|
+
const normalized = arg.trim().toLowerCase();
|
|
2359
|
+
const refMatch = /^b0*(\d+)$/.exec(normalized);
|
|
2360
|
+
if (refMatch && refMatch[1] !== void 0) return `b${refMatch[1]}`;
|
|
2361
|
+
const numMatch = /^(\d+)$/.exec(normalized);
|
|
2362
|
+
if (numMatch && numMatch[1] !== void 0) return `b${numMatch[1]}`;
|
|
2363
|
+
return null;
|
|
2364
|
+
}
|
|
2365
|
+
function collectBlockContent(state, block, messages, options = {}) {
|
|
2366
|
+
const full = options.full ?? false;
|
|
2367
|
+
const targetIds = new Set(block.effectiveMessageIds);
|
|
2368
|
+
if (full) {
|
|
2369
|
+
const msgs = messages.filter((m) => targetIds.has(m.id));
|
|
2370
|
+
if (msgs.length === 0) return { text: "", count: 0 };
|
|
2371
|
+
return { text: msgs.map(formatMessage).join("\n\n"), count: msgs.length };
|
|
2372
|
+
}
|
|
2373
|
+
const nestedChildren = [];
|
|
2374
|
+
const nestedCovered = /* @__PURE__ */ new Set();
|
|
2375
|
+
for (const childId of block.directBlockIds) {
|
|
2376
|
+
const child = state.blocks.find((b) => b.blockId === childId);
|
|
2377
|
+
if (!child?.active) continue;
|
|
2378
|
+
nestedChildren.push(child);
|
|
2379
|
+
for (const id of child.effectiveMessageIds) nestedCovered.add(id);
|
|
2380
|
+
}
|
|
2381
|
+
const parts = [];
|
|
2382
|
+
for (const child of nestedChildren) {
|
|
2383
|
+
const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;
|
|
2384
|
+
parts.push(`${SUMMARY_HEADER} \u2014 ${label}
|
|
2385
|
+
${child.summary}`);
|
|
2386
|
+
}
|
|
2387
|
+
let directCount = 0;
|
|
2388
|
+
for (const m of messages) {
|
|
2389
|
+
if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {
|
|
2390
|
+
parts.push(formatMessage(m));
|
|
2391
|
+
directCount++;
|
|
2028
2392
|
}
|
|
2029
2393
|
}
|
|
2030
|
-
const
|
|
2031
|
-
if (
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2394
|
+
const count = directCount + nestedChildren.length;
|
|
2395
|
+
if (count === 0) return { text: "", count: 0 };
|
|
2396
|
+
return { text: parts.join("\n\n"), count };
|
|
2397
|
+
}
|
|
2398
|
+
function formatMessage(message) {
|
|
2399
|
+
const text = message.text ?? "";
|
|
2400
|
+
if (message.toolName && message.contentType !== "text") {
|
|
2401
|
+
return `[${message.role} \u2022 ${message.toolName}]
|
|
2402
|
+
${text}`;
|
|
2035
2403
|
}
|
|
2036
|
-
return {
|
|
2404
|
+
return `[${message.role}]
|
|
2405
|
+
${text}`;
|
|
2037
2406
|
}
|
|
2038
|
-
function
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
${prompts.compressPhilosophy}`;
|
|
2407
|
+
function formatTokens2(n) {
|
|
2408
|
+
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
2409
|
+
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
2042
2410
|
}
|
|
2043
|
-
function
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
${prompts.compressPhilosophy}`;
|
|
2411
|
+
function pct(n, total) {
|
|
2412
|
+
if (n <= 0 || total <= 0) return 0;
|
|
2413
|
+
return Math.max(1, Math.round(n / total * 100));
|
|
2047
2414
|
}
|
|
2048
|
-
function
|
|
2049
|
-
|
|
2050
|
-
return
|
|
2415
|
+
function numericPart2(blockId) {
|
|
2416
|
+
const match = /^b(\d+)$/.exec(blockId);
|
|
2417
|
+
return match && match[1] !== void 0 ? Number(match[1]) : 0;
|
|
2051
2418
|
}
|
|
2052
|
-
function
|
|
2053
|
-
|
|
2419
|
+
function summaryTokensOf(block, countTokens) {
|
|
2420
|
+
return countTokens(block.summary);
|
|
2421
|
+
}
|
|
2422
|
+
function effectiveCompressedTokens(block, _state, _countTokens) {
|
|
2423
|
+
return block.compressedTokens;
|
|
2424
|
+
}
|
|
2425
|
+
function tierLabel(block) {
|
|
2426
|
+
return `T${block.tier}`;
|
|
2427
|
+
}
|
|
2428
|
+
function tierBreakdown(blocks, countTokens) {
|
|
2429
|
+
const tierTokens = {};
|
|
2430
|
+
for (const block of blocks) {
|
|
2431
|
+
tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);
|
|
2432
|
+
}
|
|
2433
|
+
const tiers = Object.keys(tierTokens).map(Number);
|
|
2434
|
+
if (tiers.length <= 1) return null;
|
|
2054
2435
|
const parts = [];
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);
|
|
2060
|
-
const growth = bd.growth > 0 ? `
|
|
2061
|
-
+${formatK(bd.growth)} since last nudge` : "";
|
|
2062
|
-
return `Context breakdown: ${parts.join(" | ")}${growth}`;
|
|
2436
|
+
for (const tier of [1, 2, 3]) {
|
|
2437
|
+
if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])}`);
|
|
2438
|
+
}
|
|
2439
|
+
return parts.join(" | ");
|
|
2063
2440
|
}
|
|
2064
|
-
function
|
|
2065
|
-
|
|
2066
|
-
|
|
2441
|
+
function collectVisible(messages, state, countTokens) {
|
|
2442
|
+
const coveredIds = /* @__PURE__ */ new Set();
|
|
2443
|
+
for (const block of state.blocks) {
|
|
2444
|
+
if (!block.active) continue;
|
|
2445
|
+
for (const id of block.effectiveMessageIds) coveredIds.add(id);
|
|
2067
2446
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2447
|
+
let summaryTokens = 0;
|
|
2448
|
+
for (const block of state.blocks) {
|
|
2449
|
+
if (block.active) summaryTokens += summaryTokensOf(block, countTokens);
|
|
2450
|
+
}
|
|
2451
|
+
const visible = [];
|
|
2452
|
+
messages.forEach((message, index) => {
|
|
2453
|
+
if (coveredIds.has(message.id)) return;
|
|
2454
|
+
const ref = refForRaw(state.messageRefs, message.id);
|
|
2455
|
+
if (!ref) return;
|
|
2456
|
+
const tokens = countTokens(message.text ?? "");
|
|
2457
|
+
const tool = message.toolName ?? "text";
|
|
2458
|
+
if (tokens > 0) visible.push({ ref, tokens, tool, index });
|
|
2072
2459
|
});
|
|
2073
|
-
return
|
|
2074
|
-
${lines.join("\n")}`;
|
|
2460
|
+
return { visible, summaryTokens };
|
|
2075
2461
|
}
|
|
2076
|
-
function
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
for (const r of compressible) {
|
|
2086
|
-
entries.push({
|
|
2087
|
-
startRef: r.startRef,
|
|
2088
|
-
endRef: r.endRef,
|
|
2089
|
-
startNum: refNum2(r.startRef),
|
|
2090
|
-
endNum: refNum2(r.endRef),
|
|
2091
|
-
count: r.count,
|
|
2092
|
-
tokens: r.tokens,
|
|
2093
|
-
toolPct: r.toolPct,
|
|
2094
|
-
textPct: r.textPct,
|
|
2095
|
-
compressibleTokens: r.tokens,
|
|
2096
|
-
compressibleCount: r.count,
|
|
2097
|
-
protectedTokens: 0,
|
|
2098
|
-
protectedCount: 0,
|
|
2099
|
-
protectedTools: [],
|
|
2100
|
-
dangerous: r.dangerous ?? false
|
|
2101
|
-
});
|
|
2462
|
+
function buildStatusReport(state, messages, countTokens, options = {}) {
|
|
2463
|
+
const scope = options.scope;
|
|
2464
|
+
const view = options.view ?? "ranges";
|
|
2465
|
+
const toolFilter = options.tool;
|
|
2466
|
+
const sort = options.sort ?? "size";
|
|
2467
|
+
const limit = options.limit ?? 30;
|
|
2468
|
+
const activeBlocks2 = state.blocks.filter((b) => b.active).sort((a, b) => numericPart2(a.blockId) - numericPart2(b.blockId));
|
|
2469
|
+
if (scope === "compressed") {
|
|
2470
|
+
return renderCompressedDrilldown(activeBlocks2, state, sort, limit, countTokens);
|
|
2102
2471
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
count: r.count,
|
|
2110
|
-
tokens: r.tokens,
|
|
2111
|
-
toolPct: 0,
|
|
2112
|
-
textPct: 0,
|
|
2113
|
-
compressibleTokens: 0,
|
|
2114
|
-
compressibleCount: 0,
|
|
2115
|
-
protectedTokens: r.tokens,
|
|
2116
|
-
protectedCount: r.count,
|
|
2117
|
-
protectedTools: [...r.tools],
|
|
2118
|
-
dangerous: false
|
|
2119
|
-
});
|
|
2120
|
-
}
|
|
2121
|
-
entries.sort((a, b) => a.startNum - b.startNum);
|
|
2122
|
-
const merged = [];
|
|
2123
|
-
for (const e of entries) {
|
|
2124
|
-
const last = merged[merged.length - 1];
|
|
2125
|
-
if (last && e.startNum <= last.endNum + 1) {
|
|
2126
|
-
last.endRef = e.endRef;
|
|
2127
|
-
last.endNum = Math.max(last.endNum, e.endNum);
|
|
2128
|
-
last.count += e.count;
|
|
2129
|
-
last.tokens += e.tokens;
|
|
2130
|
-
last.compressibleTokens += e.compressibleTokens;
|
|
2131
|
-
last.compressibleCount += e.compressibleCount;
|
|
2132
|
-
last.protectedTokens += e.protectedTokens;
|
|
2133
|
-
last.protectedCount += e.protectedCount;
|
|
2134
|
-
if (e.dangerous) last.dangerous = true;
|
|
2135
|
-
for (const t of e.protectedTools) {
|
|
2136
|
-
if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
|
|
2137
|
-
}
|
|
2138
|
-
} else {
|
|
2139
|
-
merged.push({ ...e });
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
const lines = merged.map((e) => {
|
|
2143
|
-
const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
|
|
2144
|
-
if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
|
|
2145
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
|
|
2146
|
-
}
|
|
2147
|
-
if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
|
|
2148
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
|
|
2149
|
-
}
|
|
2150
|
-
return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
|
|
2151
|
-
});
|
|
2152
|
-
return `Compressible ranges (${merged.length}, oldest first):
|
|
2153
|
-
${lines.join("\n")}`;
|
|
2154
|
-
}
|
|
2155
|
-
function renderNudgeText(decision, prompts = defaultPrompts) {
|
|
2156
|
-
const breakdownStr = formatBreakdown(decision.contextBreakdown);
|
|
2157
|
-
const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
|
|
2158
|
-
const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
|
|
2159
|
-
if (decision.tier !== null && decision.tier >= 2) {
|
|
2160
|
-
const isT2 = decision.tier === 2;
|
|
2161
|
-
const targets = decision.tierTargetBlocks ?? [];
|
|
2162
|
-
const blockList = formatTierTargetBlocks(targets);
|
|
2163
|
-
const startId = targets[0]?.blockId ?? "b1";
|
|
2164
|
-
const endId = targets[targets.length - 1]?.blockId ?? "b5";
|
|
2165
|
-
const voice = isEmergency ? "emergency" : "gentle";
|
|
2166
|
-
const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
|
|
2167
|
-
return {
|
|
2168
|
-
voice,
|
|
2169
|
-
text: [
|
|
2170
|
-
efficiencyNote(prompts),
|
|
2171
|
-
"",
|
|
2172
|
-
breakdownStr,
|
|
2173
|
-
"",
|
|
2174
|
-
triggerLine,
|
|
2175
|
-
isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
|
|
2176
|
-
blockList,
|
|
2177
|
-
`Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
|
|
2178
|
-
"",
|
|
2179
|
-
prompts.howToCompressRules,
|
|
2180
|
-
"",
|
|
2181
|
-
isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
|
|
2182
|
-
].join("\n")
|
|
2183
|
-
};
|
|
2184
|
-
}
|
|
2185
|
-
if (isEmergency) {
|
|
2186
|
-
return {
|
|
2187
|
-
voice: "emergency",
|
|
2188
|
-
text: [
|
|
2189
|
-
emergencyHeader(prompts),
|
|
2190
|
-
"",
|
|
2191
|
-
breakdownStr,
|
|
2192
|
-
"",
|
|
2193
|
-
prompts.howToCompressRules,
|
|
2194
|
-
"",
|
|
2195
|
-
`{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
|
|
2196
|
-
"Only use IDs from visible messages above. Compress older work first.",
|
|
2197
|
-
"",
|
|
2198
|
-
rangesStr
|
|
2199
|
-
].join("\n")
|
|
2200
|
-
};
|
|
2201
|
-
}
|
|
2202
|
-
return {
|
|
2203
|
-
voice: "gentle",
|
|
2204
|
-
text: [
|
|
2205
|
-
efficiencyNote(prompts),
|
|
2206
|
-
"",
|
|
2207
|
-
breakdownStr,
|
|
2208
|
-
"",
|
|
2209
|
-
prompts.howToCompressRules,
|
|
2210
|
-
"",
|
|
2211
|
-
rangesStr,
|
|
2212
|
-
"",
|
|
2213
|
-
`\u{1F4A1} Compress all ranges in one call (pass multiple content entries: \`content: [{...}, {...}]\`).`
|
|
2214
|
-
].join("\n")
|
|
2215
|
-
};
|
|
2216
|
-
}
|
|
2217
|
-
function parseBlockIdArg(arg) {
|
|
2218
|
-
const normalized = arg.trim().toLowerCase();
|
|
2219
|
-
const refMatch = /^b0*(\d+)$/.exec(normalized);
|
|
2220
|
-
if (refMatch && refMatch[1] !== void 0) return `b${refMatch[1]}`;
|
|
2221
|
-
const numMatch = /^(\d+)$/.exec(normalized);
|
|
2222
|
-
if (numMatch && numMatch[1] !== void 0) return `b${numMatch[1]}`;
|
|
2223
|
-
return null;
|
|
2224
|
-
}
|
|
2225
|
-
function collectBlockContent(state, block, messages, options = {}) {
|
|
2226
|
-
const full = options.full ?? false;
|
|
2227
|
-
const targetIds = new Set(block.effectiveMessageIds);
|
|
2228
|
-
if (full) {
|
|
2229
|
-
const msgs = messages.filter((m) => targetIds.has(m.id));
|
|
2230
|
-
if (msgs.length === 0) return { text: "", count: 0 };
|
|
2231
|
-
return { text: msgs.map(formatMessage).join("\n\n"), count: msgs.length };
|
|
2232
|
-
}
|
|
2233
|
-
const nestedChildren = [];
|
|
2234
|
-
const nestedCovered = /* @__PURE__ */ new Set();
|
|
2235
|
-
for (const childId of block.directBlockIds) {
|
|
2236
|
-
const child = state.blocks.find((b) => b.blockId === childId);
|
|
2237
|
-
if (!child?.active) continue;
|
|
2238
|
-
nestedChildren.push(child);
|
|
2239
|
-
for (const id of child.effectiveMessageIds) nestedCovered.add(id);
|
|
2240
|
-
}
|
|
2241
|
-
const parts = [];
|
|
2242
|
-
for (const child of nestedChildren) {
|
|
2243
|
-
const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;
|
|
2244
|
-
parts.push(`${SUMMARY_HEADER} \u2014 ${label}
|
|
2245
|
-
${child.summary}`);
|
|
2246
|
-
}
|
|
2247
|
-
let directCount = 0;
|
|
2248
|
-
for (const m of messages) {
|
|
2249
|
-
if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {
|
|
2250
|
-
parts.push(formatMessage(m));
|
|
2251
|
-
directCount++;
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
const count = directCount + nestedChildren.length;
|
|
2255
|
-
if (count === 0) return { text: "", count: 0 };
|
|
2256
|
-
return { text: parts.join("\n\n"), count };
|
|
2257
|
-
}
|
|
2258
|
-
function formatMessage(message) {
|
|
2259
|
-
const text = message.text ?? "";
|
|
2260
|
-
if (message.toolName && message.contentType !== "text") {
|
|
2261
|
-
return `[${message.role} \u2022 ${message.toolName}]
|
|
2262
|
-
${text}`;
|
|
2263
|
-
}
|
|
2264
|
-
return `[${message.role}]
|
|
2265
|
-
${text}`;
|
|
2266
|
-
}
|
|
2267
|
-
function formatTokens2(n) {
|
|
2268
|
-
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
2269
|
-
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
2270
|
-
}
|
|
2271
|
-
function pct(n, total) {
|
|
2272
|
-
if (n <= 0 || total <= 0) return 0;
|
|
2273
|
-
return Math.max(1, Math.round(n / total * 100));
|
|
2274
|
-
}
|
|
2275
|
-
function numericPart2(blockId) {
|
|
2276
|
-
const match = /^b(\d+)$/.exec(blockId);
|
|
2277
|
-
return match && match[1] !== void 0 ? Number(match[1]) : 0;
|
|
2278
|
-
}
|
|
2279
|
-
function summaryTokensOf(block, countTokens) {
|
|
2280
|
-
return countTokens(block.summary);
|
|
2281
|
-
}
|
|
2282
|
-
function effectiveCompressedTokens(block, _state, _countTokens) {
|
|
2283
|
-
return block.compressedTokens;
|
|
2284
|
-
}
|
|
2285
|
-
function tierLabel(block) {
|
|
2286
|
-
return `T${block.tier}`;
|
|
2287
|
-
}
|
|
2288
|
-
function tierBreakdown(blocks, countTokens) {
|
|
2289
|
-
const tierTokens = {};
|
|
2290
|
-
for (const block of blocks) {
|
|
2291
|
-
tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);
|
|
2292
|
-
}
|
|
2293
|
-
const tiers = Object.keys(tierTokens).map(Number);
|
|
2294
|
-
if (tiers.length <= 1) return null;
|
|
2295
|
-
const parts = [];
|
|
2296
|
-
for (const tier of [1, 2, 3]) {
|
|
2297
|
-
if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])}`);
|
|
2298
|
-
}
|
|
2299
|
-
return parts.join(" | ");
|
|
2300
|
-
}
|
|
2301
|
-
function collectVisible(messages, state, countTokens) {
|
|
2302
|
-
const coveredIds = /* @__PURE__ */ new Set();
|
|
2303
|
-
for (const block of state.blocks) {
|
|
2304
|
-
if (!block.active) continue;
|
|
2305
|
-
for (const id of block.effectiveMessageIds) coveredIds.add(id);
|
|
2306
|
-
}
|
|
2307
|
-
let summaryTokens = 0;
|
|
2308
|
-
for (const block of state.blocks) {
|
|
2309
|
-
if (block.active) summaryTokens += summaryTokensOf(block, countTokens);
|
|
2310
|
-
}
|
|
2311
|
-
const visible = [];
|
|
2312
|
-
messages.forEach((message, index) => {
|
|
2313
|
-
if (coveredIds.has(message.id)) return;
|
|
2314
|
-
const ref = refForRaw(state.messageRefs, message.id);
|
|
2315
|
-
if (!ref) return;
|
|
2316
|
-
const tokens = countTokens(message.text ?? "");
|
|
2317
|
-
const tool = message.toolName ?? "text";
|
|
2318
|
-
if (tokens > 0) visible.push({ ref, tokens, tool, index });
|
|
2319
|
-
});
|
|
2320
|
-
return { visible, summaryTokens };
|
|
2321
|
-
}
|
|
2322
|
-
function buildStatusReport(state, messages, countTokens, options = {}) {
|
|
2323
|
-
const scope = options.scope;
|
|
2324
|
-
const view = options.view ?? "ranges";
|
|
2325
|
-
const toolFilter = options.tool;
|
|
2326
|
-
const sort = options.sort ?? "size";
|
|
2327
|
-
const limit = options.limit ?? 30;
|
|
2328
|
-
const activeBlocks2 = state.blocks.filter((b) => b.active).sort((a, b) => numericPart2(a.blockId) - numericPart2(b.blockId));
|
|
2329
|
-
if (scope === "compressed") {
|
|
2330
|
-
return renderCompressedDrilldown(activeBlocks2, state, sort, limit, countTokens);
|
|
2331
|
-
}
|
|
2332
|
-
const { visible, summaryTokens } = collectVisible(messages, state, countTokens);
|
|
2333
|
-
if (scope === "uncompressed") {
|
|
2334
|
-
if (view === "messages") {
|
|
2335
|
-
return renderMessageDrilldown(visible, toolFilter, sort, limit);
|
|
2336
|
-
}
|
|
2337
|
-
return renderUncompressedRanges(visible);
|
|
2472
|
+
const { visible, summaryTokens } = collectVisible(messages, state, countTokens);
|
|
2473
|
+
if (scope === "uncompressed") {
|
|
2474
|
+
if (view === "messages") {
|
|
2475
|
+
return renderMessageDrilldown(visible, toolFilter, sort, limit);
|
|
2476
|
+
}
|
|
2477
|
+
return renderUncompressedRanges(visible);
|
|
2338
2478
|
}
|
|
2339
2479
|
return renderOverview(visible, summaryTokens, activeBlocks2, state, countTokens, limit);
|
|
2340
2480
|
}
|
|
@@ -3604,7 +3744,7 @@ async function abortableSleep(ms, signal) {
|
|
|
3604
3744
|
if (signal.aborted) return "aborted";
|
|
3605
3745
|
const remaining = end - Date.now();
|
|
3606
3746
|
if (remaining <= 0) return "ok";
|
|
3607
|
-
await new Promise((
|
|
3747
|
+
await new Promise((resolve3) => setTimeout(resolve3, Math.min(250, remaining)));
|
|
3608
3748
|
}
|
|
3609
3749
|
}
|
|
3610
3750
|
|
|
@@ -3930,17 +4070,20 @@ function createRuntime(adapter) {
|
|
|
3930
4070
|
for (const o of outcomes) {
|
|
3931
4071
|
if (compressOutcomeSeen.has(o.toolCallId)) continue;
|
|
3932
4072
|
compressOutcomeSeen.add(o.toolCallId);
|
|
3933
|
-
if (o.isError) {
|
|
4073
|
+
if (o.isError || o.noop === true) {
|
|
3934
4074
|
compressFailCount += 1;
|
|
3935
4075
|
} else if (o.success) {
|
|
3936
4076
|
compressFailCount = 0;
|
|
3937
4077
|
}
|
|
3938
4078
|
}
|
|
3939
4079
|
const latest = outcomes.length > 0 ? outcomes[outcomes.length - 1] : void 0;
|
|
3940
|
-
const retryFor = latest && latest.isError && compressFailCount >= 1 && compressFailCount < MAX_COMPRESS_ATTEMPTS ? latest.toolCallId : null;
|
|
4080
|
+
const retryFor = latest && (latest.isError || latest.noop === true) && compressFailCount >= 1 && compressFailCount < MAX_COMPRESS_ATTEMPTS ? latest.toolCallId : null;
|
|
3941
4081
|
const cappedNow = compressFailCount >= MAX_COMPRESS_ATTEMPTS && prevCount < MAX_COMPRESS_ATTEMPTS;
|
|
3942
4082
|
return { count: compressFailCount, retryFor, cappedNow };
|
|
3943
4083
|
}
|
|
4084
|
+
function compressRetryCappedFor(turnKey) {
|
|
4085
|
+
return compressFailTurnKey === turnKey && compressFailCount >= MAX_COMPRESS_ATTEMPTS;
|
|
4086
|
+
}
|
|
3944
4087
|
function clearCompressRetryTracking() {
|
|
3945
4088
|
compressOutcomeSeen.clear();
|
|
3946
4089
|
compressFailTurnKey = null;
|
|
@@ -3949,10 +4092,10 @@ function createRuntime(adapter) {
|
|
|
3949
4092
|
async function acquireLock(sid) {
|
|
3950
4093
|
const prev = locks.get(sid) ?? Promise.resolve();
|
|
3951
4094
|
let release;
|
|
3952
|
-
const next = new Promise((
|
|
4095
|
+
const next = new Promise((resolve3) => {
|
|
3953
4096
|
release = () => {
|
|
3954
4097
|
locks.delete(sid);
|
|
3955
|
-
|
|
4098
|
+
resolve3();
|
|
3956
4099
|
};
|
|
3957
4100
|
});
|
|
3958
4101
|
locks.set(sid, prev.then(() => next));
|
|
@@ -4033,7 +4176,7 @@ function createRuntime(adapter) {
|
|
|
4033
4176
|
nudgeShownTurns.add(k);
|
|
4034
4177
|
}, nudgeShownFor: (k) => nudgeShownTurns.has(k), clearNudgeTracking: () => {
|
|
4035
4178
|
nudgeShownTurns.clear();
|
|
4036
|
-
}, noteCompressOutcomes, clearCompressRetryTracking, liveContextLimit, configFor, reloadConfig, stateFor, save, acquireLock, overflowFor, overflowDrop, throttleFor, throttleDrop };
|
|
4179
|
+
}, noteCompressOutcomes, compressRetryCappedFor, clearCompressRetryTracking, liveContextLimit, configFor, reloadConfig, stateFor, save, acquireLock, overflowFor, overflowDrop, throttleFor, throttleDrop };
|
|
4037
4180
|
}
|
|
4038
4181
|
|
|
4039
4182
|
// node_modules/typebox/build/system/memory/memory.mjs
|
|
@@ -8545,8 +8688,16 @@ function normalizeRanges(content) {
|
|
|
8545
8688
|
}
|
|
8546
8689
|
return ranges;
|
|
8547
8690
|
}
|
|
8691
|
+
function compressPanelBlocks(text) {
|
|
8692
|
+
if (!text.trimStart().startsWith("\u25A3 ACP |")) return -1;
|
|
8693
|
+
const m = text.match(/, (\d+) blocks?\)/);
|
|
8694
|
+
return m ? Number(m[1]) : -1;
|
|
8695
|
+
}
|
|
8548
8696
|
function isCompressSuccessText(text) {
|
|
8549
|
-
return text
|
|
8697
|
+
return compressPanelBlocks(text) > 0;
|
|
8698
|
+
}
|
|
8699
|
+
function isCompressNoopText(text) {
|
|
8700
|
+
return compressPanelBlocks(text) === 0;
|
|
8550
8701
|
}
|
|
8551
8702
|
function tier3OnlyRewrite(newBlocks, allBlocks) {
|
|
8552
8703
|
if (newBlocks.length === 0) return null;
|
|
@@ -8978,16 +9129,19 @@ function formatSize(tokens) {
|
|
|
8978
9129
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
8979
9130
|
}
|
|
8980
9131
|
|
|
8981
|
-
//
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
}
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
9132
|
+
// src/delegate-tool.ts
|
|
9133
|
+
import {
|
|
9134
|
+
spawn
|
|
9135
|
+
} from "child_process";
|
|
9136
|
+
import { createWriteStream, existsSync as existsSync3 } from "fs";
|
|
9137
|
+
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
9138
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
9139
|
+
import { dirname as dirname4, join as join6, resolve as resolvePath } from "path";
|
|
9140
|
+
|
|
9141
|
+
// src/footer-status.ts
|
|
9142
|
+
var FOOTER_STATUS_KEY = "billion-context-pi";
|
|
9143
|
+
var ui;
|
|
9144
|
+
var lastFooterText = "";
|
|
8991
9145
|
function formatCompactTokens(count) {
|
|
8992
9146
|
if (count < 1e3) return count.toString();
|
|
8993
9147
|
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
@@ -8995,117 +9149,9 @@ function formatCompactTokens(count) {
|
|
|
8995
9149
|
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
8996
9150
|
return `${Math.round(count / 1e6)}M`;
|
|
8997
9151
|
}
|
|
8998
|
-
function
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
9002
|
-
}
|
|
9003
|
-
function buildStatusPanel(input) {
|
|
9004
|
-
const { tokenCount, state, nudge, modelContextLimit } = input;
|
|
9005
|
-
const fmt2 = input.fmtTokens ?? formatCompactTokens;
|
|
9006
|
-
const bd = nudge?.contextBreakdown;
|
|
9007
|
-
const limit = modelContextLimit;
|
|
9008
|
-
const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
|
|
9009
|
-
const systemPromptTokens = input.systemPromptTokens;
|
|
9010
|
-
const sentTotal = classified + systemPromptTokens;
|
|
9011
|
-
const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
|
|
9012
|
-
const displayTotal = tokenCount;
|
|
9013
|
-
const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
|
|
9014
|
-
const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
|
|
9015
|
-
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
9016
|
-
const totalBlocksList = state.blocks;
|
|
9017
|
-
const lines = [];
|
|
9018
|
-
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
9019
|
-
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
9020
|
-
lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
9021
|
-
if (input.version) lines.push(input.version);
|
|
9022
|
-
lines.push("");
|
|
9023
|
-
lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
|
|
9024
|
-
if (nudge && bd) {
|
|
9025
|
-
const growth = bd.growth;
|
|
9026
|
-
if (growth > 0 && displayTotal > 0) {
|
|
9027
|
-
lines.push(`Growth: +${fmt2(growth)} since last nudge`);
|
|
9028
|
-
}
|
|
9029
|
-
lines.push("");
|
|
9030
|
-
lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
|
|
9031
|
-
if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
|
|
9032
|
-
lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
|
|
9033
|
-
}
|
|
9034
|
-
lines.push("");
|
|
9035
|
-
lines.push("Token Breakdown (sent view):");
|
|
9036
|
-
const categories = [
|
|
9037
|
-
{ label: "Tool", value: bd.tool },
|
|
9038
|
-
{ label: "SysPrompt", value: systemPromptTokens },
|
|
9039
|
-
{ label: "Text", value: bd.text },
|
|
9040
|
-
{ label: "Code", value: bd.code },
|
|
9041
|
-
{ label: "Summaries", value: bd.summaries }
|
|
9042
|
-
];
|
|
9043
|
-
for (const cat of categories) {
|
|
9044
|
-
if (cat.value <= 0) continue;
|
|
9045
|
-
const pct2 = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
|
|
9046
|
-
const b = bar(cat.value, sentTotal);
|
|
9047
|
-
lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
|
|
9048
|
-
}
|
|
9049
|
-
}
|
|
9050
|
-
lines.push("");
|
|
9051
|
-
if (nudge) {
|
|
9052
|
-
if (nudge.shouldInject) {
|
|
9053
|
-
const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
|
|
9054
|
-
lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
|
|
9055
|
-
} else {
|
|
9056
|
-
lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
|
|
9057
|
-
}
|
|
9058
|
-
}
|
|
9059
|
-
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
9060
|
-
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
9061
|
-
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
9062
|
-
lines.push("");
|
|
9063
|
-
lines.push(formatRanges(ranges, protectedRanges));
|
|
9064
|
-
}
|
|
9065
|
-
if (activeBlocksList.length > 0) {
|
|
9066
|
-
lines.push("");
|
|
9067
|
-
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
9068
|
-
for (const b of activeBlocksList) {
|
|
9069
|
-
const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
|
|
9070
|
-
const summaryTok = defaultCountTokens(b.summary || "");
|
|
9071
|
-
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
9072
|
-
lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
|
|
9073
|
-
}
|
|
9074
|
-
} else if (totalBlocksList.length > 0) {
|
|
9075
|
-
lines.push("");
|
|
9076
|
-
lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
9077
|
-
} else {
|
|
9078
|
-
lines.push("");
|
|
9079
|
-
lines.push("Blocks: none (nothing compressed yet)");
|
|
9080
|
-
}
|
|
9081
|
-
lines.push("");
|
|
9082
|
-
lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
|
|
9083
|
-
return lines.join("\n");
|
|
9084
|
-
}
|
|
9085
|
-
|
|
9086
|
-
// src/delegate-tool.ts
|
|
9087
|
-
import {
|
|
9088
|
-
spawn
|
|
9089
|
-
} from "child_process";
|
|
9090
|
-
import { createWriteStream, existsSync as existsSync3 } from "fs";
|
|
9091
|
-
import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
|
|
9092
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
9093
|
-
import { dirname as dirname4, join as join6, resolve as resolvePath } from "path";
|
|
9094
|
-
|
|
9095
|
-
// src/footer-status.ts
|
|
9096
|
-
var FOOTER_STATUS_KEY = "billion-context-pi";
|
|
9097
|
-
var ui;
|
|
9098
|
-
var lastFooterText = "";
|
|
9099
|
-
function formatCompactTokens2(count) {
|
|
9100
|
-
if (count < 1e3) return count.toString();
|
|
9101
|
-
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
9102
|
-
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
9103
|
-
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
9104
|
-
return `${Math.round(count / 1e6)}M`;
|
|
9105
|
-
}
|
|
9106
|
-
function initFooterStatus(ctx) {
|
|
9107
|
-
ui = ctx.ui;
|
|
9108
|
-
lastFooterText = void 0;
|
|
9152
|
+
function initFooterStatus(ctx) {
|
|
9153
|
+
ui = ctx.ui;
|
|
9154
|
+
lastFooterText = void 0;
|
|
9109
9155
|
}
|
|
9110
9156
|
function updateFooterStatus() {
|
|
9111
9157
|
if (!ui) return;
|
|
@@ -9113,7 +9159,7 @@ function updateFooterStatus() {
|
|
|
9113
9159
|
let text;
|
|
9114
9160
|
if (usage && usage.totalTokens > 0) {
|
|
9115
9161
|
const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
|
|
9116
|
-
text = `sub-agents \u2191${
|
|
9162
|
+
text = `sub-agents \u2191${formatCompactTokens(usage.input)} \u2193${formatCompactTokens(usage.output)}${costStr}`;
|
|
9117
9163
|
}
|
|
9118
9164
|
if ((text ?? "") === lastFooterText) return;
|
|
9119
9165
|
lastFooterText = text ?? "";
|
|
@@ -9874,7 +9920,7 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9874
9920
|
if (run.waiter) {
|
|
9875
9921
|
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` already has a wait in progress; do not wait on it twice.` }] };
|
|
9876
9922
|
}
|
|
9877
|
-
return new Promise((
|
|
9923
|
+
return new Promise((resolve3) => {
|
|
9878
9924
|
let settled = false;
|
|
9879
9925
|
let timer2;
|
|
9880
9926
|
const finish = (result) => {
|
|
@@ -9883,7 +9929,7 @@ function makeDelegateWaitTool(_pi) {
|
|
|
9883
9929
|
run.waiter = void 0;
|
|
9884
9930
|
if (timer2) clearTimeout(timer2);
|
|
9885
9931
|
signal?.removeEventListener("abort", onAbort);
|
|
9886
|
-
|
|
9932
|
+
resolve3(result);
|
|
9887
9933
|
};
|
|
9888
9934
|
const onAbort = () => {
|
|
9889
9935
|
finish({ details: void 0, content: [{ type: "text", text: `Aborted; delegate \`${args.runId}\` is still running in the background. A notification will be injected when it finishes.` }] });
|
|
@@ -9996,9 +10042,9 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
9996
10042
|
await mkdir2(OUT_DIR, { recursive: true });
|
|
9997
10043
|
const replyStream = createWriteStream(replyFile, { flags: "a" });
|
|
9998
10044
|
const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
|
|
9999
|
-
const endStream = (s) => new Promise((
|
|
10000
|
-
if (!s || s.destroyed || s.closed) return
|
|
10001
|
-
s.end(() =>
|
|
10045
|
+
const endStream = (s) => new Promise((resolve3) => {
|
|
10046
|
+
if (!s || s.destroyed || s.closed) return resolve3();
|
|
10047
|
+
s.end(() => resolve3());
|
|
10002
10048
|
});
|
|
10003
10049
|
let stdoutBuf = "";
|
|
10004
10050
|
const applier = makeEventApplier(
|
|
@@ -10167,7 +10213,7 @@ Complete the task below.`, "utf8");
|
|
|
10167
10213
|
return { cliArgs, tmpDir, isAsync, useJsonStream };
|
|
10168
10214
|
}
|
|
10169
10215
|
function waitForChild(child, signal) {
|
|
10170
|
-
return new Promise((
|
|
10216
|
+
return new Promise((resolve3) => {
|
|
10171
10217
|
const stdoutChunks = [];
|
|
10172
10218
|
let stderrText = "";
|
|
10173
10219
|
child.stdout?.on("data", (c) => stdoutChunks.push(c));
|
|
@@ -10186,7 +10232,7 @@ function waitForChild(child, signal) {
|
|
|
10186
10232
|
function finish(r) {
|
|
10187
10233
|
clearTimeout(timer2);
|
|
10188
10234
|
signal?.removeEventListener("abort", onAbort);
|
|
10189
|
-
|
|
10235
|
+
resolve3(r);
|
|
10190
10236
|
}
|
|
10191
10237
|
child.on("close", (code) => {
|
|
10192
10238
|
finish({
|
|
@@ -10249,144 +10295,438 @@ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleStr
|
|
|
10249
10295
|
lines.push(`cost=${JSON.stringify(c)}`);
|
|
10250
10296
|
}
|
|
10251
10297
|
}
|
|
10252
|
-
if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
|
|
10298
|
+
if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
|
|
10299
|
+
}
|
|
10300
|
+
const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine}${usageNote} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
|
|
10301
|
+
const text = formatPayload(header, file, task);
|
|
10302
|
+
try {
|
|
10303
|
+
send.call(pi, text, { deliverAs: "followUp" });
|
|
10304
|
+
return true;
|
|
10305
|
+
} catch (err) {
|
|
10306
|
+
debug.event("delegate-inject-error", { runId, error: String(err) });
|
|
10307
|
+
logError("delegate", { event: "inject-error", runId, agent, error: String(err) });
|
|
10308
|
+
return false;
|
|
10309
|
+
}
|
|
10310
|
+
}
|
|
10311
|
+
function formatPayload(header, file, task, body) {
|
|
10312
|
+
const lines = [header, "", `Task: ${truncate2(task, 160)}`];
|
|
10313
|
+
if (file) {
|
|
10314
|
+
lines.push(``, `Full result: \`${file}\``, "(use the `read` tool to open it if you need the details)");
|
|
10315
|
+
} else {
|
|
10316
|
+
lines.push("", "(result could not be persisted to a file)");
|
|
10317
|
+
}
|
|
10318
|
+
if (body) {
|
|
10319
|
+
lines.push("", "Output:", "~~~", truncate2(body, RESULT_SUMMARY_CHARS), "~~~");
|
|
10320
|
+
}
|
|
10321
|
+
lines.push("");
|
|
10322
|
+
return lines.join("\n");
|
|
10323
|
+
}
|
|
10324
|
+
async function persistResult(runId, body) {
|
|
10325
|
+
try {
|
|
10326
|
+
await mkdir2(OUT_DIR, { recursive: true });
|
|
10327
|
+
} catch {
|
|
10328
|
+
}
|
|
10329
|
+
const file = join6(OUT_DIR, `${runId}.out`);
|
|
10330
|
+
try {
|
|
10331
|
+
await writeFile2(file, body, "utf8");
|
|
10332
|
+
return file;
|
|
10333
|
+
} catch (err) {
|
|
10334
|
+
debug.event("delegate-persist-error", { runId, file, error: String(err) });
|
|
10335
|
+
logError("delegate", { event: "persist-error", runId, file, error: String(err) });
|
|
10336
|
+
return "";
|
|
10337
|
+
}
|
|
10338
|
+
}
|
|
10339
|
+
async function cleanupTmp(tmpDir) {
|
|
10340
|
+
if (!tmpDir) return;
|
|
10341
|
+
try {
|
|
10342
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
10343
|
+
} catch {
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
function truncate2(s, n) {
|
|
10347
|
+
if (s.length <= n) return s;
|
|
10348
|
+
return s.slice(0, n - 1) + "\u2026";
|
|
10349
|
+
}
|
|
10350
|
+
|
|
10351
|
+
// src/status-tool.ts
|
|
10352
|
+
var StatusParams = typebox_exports.Object({
|
|
10353
|
+
scope: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("compressed"), typebox_exports.Literal("uncompressed")], { description: '"compressed" = drill into blocks; "uncompressed" = show visible messages/ranges. Default: overview.' })),
|
|
10354
|
+
view: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("ranges"), typebox_exports.Literal("messages")], { description: 'For uncompressed scope: "ranges" (default) or "messages" (per-message listing).' })),
|
|
10355
|
+
tool: typebox_exports.Optional(typebox_exports.String({ description: 'Filter by tool name (e.g. "bash", "read"). Only for uncompressed+messages.' })),
|
|
10356
|
+
sort: typebox_exports.Optional(typebox_exports.Union([typebox_exports.Literal("size"), typebox_exports.Literal("time"), typebox_exports.Literal("tool"), typebox_exports.Literal("age")], { description: "Sort order. Default: size." })),
|
|
10357
|
+
limit: typebox_exports.Optional(typebox_exports.Number({ description: "Max items to show (default: 30)." }))
|
|
10358
|
+
});
|
|
10359
|
+
function makeStatusTool(runtime) {
|
|
10360
|
+
return {
|
|
10361
|
+
name: "acp_status",
|
|
10362
|
+
label: "ACP Status",
|
|
10363
|
+
description: "Context status: overview, compressed blocks, or uncompressed ranges/messages. No args = overview + totals + compressible ranges. scope:'uncompressed' + view:'messages' for per-message listing. scope:'compressed' for block drilldown.",
|
|
10364
|
+
promptSnippet: 'acp_status({}) or acp_status({ scope: "uncompressed", view: "messages" })',
|
|
10365
|
+
promptGuidelines: [
|
|
10366
|
+
"Call with no args for a quick overview of context usage.",
|
|
10367
|
+
"Use scope:'uncompressed' to find the largest compressible ranges.",
|
|
10368
|
+
"Use scope:'compressed' to inspect existing compression blocks."
|
|
10369
|
+
],
|
|
10370
|
+
parameters: StatusParams,
|
|
10371
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10372
|
+
let result;
|
|
10373
|
+
try {
|
|
10374
|
+
result = await handleStatus(params, runtime, ctx);
|
|
10375
|
+
} catch (e) {
|
|
10376
|
+
logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
|
|
10377
|
+
throw e;
|
|
10378
|
+
}
|
|
10379
|
+
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
10380
|
+
}
|
|
10381
|
+
};
|
|
10382
|
+
}
|
|
10383
|
+
async function handleStatus(args, runtime, ctx) {
|
|
10384
|
+
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
10385
|
+
const config = runtime.configFor(ctx);
|
|
10386
|
+
const coveredIds = collectCoveredMessageIds(state);
|
|
10387
|
+
const modelId = ctx.model?.id ?? "default";
|
|
10388
|
+
const systemPromptText = getSystemPromptText(ctx);
|
|
10389
|
+
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10390
|
+
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10391
|
+
const turn = runtime.core.processTurn({
|
|
10392
|
+
messages: coreMessages,
|
|
10393
|
+
state,
|
|
10394
|
+
config,
|
|
10395
|
+
tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId))
|
|
10396
|
+
});
|
|
10397
|
+
const processed = turn.messages;
|
|
10398
|
+
const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
|
|
10399
|
+
scope: args.scope,
|
|
10400
|
+
view: args.view,
|
|
10401
|
+
tool: args.tool,
|
|
10402
|
+
sort: args.sort,
|
|
10403
|
+
limit: args.limit
|
|
10404
|
+
});
|
|
10405
|
+
if (args.scope) return base;
|
|
10406
|
+
const nudge = turn.nudge;
|
|
10407
|
+
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
10408
|
+
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
10409
|
+
const extra = [];
|
|
10410
|
+
if (nudge) {
|
|
10411
|
+
extra.push("");
|
|
10412
|
+
extra.push(
|
|
10413
|
+
nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`
|
|
10414
|
+
);
|
|
10415
|
+
}
|
|
10416
|
+
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
10417
|
+
extra.push("");
|
|
10418
|
+
extra.push(formatRanges(ranges, protectedRanges));
|
|
10419
|
+
}
|
|
10420
|
+
const delegateUsage = getDelegateUsage();
|
|
10421
|
+
if (delegateUsage && delegateUsage.totalTokens > 0) {
|
|
10422
|
+
extra.push("");
|
|
10423
|
+
const cost = delegateUsage.cost.total;
|
|
10424
|
+
const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
|
|
10425
|
+
extra.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
|
|
10426
|
+
extra.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
|
|
10427
|
+
} else if (resolveDelegate(runtime.adapter).displayUsage === "merged") {
|
|
10428
|
+
extra.push("");
|
|
10429
|
+
extra.push("merged mode: delegate usage is included in main session totals.");
|
|
10430
|
+
} else {
|
|
10431
|
+
extra.push("");
|
|
10432
|
+
extra.push("Delegate usage: none this session.");
|
|
10433
|
+
}
|
|
10434
|
+
return extra.length > 0 ? `${base}
|
|
10435
|
+
${extra.join("\n")}` : base;
|
|
10436
|
+
}
|
|
10437
|
+
|
|
10438
|
+
// node_modules/acp-kernel/dist/panel/index.js
|
|
10439
|
+
function topicFallback(summary) {
|
|
10440
|
+
const first = summary.split(/[.\n]/)[0] ?? "";
|
|
10441
|
+
const t = first.trim().replace(/^["'`]+/, "").trim();
|
|
10442
|
+
return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}\u2026`;
|
|
10443
|
+
}
|
|
10444
|
+
function formatCompactTokens2(count) {
|
|
10445
|
+
if (count < 1e3) return count.toString();
|
|
10446
|
+
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
10447
|
+
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
10448
|
+
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
10449
|
+
return `${Math.round(count / 1e6)}M`;
|
|
10450
|
+
}
|
|
10451
|
+
function bar(value, total, width = 20) {
|
|
10452
|
+
if (total === 0) return "";
|
|
10453
|
+
const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
|
|
10454
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
10455
|
+
}
|
|
10456
|
+
function buildStatusPanel(input) {
|
|
10457
|
+
const { tokenCount, state, nudge, modelContextLimit } = input;
|
|
10458
|
+
const fmt2 = input.fmtTokens ?? formatCompactTokens2;
|
|
10459
|
+
const bd = nudge?.contextBreakdown;
|
|
10460
|
+
const limit = modelContextLimit;
|
|
10461
|
+
const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
|
|
10462
|
+
const systemPromptTokens = input.systemPromptTokens;
|
|
10463
|
+
const sentTotal = classified + systemPromptTokens;
|
|
10464
|
+
const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
|
|
10465
|
+
const displayTotal = tokenCount;
|
|
10466
|
+
const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
|
|
10467
|
+
const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
|
|
10468
|
+
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
10469
|
+
const totalBlocksList = state.blocks;
|
|
10470
|
+
const lines = [];
|
|
10471
|
+
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
10472
|
+
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
10473
|
+
lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
10474
|
+
if (input.version) lines.push(input.version);
|
|
10475
|
+
lines.push("");
|
|
10476
|
+
lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
|
|
10477
|
+
if (nudge && bd) {
|
|
10478
|
+
const growth = bd.growth;
|
|
10479
|
+
if (growth > 0 && displayTotal > 0) {
|
|
10480
|
+
lines.push(`Growth: +${fmt2(growth)} since last nudge`);
|
|
10481
|
+
}
|
|
10482
|
+
lines.push("");
|
|
10483
|
+
lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
|
|
10484
|
+
if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
|
|
10485
|
+
lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
|
|
10486
|
+
}
|
|
10487
|
+
lines.push("");
|
|
10488
|
+
lines.push("Token Breakdown (sent view):");
|
|
10489
|
+
const categories = [
|
|
10490
|
+
{ label: "Tool", value: bd.tool },
|
|
10491
|
+
{ label: "SysPrompt", value: systemPromptTokens },
|
|
10492
|
+
{ label: "Text", value: bd.text },
|
|
10493
|
+
{ label: "Code", value: bd.code },
|
|
10494
|
+
{ label: "Summaries", value: bd.summaries }
|
|
10495
|
+
];
|
|
10496
|
+
for (const cat of categories) {
|
|
10497
|
+
if (cat.value <= 0) continue;
|
|
10498
|
+
const pct2 = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
|
|
10499
|
+
const b = bar(cat.value, sentTotal);
|
|
10500
|
+
lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
lines.push("");
|
|
10504
|
+
if (nudge) {
|
|
10505
|
+
if (nudge.shouldInject) {
|
|
10506
|
+
const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
|
|
10507
|
+
lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
|
|
10508
|
+
} else {
|
|
10509
|
+
lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
10513
|
+
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
10514
|
+
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
10515
|
+
lines.push("");
|
|
10516
|
+
lines.push(formatRanges(ranges, protectedRanges));
|
|
10517
|
+
}
|
|
10518
|
+
if (activeBlocksList.length > 0) {
|
|
10519
|
+
lines.push("");
|
|
10520
|
+
lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
10521
|
+
for (const b of activeBlocksList) {
|
|
10522
|
+
const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
|
|
10523
|
+
const summaryTok = defaultCountTokens(b.summary || "");
|
|
10524
|
+
const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
|
|
10525
|
+
lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
|
|
10526
|
+
}
|
|
10527
|
+
} else if (totalBlocksList.length > 0) {
|
|
10528
|
+
lines.push("");
|
|
10529
|
+
lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
|
|
10530
|
+
} else {
|
|
10531
|
+
lines.push("");
|
|
10532
|
+
lines.push("Blocks: none (nothing compressed yet)");
|
|
10533
|
+
}
|
|
10534
|
+
lines.push("");
|
|
10535
|
+
lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
|
|
10536
|
+
return lines.join("\n");
|
|
10537
|
+
}
|
|
10538
|
+
|
|
10539
|
+
// src/setup-subagent-tools.ts
|
|
10540
|
+
import * as fs3 from "fs";
|
|
10541
|
+
import * as os from "os";
|
|
10542
|
+
import * as path4 from "path";
|
|
10543
|
+
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
|
|
10544
|
+
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
10545
|
+
function resolveAgentDir() {
|
|
10546
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
10547
|
+
if (envDir) {
|
|
10548
|
+
if (envDir === "~") return os.homedir();
|
|
10549
|
+
if (envDir.startsWith("~/")) return path4.join(os.homedir(), envDir.slice(2));
|
|
10550
|
+
return envDir;
|
|
10551
|
+
}
|
|
10552
|
+
return path4.join(os.homedir(), CONFIG_DIR_NAME3, "agent");
|
|
10553
|
+
}
|
|
10554
|
+
function parseFrontmatterTools(content) {
|
|
10555
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
10556
|
+
if (!match) return null;
|
|
10557
|
+
const body = match[1];
|
|
10558
|
+
if (!body) return null;
|
|
10559
|
+
let name;
|
|
10560
|
+
let tools;
|
|
10561
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
10562
|
+
const line = rawLine.trim();
|
|
10563
|
+
if (line.startsWith("name:")) {
|
|
10564
|
+
name = line.slice(5).trim().replace(/^["']|["']$/g, "");
|
|
10565
|
+
} else if (line.startsWith("tools:")) {
|
|
10566
|
+
const value = line.slice(6).trim();
|
|
10567
|
+
if (value) {
|
|
10568
|
+
tools = value.split(",").map((t) => t.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
10569
|
+
}
|
|
10570
|
+
}
|
|
10571
|
+
}
|
|
10572
|
+
if (!name) return null;
|
|
10573
|
+
return tools ? { name, tools } : { name };
|
|
10574
|
+
}
|
|
10575
|
+
function findPiSubagentsInstall(agentDir, cwd) {
|
|
10576
|
+
const candidates = [
|
|
10577
|
+
path4.join(agentDir, "npm", "node_modules", "pi-subagents"),
|
|
10578
|
+
path4.join(cwd, CONFIG_DIR_NAME3, "npm", "node_modules", "pi-subagents")
|
|
10579
|
+
];
|
|
10580
|
+
const extensionRoots = [
|
|
10581
|
+
path4.join(agentDir, "extensions"),
|
|
10582
|
+
path4.join(cwd, CONFIG_DIR_NAME3, "extensions")
|
|
10583
|
+
];
|
|
10584
|
+
for (const dir of candidates) {
|
|
10585
|
+
if (fs3.existsSync(path4.join(dir, "package.json"))) return dir;
|
|
10586
|
+
}
|
|
10587
|
+
for (const root of extensionRoots) {
|
|
10588
|
+
let entries;
|
|
10589
|
+
try {
|
|
10590
|
+
entries = fs3.readdirSync(root, { withFileTypes: true });
|
|
10591
|
+
} catch {
|
|
10592
|
+
continue;
|
|
10593
|
+
}
|
|
10594
|
+
for (const entry of entries) {
|
|
10595
|
+
if (!entry.isDirectory()) continue;
|
|
10596
|
+
const pkgPath = path4.join(root, entry.name, "package.json");
|
|
10597
|
+
try {
|
|
10598
|
+
if (JSON.parse(fs3.readFileSync(pkgPath, "utf-8")).name === "pi-subagents") {
|
|
10599
|
+
return path4.join(root, entry.name);
|
|
10600
|
+
}
|
|
10601
|
+
} catch {
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10253
10604
|
}
|
|
10254
|
-
|
|
10255
|
-
|
|
10605
|
+
return null;
|
|
10606
|
+
}
|
|
10607
|
+
function discoverBuiltinAgents(installDir) {
|
|
10608
|
+
const agentsDir = path4.join(installDir, "agents");
|
|
10609
|
+
let entries;
|
|
10256
10610
|
try {
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
debug.event("delegate-inject-error", { runId, error: String(err) });
|
|
10261
|
-
logError("delegate", { event: "inject-error", runId, agent, error: String(err) });
|
|
10262
|
-
return false;
|
|
10611
|
+
entries = fs3.readdirSync(agentsDir, { withFileTypes: true });
|
|
10612
|
+
} catch {
|
|
10613
|
+
return [];
|
|
10263
10614
|
}
|
|
10615
|
+
const parsed = [];
|
|
10616
|
+
for (const entry of entries) {
|
|
10617
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
10618
|
+
try {
|
|
10619
|
+
const result = parseFrontmatterTools(fs3.readFileSync(path4.join(agentsDir, entry.name), "utf-8"));
|
|
10620
|
+
if (result) parsed.push(result);
|
|
10621
|
+
} catch {
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
return parsed;
|
|
10264
10625
|
}
|
|
10265
|
-
function
|
|
10266
|
-
const
|
|
10267
|
-
|
|
10268
|
-
|
|
10626
|
+
function desiredTools(baseTools) {
|
|
10627
|
+
const tools = baseTools ? [...baseTools] : [];
|
|
10628
|
+
for (const tool of ACP_TOOLS2) {
|
|
10629
|
+
if (!tools.includes(tool)) tools.push(tool);
|
|
10630
|
+
}
|
|
10631
|
+
return tools;
|
|
10632
|
+
}
|
|
10633
|
+
function ensureSubagentAcpTools(settingsPath, options) {
|
|
10634
|
+
const agentDir = options?.agentDir ?? resolveAgentDir();
|
|
10635
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
10636
|
+
const path_ = settingsPath ?? path4.join(agentDir, "settings.json");
|
|
10637
|
+
let installDir;
|
|
10638
|
+
if (options?.installDir) {
|
|
10639
|
+
installDir = path4.resolve(options.installDir);
|
|
10640
|
+
if (!fs3.existsSync(path4.join(installDir, "package.json"))) {
|
|
10641
|
+
return { path: path_, action: "failed", reason: `not a package: ${installDir}` };
|
|
10642
|
+
}
|
|
10269
10643
|
} else {
|
|
10270
|
-
|
|
10644
|
+
const detected = findPiSubagentsInstall(agentDir, cwd);
|
|
10645
|
+
if (!detected) {
|
|
10646
|
+
return { path: path_, action: "skipped", reason: "pi-subagents not installed" };
|
|
10647
|
+
}
|
|
10648
|
+
installDir = detected;
|
|
10271
10649
|
}
|
|
10272
|
-
|
|
10273
|
-
|
|
10650
|
+
const builtins = discoverBuiltinAgents(installDir);
|
|
10651
|
+
if (builtins.length === 0) {
|
|
10652
|
+
return { path: path_, action: "skipped", reason: "pi-subagents ships no agents/*.md" };
|
|
10274
10653
|
}
|
|
10275
|
-
|
|
10276
|
-
return lines.join("\n");
|
|
10277
|
-
}
|
|
10278
|
-
async function persistResult(runId, body) {
|
|
10654
|
+
let settingsRaw;
|
|
10279
10655
|
try {
|
|
10280
|
-
|
|
10656
|
+
settingsRaw = fs3.readFileSync(path_, "utf-8");
|
|
10281
10657
|
} catch {
|
|
10658
|
+
return { path: path_, action: "skipped", reason: "not found" };
|
|
10282
10659
|
}
|
|
10283
|
-
|
|
10660
|
+
let settings2;
|
|
10284
10661
|
try {
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
return "";
|
|
10662
|
+
settings2 = JSON.parse(settingsRaw);
|
|
10663
|
+
if (typeof settings2 !== "object" || settings2 === null || Array.isArray(settings2)) {
|
|
10664
|
+
return { path: path_, action: "failed", reason: "settings.json root is not an object" };
|
|
10665
|
+
}
|
|
10666
|
+
} catch {
|
|
10667
|
+
return { path: path_, action: "failed", reason: "settings.json is not valid JSON" };
|
|
10668
|
+
}
|
|
10669
|
+
const subagents = typeof settings2.subagents === "object" && settings2.subagents !== null ? settings2.subagents : {};
|
|
10670
|
+
const existingOverrides = typeof subagents.agentOverrides === "object" && subagents.agentOverrides !== null ? subagents.agentOverrides : {};
|
|
10671
|
+
let changed = false;
|
|
10672
|
+
const overrides = {};
|
|
10673
|
+
for (const [name, existing] of Object.entries(existingOverrides)) overrides[name] = existing ?? {};
|
|
10674
|
+
const frontmatterByName = new Map(builtins.map((b) => [b.name, b.tools]));
|
|
10675
|
+
for (const name of builtins.map((b) => b.name)) {
|
|
10676
|
+
const existing = overrides[name];
|
|
10677
|
+
const baseTools = existing?.tools && Array.isArray(existing.tools) && existing.tools.length > 0 ? existing.tools : frontmatterByName.get(name);
|
|
10678
|
+
if (baseTools === void 0) continue;
|
|
10679
|
+
const wanted = desiredTools(baseTools);
|
|
10680
|
+
const current = existing?.tools;
|
|
10681
|
+
if (Array.isArray(current) && current.length > 0 && wanted.every((tool) => current.includes(tool))) {
|
|
10682
|
+
continue;
|
|
10683
|
+
}
|
|
10684
|
+
overrides[name] = { ...existing, tools: wanted };
|
|
10685
|
+
changed = true;
|
|
10291
10686
|
}
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10687
|
+
if (!changed) {
|
|
10688
|
+
return { path: path_, action: "skipped", reason: "already have ACP tools" };
|
|
10689
|
+
}
|
|
10690
|
+
subagents.agentOverrides = overrides;
|
|
10691
|
+
settings2.subagents = subagents;
|
|
10692
|
+
const backupPath = `${path_}.acp-bak`;
|
|
10295
10693
|
try {
|
|
10296
|
-
|
|
10297
|
-
} catch {
|
|
10694
|
+
if (!fs3.existsSync(backupPath)) fs3.copyFileSync(path_, backupPath);
|
|
10695
|
+
} catch (err) {
|
|
10696
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10697
|
+
return { path: path_, action: "failed", reason: `backup failed: ${message}` };
|
|
10298
10698
|
}
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
],
|
|
10324
|
-
parameters: StatusParams,
|
|
10325
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10326
|
-
let result;
|
|
10699
|
+
const expectedMtimeMs = fs3.statSync(path_).mtimeMs;
|
|
10700
|
+
const tmpPath = `${path_}.tmp-${process.pid}`;
|
|
10701
|
+
try {
|
|
10702
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(settings2, null, 2) + "\n", "utf-8");
|
|
10703
|
+
if (fs3.statSync(path_).mtimeMs !== expectedMtimeMs) {
|
|
10704
|
+
fs3.unlinkSync(tmpPath);
|
|
10705
|
+
return { path: path_, action: "failed", reason: "settings.json changed during write" };
|
|
10706
|
+
}
|
|
10707
|
+
fs3.renameSync(tmpPath, path_);
|
|
10708
|
+
const written = JSON.parse(fs3.readFileSync(path_, "utf-8"));
|
|
10709
|
+
const writtenSub = written.subagents;
|
|
10710
|
+
const writtenOverrides = writtenSub?.agentOverrides ?? {};
|
|
10711
|
+
for (const b of builtins) {
|
|
10712
|
+
const entry = writtenOverrides[b.name];
|
|
10713
|
+
const tools = entry?.tools ?? [];
|
|
10714
|
+
if (frontmatterByName.get(b.name) !== void 0 && !ACP_TOOLS2.every((t) => tools.includes(t))) {
|
|
10715
|
+
fs3.copyFileSync(backupPath, path_);
|
|
10716
|
+
return { path: path_, action: "failed", reason: "post-write verification failed" };
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
return { path: path_, action: "updated" };
|
|
10720
|
+
} catch (err) {
|
|
10721
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10722
|
+
if (fs3.existsSync(backupPath)) {
|
|
10327
10723
|
try {
|
|
10328
|
-
|
|
10329
|
-
} catch
|
|
10330
|
-
logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
|
|
10331
|
-
throw e;
|
|
10724
|
+
fs3.copyFileSync(backupPath, path_);
|
|
10725
|
+
} catch {
|
|
10332
10726
|
}
|
|
10333
|
-
return { details: void 0, content: [{ type: "text", text: result }] };
|
|
10334
10727
|
}
|
|
10335
|
-
|
|
10336
|
-
}
|
|
10337
|
-
async function handleStatus(args, runtime, ctx) {
|
|
10338
|
-
const { state, coreMessages } = await runtime.stateFor(ctx);
|
|
10339
|
-
const config = runtime.configFor(ctx);
|
|
10340
|
-
const coveredIds = collectCoveredMessageIds(state);
|
|
10341
|
-
const modelId = ctx.model?.id ?? "default";
|
|
10342
|
-
const systemPromptText = getSystemPromptText(ctx);
|
|
10343
|
-
const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
|
|
10344
|
-
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10345
|
-
const turn = runtime.core.processTurn({
|
|
10346
|
-
messages: coreMessages,
|
|
10347
|
-
state,
|
|
10348
|
-
config,
|
|
10349
|
-
tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId))
|
|
10350
|
-
});
|
|
10351
|
-
const processed = turn.messages;
|
|
10352
|
-
const base = buildStatusReport(turn.state, processed, defaultCountTokens, {
|
|
10353
|
-
scope: args.scope,
|
|
10354
|
-
view: args.view,
|
|
10355
|
-
tool: args.tool,
|
|
10356
|
-
sort: args.sort,
|
|
10357
|
-
limit: args.limit
|
|
10358
|
-
});
|
|
10359
|
-
if (args.scope) return base;
|
|
10360
|
-
const nudge = turn.nudge;
|
|
10361
|
-
const ranges = viableRanges(nudge?.compressibleRanges ?? []);
|
|
10362
|
-
const protectedRanges = nudge?.protectedRanges ?? [];
|
|
10363
|
-
const extra = [];
|
|
10364
|
-
if (nudge) {
|
|
10365
|
-
extra.push("");
|
|
10366
|
-
extra.push(
|
|
10367
|
-
nudge.shouldInject ? `Nudge: ACTIVE \u2014 ${nudge.reason}` : `Nudge: idle \u2014 ${nudge.reason}`
|
|
10368
|
-
);
|
|
10728
|
+
return { path: path_, action: "failed", reason: message };
|
|
10369
10729
|
}
|
|
10370
|
-
if (ranges.length > 0 || protectedRanges.length > 0) {
|
|
10371
|
-
extra.push("");
|
|
10372
|
-
extra.push(formatRanges(ranges, protectedRanges));
|
|
10373
|
-
}
|
|
10374
|
-
const delegateUsage = getDelegateUsage();
|
|
10375
|
-
if (delegateUsage && delegateUsage.totalTokens > 0) {
|
|
10376
|
-
extra.push("");
|
|
10377
|
-
const cost = delegateUsage.cost.total;
|
|
10378
|
-
const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
|
|
10379
|
-
extra.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
|
|
10380
|
-
extra.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
|
|
10381
|
-
} else if (resolveDelegate(runtime.adapter).displayUsage === "merged") {
|
|
10382
|
-
extra.push("");
|
|
10383
|
-
extra.push("merged mode: delegate usage is included in main session totals.");
|
|
10384
|
-
} else {
|
|
10385
|
-
extra.push("");
|
|
10386
|
-
extra.push("Delegate usage: none this session.");
|
|
10387
|
-
}
|
|
10388
|
-
return extra.length > 0 ? `${base}
|
|
10389
|
-
${extra.join("\n")}` : base;
|
|
10390
10730
|
}
|
|
10391
10731
|
|
|
10392
10732
|
// src/commands.ts
|
|
@@ -10453,6 +10793,25 @@ ${text}`);
|
|
|
10453
10793
|
ctx.ui.notify(lines.join("\n"));
|
|
10454
10794
|
}
|
|
10455
10795
|
}
|
|
10796
|
+
},
|
|
10797
|
+
{
|
|
10798
|
+
name: "acp-subagents",
|
|
10799
|
+
options: {
|
|
10800
|
+
description: "Add ACP context tools (compress/decompress/search_context/acp_status) to pi-subagents' builtin agents. One-time setup \u2014 re-run after upgrading pi-subagents. Usage: /acp-subagents [installDir]",
|
|
10801
|
+
handler: async (args, ctx) => {
|
|
10802
|
+
const installDir = args.trim();
|
|
10803
|
+
const result = ensureSubagentAcpTools(void 0, installDir ? { installDir } : void 0);
|
|
10804
|
+
if (result.action === "updated") {
|
|
10805
|
+
ctx.ui.notify(`ACP tools enabled for pi-subagents agents in ${result.path}`);
|
|
10806
|
+
} else if (result.action === "skipped") {
|
|
10807
|
+
ctx.ui.notify(
|
|
10808
|
+
`Nothing to do: ${result.reason ?? ""}. Install pi-subagents (pi install npm:pi-subagents) or pass its directory: /acp-subagents <installDir>`
|
|
10809
|
+
);
|
|
10810
|
+
} else {
|
|
10811
|
+
ctx.ui.notify(`Failed to update ${result.path}: ${result.reason ?? "unknown"}`);
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10456
10815
|
}
|
|
10457
10816
|
];
|
|
10458
10817
|
}
|
|
@@ -10467,7 +10826,7 @@ async function statusReport(runtime, ctx) {
|
|
|
10467
10826
|
const modelId = ctx.model?.id ?? "default";
|
|
10468
10827
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
10469
10828
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
|
|
10470
|
-
const versionStr = "0.1.
|
|
10829
|
+
const versionStr = "0.1.46" ? `billion-context-pi@${"0.1.46"}` : void 0;
|
|
10471
10830
|
let text = buildStatusPanel({
|
|
10472
10831
|
version: versionStr,
|
|
10473
10832
|
tokenCount: sessionTokens,
|
|
@@ -10685,18 +11044,48 @@ function wireToolGuardrails(pi, runtime) {
|
|
|
10685
11044
|
}
|
|
10686
11045
|
|
|
10687
11046
|
// src/update.ts
|
|
10688
|
-
import { readFile, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
10689
|
-
import { join as
|
|
11047
|
+
import { readFile, writeFile as writeFile3, mkdir as mkdir3, access } from "fs/promises";
|
|
11048
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
10690
11049
|
import { fileURLToPath } from "url";
|
|
10691
11050
|
import { execFile } from "child_process";
|
|
10692
|
-
import { homedir as
|
|
10693
|
-
import { CONFIG_DIR_NAME as
|
|
11051
|
+
import { homedir as homedir5 } from "os";
|
|
11052
|
+
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@earendil-works/pi-coding-agent";
|
|
10694
11053
|
var PACKAGE_NAME = "billion-context-pi";
|
|
10695
11054
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
10696
11055
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
|
|
10697
11056
|
var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
|
|
10698
|
-
var
|
|
11057
|
+
var throttleFile = () => process.env.ACP_UPDATE_THROTTLE_FILE ?? join8(homedir5(), CONFIG_DIR_NAME4, "agent", ".billion-context-pi-update-check");
|
|
10699
11058
|
var updateInFlight = false;
|
|
11059
|
+
var runNpm = async (args, opts) => {
|
|
11060
|
+
return new Promise((resolve3) => {
|
|
11061
|
+
execFile(
|
|
11062
|
+
"npm",
|
|
11063
|
+
args,
|
|
11064
|
+
{ ...opts, shell: process.platform === "win32", maxBuffer: 4 * 1024 * 1024 },
|
|
11065
|
+
(err, stdout, stderr) => resolve3({
|
|
11066
|
+
code: err ? 1 : 0,
|
|
11067
|
+
stdout: String(stdout ?? ""),
|
|
11068
|
+
stderr: String(stderr ?? "")
|
|
11069
|
+
})
|
|
11070
|
+
);
|
|
11071
|
+
});
|
|
11072
|
+
};
|
|
11073
|
+
var runNpmImpl = runNpm;
|
|
11074
|
+
var runNode = (args, opts) => {
|
|
11075
|
+
return new Promise((resolve3) => {
|
|
11076
|
+
execFile(
|
|
11077
|
+
process.execPath,
|
|
11078
|
+
args,
|
|
11079
|
+
{ ...opts, maxBuffer: 4 * 1024 * 1024 },
|
|
11080
|
+
(err, stdout, stderr) => resolve3({
|
|
11081
|
+
code: err ? 1 : 0,
|
|
11082
|
+
stdout: String(stdout ?? ""),
|
|
11083
|
+
stderr: String(stderr ?? "")
|
|
11084
|
+
})
|
|
11085
|
+
);
|
|
11086
|
+
});
|
|
11087
|
+
};
|
|
11088
|
+
var runNodeImpl = runNode;
|
|
10700
11089
|
function parseVersion(v) {
|
|
10701
11090
|
return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
10702
11091
|
}
|
|
@@ -10711,7 +11100,7 @@ function isNewer(latest, current) {
|
|
|
10711
11100
|
}
|
|
10712
11101
|
async function readLastCheck() {
|
|
10713
11102
|
try {
|
|
10714
|
-
const data = await readFile(
|
|
11103
|
+
const data = await readFile(throttleFile(), "utf-8");
|
|
10715
11104
|
return parseInt(data.trim(), 10) || 0;
|
|
10716
11105
|
} catch {
|
|
10717
11106
|
return 0;
|
|
@@ -10719,14 +11108,14 @@ async function readLastCheck() {
|
|
|
10719
11108
|
}
|
|
10720
11109
|
async function writeLastCheck(timestamp) {
|
|
10721
11110
|
try {
|
|
10722
|
-
await mkdir3(dirname5(
|
|
10723
|
-
await writeFile3(
|
|
11111
|
+
await mkdir3(dirname5(throttleFile()), { recursive: true });
|
|
11112
|
+
await writeFile3(throttleFile(), String(timestamp), "utf-8");
|
|
10724
11113
|
} catch {
|
|
10725
11114
|
}
|
|
10726
11115
|
}
|
|
10727
|
-
async function readPackageJson(
|
|
11116
|
+
async function readPackageJson(path5) {
|
|
10728
11117
|
try {
|
|
10729
|
-
const data = JSON.parse(await readFile(
|
|
11118
|
+
const data = JSON.parse(await readFile(path5, "utf-8"));
|
|
10730
11119
|
return data && typeof data === "object" ? data : void 0;
|
|
10731
11120
|
} catch {
|
|
10732
11121
|
return void 0;
|
|
@@ -10744,31 +11133,118 @@ function findNpmRoot(extDir) {
|
|
|
10744
11133
|
async function findExtensionDir() {
|
|
10745
11134
|
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
10746
11135
|
for (; ; ) {
|
|
10747
|
-
const pkg = await readPackageJson(
|
|
11136
|
+
const pkg = await readPackageJson(join8(dir, "package.json"));
|
|
10748
11137
|
if (pkg?.name === PACKAGE_NAME) return dir;
|
|
10749
11138
|
const parent = dirname5(dir);
|
|
10750
11139
|
if (parent === dir) return void 0;
|
|
10751
11140
|
dir = parent;
|
|
10752
11141
|
}
|
|
10753
11142
|
}
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
const
|
|
10757
|
-
|
|
11143
|
+
function declaredEntries(pkg) {
|
|
11144
|
+
const dot = pkg.exports?.["."];
|
|
11145
|
+
const exportEntry = typeof dot === "string" ? dot : dot?.import;
|
|
11146
|
+
return [.../* @__PURE__ */ new Set([pkg.pi?.extensions?.[0], exportEntry, pkg.main])].filter((v) => typeof v === "string" && v.length > 0);
|
|
11147
|
+
}
|
|
11148
|
+
async function verifyInstall(npmDir, latest) {
|
|
11149
|
+
const dir = join8(npmDir, "node_modules", PACKAGE_NAME);
|
|
11150
|
+
const pkg = await readPackageJson(join8(dir, "package.json"));
|
|
11151
|
+
if (!pkg?.version) return { ok: false, reason: "package-json-missing" };
|
|
11152
|
+
if (pkg.version !== latest) return { ok: false, reason: `version-mismatch:${pkg.version}` };
|
|
11153
|
+
const entries = declaredEntries(pkg);
|
|
11154
|
+
if (entries.length === 0) return { ok: false, reason: "no-entry-declared" };
|
|
11155
|
+
for (const rel of entries) {
|
|
11156
|
+
try {
|
|
11157
|
+
await access(join8(dir, rel));
|
|
11158
|
+
} catch {
|
|
11159
|
+
return { ok: false, reason: `entry-missing:${rel}` };
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
const smokeEntry = pkg.pi?.extensions?.[0] ?? entries[0];
|
|
11163
|
+
if (!smokeEntry) return { ok: false, reason: "no-entry-declared" };
|
|
11164
|
+
const entry = join8(dir, smokeEntry);
|
|
11165
|
+
const SMOKE = "const{pathToFileURL}=require('node:url');import(pathToFileURL(process.argv[1]).href).then(()=>{},(e)=>{console.error(e&&e.stack||e);process.exit(1)})";
|
|
11166
|
+
const { code, stderr } = await runNodeImpl(["-e", SMOKE, entry], { timeout: 15e3 });
|
|
11167
|
+
if (code !== 0) return { ok: false, reason: `entry-import-failed:${stderr.trim().slice(-500)}` };
|
|
11168
|
+
return { ok: true };
|
|
11169
|
+
}
|
|
11170
|
+
async function autoInstallLatest(latest, extDirOverride) {
|
|
11171
|
+
if (!SEMVER_RE.test(latest)) return "failed";
|
|
11172
|
+
const extDir = extDirOverride ?? await findExtensionDir();
|
|
11173
|
+
if (!extDir) {
|
|
11174
|
+
logWarn("update", { event: "install-skip", reason: "extension-dir-not-found" });
|
|
11175
|
+
return "failed";
|
|
11176
|
+
}
|
|
10758
11177
|
const npmDir = findNpmRoot(extDir);
|
|
10759
|
-
if (!npmDir)
|
|
11178
|
+
if (!npmDir) {
|
|
11179
|
+
logWarn("update", { event: "install-skip", reason: "not-under-node-modules", extDir });
|
|
11180
|
+
return "failed";
|
|
11181
|
+
}
|
|
10760
11182
|
try {
|
|
10761
|
-
const
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
11183
|
+
const installArgs = (v) => [
|
|
11184
|
+
"install",
|
|
11185
|
+
`${PACKAGE_NAME}@${v}`,
|
|
11186
|
+
"--silent",
|
|
11187
|
+
"--no-audit",
|
|
11188
|
+
"--no-fund",
|
|
11189
|
+
"--no-save"
|
|
11190
|
+
];
|
|
11191
|
+
const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.46";
|
|
11192
|
+
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
11193
|
+
if (code !== 0) {
|
|
11194
|
+
logWarn("update", {
|
|
11195
|
+
event: "auto-install-failed",
|
|
11196
|
+
latest,
|
|
11197
|
+
npmDir,
|
|
11198
|
+
stderr: stderr.trim().slice(-2e3)
|
|
11199
|
+
});
|
|
11200
|
+
return "failed";
|
|
11201
|
+
}
|
|
11202
|
+
const verify = await verifyInstall(npmDir, latest);
|
|
11203
|
+
if (!verify.ok) {
|
|
11204
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.46";
|
|
11205
|
+
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
11206
|
+
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
11207
|
+
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
11208
|
+
return "rolled-back";
|
|
11209
|
+
}
|
|
11210
|
+
return "ok";
|
|
11211
|
+
} catch (e) {
|
|
11212
|
+
logWarn("update", {
|
|
11213
|
+
event: "auto-install-error",
|
|
11214
|
+
latest,
|
|
11215
|
+
error: e instanceof Error ? e.message : String(e)
|
|
11216
|
+
});
|
|
11217
|
+
return "failed";
|
|
11218
|
+
}
|
|
11219
|
+
}
|
|
11220
|
+
async function fetchLatestVersion() {
|
|
11221
|
+
try {
|
|
11222
|
+
const { code, stdout } = await runNpmImpl(["view", PACKAGE_NAME, "version"], {
|
|
11223
|
+
timeout: 2e4
|
|
10768
11224
|
});
|
|
10769
|
-
|
|
11225
|
+
if (code === 0) {
|
|
11226
|
+
const v = stdout.trim().split("\n").map((s) => s.trim()).filter(Boolean).pop();
|
|
11227
|
+
if (v && SEMVER_RE.test(v)) return v;
|
|
11228
|
+
}
|
|
10770
11229
|
} catch {
|
|
10771
|
-
|
|
11230
|
+
}
|
|
11231
|
+
try {
|
|
11232
|
+
const res = await fetch(REGISTRY_URL, {
|
|
11233
|
+
signal: AbortSignal.timeout(1e4),
|
|
11234
|
+
headers: { Accept: "application/json" }
|
|
11235
|
+
});
|
|
11236
|
+
if (!res.ok) {
|
|
11237
|
+
logWarn("update", { event: "check-http", status: res.status });
|
|
11238
|
+
return void 0;
|
|
11239
|
+
}
|
|
11240
|
+
const data = await res.json();
|
|
11241
|
+
return data.version;
|
|
11242
|
+
} catch (e) {
|
|
11243
|
+
logWarn("update", {
|
|
11244
|
+
event: "check-fetch-error",
|
|
11245
|
+
error: e instanceof Error ? e.message : String(e)
|
|
11246
|
+
});
|
|
11247
|
+
return void 0;
|
|
10772
11248
|
}
|
|
10773
11249
|
}
|
|
10774
11250
|
async function checkForUpdate(autoUpdate, notify) {
|
|
@@ -10784,18 +11260,9 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10784
11260
|
if (now - lastCheck < CHECK_INTERVAL_MS) return;
|
|
10785
11261
|
await writeLastCheck(now);
|
|
10786
11262
|
const runtimeVersion = await getRuntimeVersion();
|
|
10787
|
-
const
|
|
10788
|
-
signal: AbortSignal.timeout(5e3),
|
|
10789
|
-
headers: { Accept: "application/json" }
|
|
10790
|
-
});
|
|
10791
|
-
if (!res.ok) {
|
|
10792
|
-
logWarn("update", { event: "check-http", status: res.status });
|
|
10793
|
-
return;
|
|
10794
|
-
}
|
|
10795
|
-
const data = await res.json();
|
|
10796
|
-
const latest = data.version;
|
|
11263
|
+
const latest = await fetchLatestVersion();
|
|
10797
11264
|
if (!latest) return;
|
|
10798
|
-
const current = runtimeVersion ?? "0.1.
|
|
11265
|
+
const current = runtimeVersion ?? "0.1.46";
|
|
10799
11266
|
const hasUpdate = isNewer(latest, current);
|
|
10800
11267
|
debug.event("update-check", {
|
|
10801
11268
|
current,
|
|
@@ -10804,13 +11271,17 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10804
11271
|
});
|
|
10805
11272
|
logInfo("update", { event: "check", current, latest, hasUpdate });
|
|
10806
11273
|
if (hasUpdate) {
|
|
10807
|
-
const
|
|
10808
|
-
if (
|
|
11274
|
+
const outcome = await autoInstallLatest(latest);
|
|
11275
|
+
if (outcome === "ok" && notify) {
|
|
10809
11276
|
notify(
|
|
10810
11277
|
`\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart Pi to finish.\x1B[0m`
|
|
10811
11278
|
);
|
|
10812
11279
|
logInfo("update", { event: "auto-installed", from: current, to: latest });
|
|
10813
|
-
} else if (
|
|
11280
|
+
} else if (outcome === "rolled-back" && notify) {
|
|
11281
|
+
notify(
|
|
11282
|
+
`\x1B[33mACP ${latest} failed verification and was rolled back. Keeping ${current}. A later release will auto-update.\x1B[0m`
|
|
11283
|
+
);
|
|
11284
|
+
} else if (notify) {
|
|
10814
11285
|
notify(
|
|
10815
11286
|
`${PACKAGE_NAME} ${latest} available (you have ${current}). Run: pi update --extension npm:${PACKAGE_NAME}`
|
|
10816
11287
|
);
|
|
@@ -10825,140 +11296,10 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
10825
11296
|
async function getRuntimeVersion() {
|
|
10826
11297
|
const extDir = await findExtensionDir();
|
|
10827
11298
|
if (!extDir) return void 0;
|
|
10828
|
-
const pkg = await readPackageJson(
|
|
11299
|
+
const pkg = await readPackageJson(join8(extDir, "package.json"));
|
|
10829
11300
|
return pkg?.version;
|
|
10830
11301
|
}
|
|
10831
11302
|
|
|
10832
|
-
// src/setup-subagent-tools.ts
|
|
10833
|
-
import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
|
|
10834
|
-
import { existsSync as existsSync4 } from "fs";
|
|
10835
|
-
import { homedir as homedir5 } from "os";
|
|
10836
|
-
import { join as join8 } from "path";
|
|
10837
|
-
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@earendil-works/pi-coding-agent";
|
|
10838
|
-
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
10839
|
-
var BUILTIN_DEFAULT_TOOLS = {
|
|
10840
|
-
advisor: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
10841
|
-
"context-builder": ["read", "grep", "find", "ls", "bash", "write", "web_search", "intercom"],
|
|
10842
|
-
delegate: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
|
|
10843
|
-
oracle: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
10844
|
-
planner: ["read", "grep", "find", "ls", "intercom"],
|
|
10845
|
-
researcher: ["read", "write", "web_search", "fetch_content", "get_search_content", "intercom"],
|
|
10846
|
-
reviewer: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
|
|
10847
|
-
scout: ["read", "grep", "find", "ls", "bash", "write", "intercom"],
|
|
10848
|
-
worker: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"]
|
|
10849
|
-
};
|
|
10850
|
-
function resolveAgentDir() {
|
|
10851
|
-
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
10852
|
-
if (configured === "~") return homedir5();
|
|
10853
|
-
if (configured?.startsWith("~/")) return join8(homedir5(), configured.slice(2));
|
|
10854
|
-
return configured || join8(homedir5(), CONFIG_DIR_NAME4, "agent");
|
|
10855
|
-
}
|
|
10856
|
-
function desiredTools(existing, name) {
|
|
10857
|
-
const base = Array.isArray(existing?.tools) && existing.tools.length > 0 ? [...existing.tools] : [...BUILTIN_DEFAULT_TOOLS[name] ?? []];
|
|
10858
|
-
const hasAll = ACP_TOOLS2.every((t) => base.includes(t));
|
|
10859
|
-
if (hasAll) return { tools: base, changed: false };
|
|
10860
|
-
for (const t of ACP_TOOLS2) if (!base.includes(t)) base.push(t);
|
|
10861
|
-
return { tools: base, changed: true };
|
|
10862
|
-
}
|
|
10863
|
-
async function ensureSubagentAcpTools(settingsPath) {
|
|
10864
|
-
const path4 = settingsPath ?? join8(resolveAgentDir(), "settings.json");
|
|
10865
|
-
let raw;
|
|
10866
|
-
let mtimeMs;
|
|
10867
|
-
try {
|
|
10868
|
-
raw = await readFile2(path4, "utf-8");
|
|
10869
|
-
mtimeMs = (await stat(path4)).mtimeMs;
|
|
10870
|
-
} catch {
|
|
10871
|
-
return { path: path4, action: "skipped", reason: "settings.json not found; will retry next session" };
|
|
10872
|
-
}
|
|
10873
|
-
let settings2;
|
|
10874
|
-
try {
|
|
10875
|
-
const parsed = JSON.parse(raw);
|
|
10876
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10877
|
-
return { path: path4, action: "failed", reason: "settings.json root is not a JSON object" };
|
|
10878
|
-
}
|
|
10879
|
-
settings2 = parsed;
|
|
10880
|
-
} catch (e) {
|
|
10881
|
-
return { path: path4, action: "failed", reason: `settings.json is not valid JSON: ${e.message}` };
|
|
10882
|
-
}
|
|
10883
|
-
const subagents = typeof settings2.subagents === "object" && settings2.subagents !== null && !Array.isArray(settings2.subagents) ? { ...settings2.subagents } : {};
|
|
10884
|
-
const overridesRaw = typeof subagents.agentOverrides === "object" && subagents.agentOverrides !== null && !Array.isArray(subagents.agentOverrides) ? { ...subagents.agentOverrides } : {};
|
|
10885
|
-
const updatedOverrides = { ...overridesRaw };
|
|
10886
|
-
let anyChanged = false;
|
|
10887
|
-
for (const name of Object.keys(BUILTIN_DEFAULT_TOOLS)) {
|
|
10888
|
-
const existing = overridesRaw[name] ?? void 0;
|
|
10889
|
-
const { tools, changed } = desiredTools(existing, name);
|
|
10890
|
-
if (changed) {
|
|
10891
|
-
updatedOverrides[name] = { ...existing ?? {}, tools };
|
|
10892
|
-
anyChanged = true;
|
|
10893
|
-
}
|
|
10894
|
-
}
|
|
10895
|
-
if (!anyChanged) {
|
|
10896
|
-
return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
|
|
10897
|
-
}
|
|
10898
|
-
const backupPath = `${path4}.acp-bak`;
|
|
10899
|
-
if (!existsSync4(backupPath)) {
|
|
10900
|
-
try {
|
|
10901
|
-
await copyFile(path4, backupPath);
|
|
10902
|
-
} catch {
|
|
10903
|
-
}
|
|
10904
|
-
}
|
|
10905
|
-
try {
|
|
10906
|
-
if ((await stat(path4)).mtimeMs !== mtimeMs) {
|
|
10907
|
-
return { path: path4, action: "skipped", reason: "settings.json changed during setup (concurrent write); will retry next session" };
|
|
10908
|
-
}
|
|
10909
|
-
} catch {
|
|
10910
|
-
return { path: path4, action: "failed", reason: "settings.json disappeared during setup" };
|
|
10911
|
-
}
|
|
10912
|
-
const next = {
|
|
10913
|
-
...settings2,
|
|
10914
|
-
subagents: { ...subagents, agentOverrides: updatedOverrides }
|
|
10915
|
-
};
|
|
10916
|
-
const tmpPath = `${path4}.tmp-${process.pid}`;
|
|
10917
|
-
try {
|
|
10918
|
-
await writeFile4(tmpPath, `${JSON.stringify(next, null, 2)}
|
|
10919
|
-
`, "utf-8");
|
|
10920
|
-
await rename(tmpPath, path4);
|
|
10921
|
-
} catch (e) {
|
|
10922
|
-
return { path: path4, action: "failed", reason: `write failed: ${e.message}` };
|
|
10923
|
-
}
|
|
10924
|
-
try {
|
|
10925
|
-
const verify = JSON.parse(await readFile2(path4, "utf-8"));
|
|
10926
|
-
const sub = verify.subagents;
|
|
10927
|
-
const v = sub?.agentOverrides;
|
|
10928
|
-
if (typeof v !== "object" || v === null) throw new Error("agentOverrides missing after write");
|
|
10929
|
-
for (const name of Object.keys(BUILTIN_DEFAULT_TOOLS)) {
|
|
10930
|
-
const tools = v[name]?.tools;
|
|
10931
|
-
if (!Array.isArray(tools) || !ACP_TOOLS2.every((t) => tools.includes(t))) {
|
|
10932
|
-
throw new Error(`${name} missing ACP tools after write`);
|
|
10933
|
-
}
|
|
10934
|
-
}
|
|
10935
|
-
} catch (e) {
|
|
10936
|
-
try {
|
|
10937
|
-
await copyFile(backupPath, path4);
|
|
10938
|
-
} catch {
|
|
10939
|
-
}
|
|
10940
|
-
return { path: path4, action: "failed", reason: `verification failed; restored backup: ${e.message}` };
|
|
10941
|
-
}
|
|
10942
|
-
return { path: path4, action: "updated" };
|
|
10943
|
-
}
|
|
10944
|
-
async function runSetupAndNotify(notify) {
|
|
10945
|
-
try {
|
|
10946
|
-
const result = await ensureSubagentAcpTools();
|
|
10947
|
-
debug.event("setup-subagent-tools", { action: result.action, reason: result.reason });
|
|
10948
|
-
if (result.action === "updated" && notify) {
|
|
10949
|
-
notify(`ACP: enabled context tools (compress/decompress/search_context/acp_status) for subagents`);
|
|
10950
|
-
}
|
|
10951
|
-
if (result.action === "failed") {
|
|
10952
|
-
logWarn("setup", { event: "subagent-tools", action: result.action, reason: result.reason });
|
|
10953
|
-
}
|
|
10954
|
-
return result;
|
|
10955
|
-
} catch (e) {
|
|
10956
|
-
debug.event("setup-subagent-tools-error", { msg: String(e) });
|
|
10957
|
-
logError("setup", { event: "subagent-tools-error", error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack ?? "" : "" });
|
|
10958
|
-
return { path: "", action: "failed", reason: String(e) };
|
|
10959
|
-
}
|
|
10960
|
-
}
|
|
10961
|
-
|
|
10962
11303
|
// src/index.ts
|
|
10963
11304
|
function createAcpExtension(adapter = {}) {
|
|
10964
11305
|
return (pi) => {
|
|
@@ -10996,7 +11337,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
10996
11337
|
const sid = ctx.sessionManager.getSessionId();
|
|
10997
11338
|
runtime.clearSessionTracking(sid);
|
|
10998
11339
|
const modelInfo = ctx.model;
|
|
10999
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
11340
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.46" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
11000
11341
|
try {
|
|
11001
11342
|
await runtime.reloadConfig(ctx.cwd);
|
|
11002
11343
|
setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
|
|
@@ -11014,10 +11355,10 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
11014
11355
|
pi.registerTool(makeDelegateWaitTool(pi));
|
|
11015
11356
|
pi.registerTool(makeDelegateCancelTool(pi));
|
|
11016
11357
|
}
|
|
11017
|
-
|
|
11358
|
+
const updateCheck = checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
11018
11359
|
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
11019
11360
|
});
|
|
11020
|
-
|
|
11361
|
+
if (!ctx.hasUI) await updateCheck;
|
|
11021
11362
|
delegateStatusWidget.setContext(ctx, runningRunsSnapshot);
|
|
11022
11363
|
});
|
|
11023
11364
|
pi.on("session_shutdown", () => {
|
|
@@ -11119,10 +11460,13 @@ function wireContextTransform(pi, runtime) {
|
|
|
11119
11460
|
const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
|
|
11120
11461
|
const debugOn2 = debug.enabled;
|
|
11121
11462
|
const turnKey = lastUserMessageId(entries) ?? sid;
|
|
11463
|
+
const compressOutcomes = collectCompressOutcomes(entries, turnStartIndex(entries));
|
|
11464
|
+
const outcome = compressOutcomes.length > 0 ? runtime.noteCompressOutcomes(turnKey, compressOutcomes) : null;
|
|
11122
11465
|
if (turn.nudge?.shouldInject) {
|
|
11123
11466
|
const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
|
|
11124
11467
|
turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
|
|
11125
|
-
const
|
|
11468
|
+
const retryCapped = runtime.compressRetryCappedFor(turnKey);
|
|
11469
|
+
const alreadyShown = retryCapped || !emergency && runtime.nudgeShownFor(turnKey);
|
|
11126
11470
|
if (!alreadyShown) {
|
|
11127
11471
|
rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts));
|
|
11128
11472
|
const rendered = renderNudgeText(turn.nudge, runtime.prompts);
|
|
@@ -11143,9 +11487,7 @@ ${rendered.text}${example}`);
|
|
|
11143
11487
|
debug.event("nudge-suppressed", { sid: ctx.sessionManager.getSessionId(), turnKey, reason: turn.nudge.reason });
|
|
11144
11488
|
}
|
|
11145
11489
|
}
|
|
11146
|
-
|
|
11147
|
-
if (compressOutcomes.length > 0) {
|
|
11148
|
-
const outcome = runtime.noteCompressOutcomes(turnKey, compressOutcomes);
|
|
11490
|
+
if (outcome !== null) {
|
|
11149
11491
|
const failed = outcome.retryFor !== null ? compressOutcomes.find((o) => o.toolCallId === outcome.retryFor) : void 0;
|
|
11150
11492
|
if (failed) {
|
|
11151
11493
|
rebuilt.push(compressRetryMessage(failed.text, outcome.count, MAX_COMPRESS_ATTEMPTS));
|
|
@@ -11160,9 +11502,10 @@ ${rendered.text}${example}`);
|
|
|
11160
11502
|
}
|
|
11161
11503
|
}
|
|
11162
11504
|
debug.event("context-out", { outMsgs: rebuilt.length, injected: turn.nudge?.shouldInject ?? false, emergency: turn.nudge?.breakdown?.emergencyOverride === 1 });
|
|
11163
|
-
|
|
11505
|
+
const updateCheck = checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
11164
11506
|
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
11165
11507
|
});
|
|
11508
|
+
if (!ctx.hasUI) await updateCheck;
|
|
11166
11509
|
return { messages: rebuilt };
|
|
11167
11510
|
} catch (e) {
|
|
11168
11511
|
logThrow("context", e, { sid, phase: "transform" });
|
|
@@ -11288,7 +11631,7 @@ function collectCompressOutcomes(entries, startIndex) {
|
|
|
11288
11631
|
const m = entry.message;
|
|
11289
11632
|
if (m.role !== "toolResult" || m.toolName !== "compress" || !m.toolCallId) continue;
|
|
11290
11633
|
const text = extractText(m.content);
|
|
11291
|
-
out.push({ toolCallId: m.toolCallId, isError: m.isError === true, success: m.isError !== true && isCompressSuccessText(text), text });
|
|
11634
|
+
out.push({ toolCallId: m.toolCallId, isError: m.isError === true, success: m.isError !== true && isCompressSuccessText(text), noop: m.isError !== true && isCompressNoopText(text), text });
|
|
11292
11635
|
}
|
|
11293
11636
|
return out;
|
|
11294
11637
|
}
|
|
@@ -11304,7 +11647,8 @@ function compressRetryMessage(errorText, attempt, maxAttempts) {
|
|
|
11304
11647
|
"- content must be an ARRAY of { startId, endId, summary } objects (topic optional) \u2014 not a JSON-encoded string.",
|
|
11305
11648
|
'- Example: compress({ content: [{ startId: "m00005", endId: "m00080", summary: "..." }] })',
|
|
11306
11649
|
'- startId/endId are the mNNNNN refs from the <acp> tags (or block ids like "b3").',
|
|
11307
|
-
attempt >= maxAttempts - 1 ? "- This is your LAST retry for this turn \u2014 if it fails again, compression pauses until the next user message." : null
|
|
11650
|
+
attempt >= maxAttempts - 1 ? "- This is your LAST retry for this turn \u2014 if it fails again, compression pauses until the next user message." : null,
|
|
11651
|
+
"- If ranges were skipped (already compressed / too small), do NOT retry the same refs \u2014 run acp_status and use its CURRENT compressible ranges."
|
|
11308
11652
|
].filter((l) => l !== null).join("\n");
|
|
11309
11653
|
return { role: "user", content: [{ type: "text", text }], timestamp: Date.now() };
|
|
11310
11654
|
}
|