billion-context-dsh 0.1.4 → 0.1.6
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.en.md +10 -8
- package/README.md +10 -8
- package/dist/config.d.ts +6 -2
- package/dist/index.d.ts +37 -6
- package/dist/index.js +379 -70
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +8 -0
- package/dist/nudge.d.ts +7 -1
- package/dist/region.d.ts +88 -1
- package/dist/state.d.ts +7 -0
- package/dist/system-prompt.d.ts +1 -1
- package/dist/tools.d.ts +4 -0
- package/dist/window.d.ts +32 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,42 +7,6 @@ import { createCore } from "acp-kernel";
|
|
|
7
7
|
|
|
8
8
|
// src/state.ts
|
|
9
9
|
import { createInitialState } from "acp-kernel";
|
|
10
|
-
var AcpStateStore = class {
|
|
11
|
-
states = /* @__PURE__ */ new Map();
|
|
12
|
-
/** Kernel state for one session, initialised on first access. */
|
|
13
|
-
stateFor(session) {
|
|
14
|
-
const id = session.id;
|
|
15
|
-
const existing = this.states.get(id);
|
|
16
|
-
if (existing !== void 0) return existing;
|
|
17
|
-
const state = createInitialState();
|
|
18
|
-
this.states.set(id, state);
|
|
19
|
-
return state;
|
|
20
|
-
}
|
|
21
|
-
set(session, state) {
|
|
22
|
-
this.states.set(session.id, state);
|
|
23
|
-
}
|
|
24
|
-
delete(session) {
|
|
25
|
-
this.states.delete(session.id);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// src/tools.ts
|
|
30
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
31
|
-
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
32
|
-
|
|
33
|
-
// src/config.ts
|
|
34
|
-
import { defaultConfig } from "acp-kernel";
|
|
35
|
-
function kernelConfigFor(input) {
|
|
36
|
-
const nudgePatch = {};
|
|
37
|
-
if (input.nudgeMinContextLimitPct !== void 0) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct;
|
|
38
|
-
if (input.nudgeMaxContextLimitPct !== void 0) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct;
|
|
39
|
-
if (input.nudgeEmergencyThresholdPct !== void 0) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct;
|
|
40
|
-
const overrides = { ...input.coreOverrides };
|
|
41
|
-
if (Object.keys(nudgePatch).length > 0) {
|
|
42
|
-
overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch };
|
|
43
|
-
}
|
|
44
|
-
return defaultConfig(input.modelContextLimit, overrides);
|
|
45
|
-
}
|
|
46
10
|
|
|
47
11
|
// src/region.ts
|
|
48
12
|
import { randomUUID } from "crypto";
|
|
@@ -145,6 +109,9 @@ function eventsToCoreMessages(events) {
|
|
|
145
109
|
function surfaceEventsOf(session) {
|
|
146
110
|
return session.surface.nodes.map((seq) => session.events[seq]).filter((event) => event !== void 0);
|
|
147
111
|
}
|
|
112
|
+
function allLogMessages(session) {
|
|
113
|
+
return eventsToCoreMessages(session.events);
|
|
114
|
+
}
|
|
148
115
|
function extractEventText(event) {
|
|
149
116
|
switch (event.type) {
|
|
150
117
|
case "user/message":
|
|
@@ -245,6 +212,9 @@ function shadowedSeqsOf(session, start, end) {
|
|
|
245
212
|
const endIdx = nodes.indexOf(end);
|
|
246
213
|
return nodes.slice(startIdx, endIdx + 1);
|
|
247
214
|
}
|
|
215
|
+
function readCompactionSummary(event) {
|
|
216
|
+
return event.data;
|
|
217
|
+
}
|
|
248
218
|
function runCompactionTransaction(session, input) {
|
|
249
219
|
assertNoActiveCompaction(session.events);
|
|
250
220
|
const turn = findOpenTurn(session.events);
|
|
@@ -258,7 +228,12 @@ function runCompactionTransaction(session, input) {
|
|
|
258
228
|
shadowedSeqs: [...input.shadowedSeqs],
|
|
259
229
|
shadowedTokenCount: input.shadowedTokenCount,
|
|
260
230
|
provider: input.provider,
|
|
261
|
-
model: input.model
|
|
231
|
+
model: input.model,
|
|
232
|
+
tier: input.tier ?? 1,
|
|
233
|
+
...input.kernelBlockId === void 0 ? {} : { kernelBlockId: input.kernelBlockId },
|
|
234
|
+
...input.parentBlockIds === void 0 || input.parentBlockIds.length === 0 ? {} : { parentBlockIds: [...input.parentBlockIds] },
|
|
235
|
+
...input.directMessageIds === void 0 ? {} : { directMessageIds: [...input.directMessageIds] },
|
|
236
|
+
...input.effectiveMessageIds === void 0 ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }
|
|
262
237
|
}).seq);
|
|
263
238
|
const message = createUserMessage({
|
|
264
239
|
content: input.summary,
|
|
@@ -271,11 +246,19 @@ function runCompactionTransaction(session, input) {
|
|
|
271
246
|
seqs.push(session.append("compaction/end", { compactionId, turn }).seq);
|
|
272
247
|
return { compactionId, seqs };
|
|
273
248
|
}
|
|
249
|
+
function summarySeqOfCompaction(events, compactionId) {
|
|
250
|
+
for (const event of events) {
|
|
251
|
+
if (event.type !== "user/message") continue;
|
|
252
|
+
const source = event.data.source;
|
|
253
|
+
if (source?.plugin === "compact" && source.compactionId === compactionId) return event.seq;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
274
257
|
function rebuildBlockLedger(events) {
|
|
275
258
|
const ledger = [];
|
|
276
259
|
for (const event of events) {
|
|
277
260
|
if (event.type !== "compaction/summary") continue;
|
|
278
|
-
const data = event
|
|
261
|
+
const data = readCompactionSummary(event);
|
|
279
262
|
let shadowedTokenCount = data.shadowedTokenCount;
|
|
280
263
|
if (shadowedTokenCount === 0) {
|
|
281
264
|
shadowedTokenCount = 0;
|
|
@@ -284,13 +267,25 @@ function rebuildBlockLedger(events) {
|
|
|
284
267
|
if (original !== void 0) shadowedTokenCount += defaultCountTokens(extractEventText(original));
|
|
285
268
|
}
|
|
286
269
|
}
|
|
270
|
+
const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1;
|
|
271
|
+
const parentBlockIds = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : [];
|
|
272
|
+
const directMessageIds = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : void 0;
|
|
273
|
+
const effectiveMessageIds = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : void 0;
|
|
274
|
+
const summarySeq = summarySeqOfCompaction(events, data.compactionId);
|
|
287
275
|
ledger.push({
|
|
288
276
|
blockId: data.compactionId,
|
|
289
277
|
summary: extractText(data.summary),
|
|
290
278
|
shadowedSeqs: [...data.shadowedSeqs],
|
|
291
279
|
shadowedTokenCount,
|
|
292
280
|
start: data.shadowedRange.start,
|
|
293
|
-
end: data.shadowedRange.end
|
|
281
|
+
end: data.shadowedRange.end,
|
|
282
|
+
tier,
|
|
283
|
+
parentBlockIds,
|
|
284
|
+
...typeof data.kernelBlockId === "string" ? { kernelBlockId: data.kernelBlockId } : {},
|
|
285
|
+
...summarySeq === null ? {} : { summarySeq },
|
|
286
|
+
...directMessageIds === void 0 ? {} : { directMessageIds },
|
|
287
|
+
...effectiveMessageIds === void 0 ? {} : { effectiveMessageIds },
|
|
288
|
+
createdAt: event.time
|
|
294
289
|
});
|
|
295
290
|
}
|
|
296
291
|
return ledger;
|
|
@@ -354,6 +349,206 @@ function surfaceSummary(session) {
|
|
|
354
349
|
const last = nodes[nodes.length - 1];
|
|
355
350
|
return `${nodes.length} nodes, seqs ${first}..${last}`;
|
|
356
351
|
}
|
|
352
|
+
function blockRegistry(session) {
|
|
353
|
+
const ledger = rebuildBlockLedger(session.events);
|
|
354
|
+
const kernelIdOf = /* @__PURE__ */ new Map();
|
|
355
|
+
const raw = [];
|
|
356
|
+
let next = 1;
|
|
357
|
+
for (const entry of ledger) {
|
|
358
|
+
let kernelBlockId;
|
|
359
|
+
if (entry.kernelBlockId !== void 0 && /^b\d+$/.test(entry.kernelBlockId)) {
|
|
360
|
+
kernelBlockId = entry.kernelBlockId;
|
|
361
|
+
const num = Number(kernelBlockId.slice(1));
|
|
362
|
+
if (Number.isInteger(num)) next = Math.max(next, num + 1);
|
|
363
|
+
} else {
|
|
364
|
+
kernelBlockId = `b${next}`;
|
|
365
|
+
next += 1;
|
|
366
|
+
}
|
|
367
|
+
kernelIdOf.set(entry.blockId, kernelBlockId);
|
|
368
|
+
raw.push({
|
|
369
|
+
blockId: entry.blockId,
|
|
370
|
+
kernelBlockId,
|
|
371
|
+
tier: entry.tier,
|
|
372
|
+
summarySeq: entry.summarySeq ?? null,
|
|
373
|
+
active: true,
|
|
374
|
+
parentBlockIds: [...entry.parentBlockIds]
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
378
|
+
for (const entry of raw) {
|
|
379
|
+
for (const parent of entry.parentBlockIds) consumed.add(parent);
|
|
380
|
+
}
|
|
381
|
+
return raw.map((entry) => ({
|
|
382
|
+
...entry,
|
|
383
|
+
active: !consumed.has(entry.blockId)
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
function blockRefForSummarySeq(session, seq) {
|
|
387
|
+
const event = session.events[seq];
|
|
388
|
+
if (event?.type !== "user/message") return null;
|
|
389
|
+
const source = event.data.source;
|
|
390
|
+
if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
|
|
391
|
+
const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId);
|
|
392
|
+
if (entry === void 0) return null;
|
|
393
|
+
return entry.kernelBlockId;
|
|
394
|
+
}
|
|
395
|
+
function compactionIdsOfKernelBlocks(session, kernelBlockIds) {
|
|
396
|
+
if (kernelBlockIds.length === 0) return [];
|
|
397
|
+
const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]));
|
|
398
|
+
return kernelBlockIds.map((id) => byKernel.get(id)).filter((id) => id !== void 0);
|
|
399
|
+
}
|
|
400
|
+
function summarySeqOfKernelBlock(session, kernelBlockId) {
|
|
401
|
+
const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId);
|
|
402
|
+
return entry?.active ? entry.summarySeq : null;
|
|
403
|
+
}
|
|
404
|
+
function checkpointBlockIdOf(events, seq) {
|
|
405
|
+
const event = events[seq];
|
|
406
|
+
if (event?.type !== "user/message") return null;
|
|
407
|
+
const source = event.data.source;
|
|
408
|
+
if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
|
|
409
|
+
return source.compactionId;
|
|
410
|
+
}
|
|
411
|
+
function expandShadowedSeqs(session, blockId) {
|
|
412
|
+
const ledger = rebuildBlockLedger(session.events);
|
|
413
|
+
const byId = new Map(ledger.map((entry) => [entry.blockId, entry]));
|
|
414
|
+
const root = byId.get(blockId);
|
|
415
|
+
if (root === void 0) return [];
|
|
416
|
+
const out = [];
|
|
417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
418
|
+
const visit = (entry) => {
|
|
419
|
+
if (seen.has(entry.blockId)) return;
|
|
420
|
+
seen.add(entry.blockId);
|
|
421
|
+
for (const seq of entry.shadowedSeqs) {
|
|
422
|
+
const childId = checkpointBlockIdOf(session.events, seq);
|
|
423
|
+
const child = childId === null ? void 0 : byId.get(childId);
|
|
424
|
+
if (child !== void 0) visit(child);
|
|
425
|
+
else out.push(seq);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
visit(root);
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/state.ts
|
|
433
|
+
function rebuildKernelBlocks(events) {
|
|
434
|
+
const ledger = rebuildBlockLedger(events);
|
|
435
|
+
if (ledger.length === 0) return [];
|
|
436
|
+
const kernelIdOf = /* @__PURE__ */ new Map();
|
|
437
|
+
const parentKernelIds = /* @__PURE__ */ new Map();
|
|
438
|
+
let next = 1;
|
|
439
|
+
for (const entry of ledger) {
|
|
440
|
+
let kernelBlockId;
|
|
441
|
+
if (entry.kernelBlockId !== void 0 && /^b\d+$/.test(entry.kernelBlockId)) {
|
|
442
|
+
kernelBlockId = entry.kernelBlockId;
|
|
443
|
+
const num = Number(kernelBlockId.slice(1));
|
|
444
|
+
if (Number.isInteger(num)) next = Math.max(next, num + 1);
|
|
445
|
+
} else {
|
|
446
|
+
kernelBlockId = `b${next}`;
|
|
447
|
+
next += 1;
|
|
448
|
+
}
|
|
449
|
+
kernelIdOf.set(entry.blockId, kernelBlockId);
|
|
450
|
+
parentKernelIds.set(
|
|
451
|
+
entry.blockId,
|
|
452
|
+
entry.parentBlockIds.map((parent) => kernelIdOf.get(parent)).filter((id) => id !== void 0)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
456
|
+
for (const entry of ledger) {
|
|
457
|
+
for (const parent of entry.parentBlockIds) consumed.add(parent);
|
|
458
|
+
}
|
|
459
|
+
const blocks = [];
|
|
460
|
+
for (const entry of ledger) {
|
|
461
|
+
const blockId = kernelIdOf.get(entry.blockId);
|
|
462
|
+
const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)];
|
|
463
|
+
const effective = entry.effectiveMessageIds ?? (entry.tier > 1 ? entry.summarySeq === void 0 ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)] : [...entry.shadowedSeqs.map(String)]);
|
|
464
|
+
blocks.push({
|
|
465
|
+
blockId,
|
|
466
|
+
runId: `r${blocks.length + 1}`,
|
|
467
|
+
tier: entry.tier,
|
|
468
|
+
summary: entry.summary,
|
|
469
|
+
directMessageIds: [...direct],
|
|
470
|
+
effectiveMessageIds: [...effective],
|
|
471
|
+
directBlockIds: parentKernelIds.get(entry.blockId) ?? [],
|
|
472
|
+
compressedTokens: entry.shadowedTokenCount,
|
|
473
|
+
createdAt: entry.createdAt,
|
|
474
|
+
survivedCount: 0,
|
|
475
|
+
generation: "young",
|
|
476
|
+
active: !consumed.has(entry.blockId)
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return blocks;
|
|
480
|
+
}
|
|
481
|
+
function nextBlockIdAfter(events) {
|
|
482
|
+
const blocks = rebuildKernelBlocks(events);
|
|
483
|
+
let max = 0;
|
|
484
|
+
for (const block of blocks) {
|
|
485
|
+
const num = Number(block.blockId.slice(1));
|
|
486
|
+
if (Number.isInteger(num)) max = Math.max(max, num);
|
|
487
|
+
}
|
|
488
|
+
return max + 1;
|
|
489
|
+
}
|
|
490
|
+
var AcpStateStore = class {
|
|
491
|
+
states = /* @__PURE__ */ new Map();
|
|
492
|
+
/** Kernel state for one session, initialised on first access. */
|
|
493
|
+
stateFor(session) {
|
|
494
|
+
const id = session.id;
|
|
495
|
+
const existing = this.states.get(id);
|
|
496
|
+
if (existing !== void 0) return existing;
|
|
497
|
+
const state = createInitialState();
|
|
498
|
+
if (session.events.some((event) => event.type === "compaction/summary")) {
|
|
499
|
+
state.blocks = rebuildKernelBlocks(session.events);
|
|
500
|
+
state.nextBlockId = nextBlockIdAfter(session.events);
|
|
501
|
+
}
|
|
502
|
+
this.states.set(id, state);
|
|
503
|
+
return state;
|
|
504
|
+
}
|
|
505
|
+
set(session, state) {
|
|
506
|
+
this.states.set(session.id, state);
|
|
507
|
+
}
|
|
508
|
+
delete(session) {
|
|
509
|
+
this.states.delete(session.id);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// src/tools.ts
|
|
514
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
515
|
+
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
516
|
+
|
|
517
|
+
// src/config.ts
|
|
518
|
+
import { defaultConfig } from "acp-kernel";
|
|
519
|
+
function kernelConfigFor(input) {
|
|
520
|
+
const nudgePatch = {};
|
|
521
|
+
if (input.nudgeMinContextLimitPct !== void 0) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct;
|
|
522
|
+
if (input.nudgeMaxContextLimitPct !== void 0) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct;
|
|
523
|
+
if (input.nudgeEmergencyThresholdPct !== void 0) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct;
|
|
524
|
+
const overrides = { ...input.coreOverrides };
|
|
525
|
+
if (Object.keys(nudgePatch).length > 0) {
|
|
526
|
+
overrides.nudge = { ...defaultConfig(input.modelContextLimit).nudge, ...nudgePatch };
|
|
527
|
+
}
|
|
528
|
+
return defaultConfig(input.modelContextLimit, overrides);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/window.ts
|
|
532
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
533
|
+
function windowSourceLabel(window) {
|
|
534
|
+
if (window.source === "explicit") return "configured";
|
|
535
|
+
if (window.source === "auto") {
|
|
536
|
+
return `auto-detected from ${window.provider ?? "?"}/${window.model ?? "?"}`;
|
|
537
|
+
}
|
|
538
|
+
return "default (auto-detection unavailable)";
|
|
539
|
+
}
|
|
540
|
+
async function detectContextWindow(agent, provider, model) {
|
|
541
|
+
const llm = agent.ctx?.get?.("llm");
|
|
542
|
+
if (llm?.resolveModelInfo === void 0) return null;
|
|
543
|
+
try {
|
|
544
|
+
const info = await llm.resolveModelInfo(provider, model);
|
|
545
|
+
const window = info?.context?.contextWindow;
|
|
546
|
+
if (typeof window === "number" && Number.isInteger(window) && window > 0) return window;
|
|
547
|
+
return null;
|
|
548
|
+
} catch {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
357
552
|
|
|
358
553
|
// src/tools.ts
|
|
359
554
|
function textOutput() {
|
|
@@ -412,8 +607,9 @@ async function handleCompress(env, args, exec) {
|
|
|
412
607
|
const agent = requireAgent(exec);
|
|
413
608
|
const session = agent.session;
|
|
414
609
|
const state = env.store.stateFor(session);
|
|
415
|
-
const coreMessages =
|
|
416
|
-
const
|
|
610
|
+
const coreMessages = allLogMessages(session);
|
|
611
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
612
|
+
const tokenCount = surfaceMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
417
613
|
const config = kernelConfigFor(env);
|
|
418
614
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
419
615
|
env.store.set(session, turn.state);
|
|
@@ -422,8 +618,10 @@ async function handleCompress(env, args, exec) {
|
|
|
422
618
|
const startSeq = parseSeq(range.startSeq);
|
|
423
619
|
const endSeq = parseSeq(range.endSeq);
|
|
424
620
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
425
|
-
const
|
|
426
|
-
const
|
|
621
|
+
const startBlockRef = blockRefForSummarySeq(session, start);
|
|
622
|
+
const endBlockRef = blockRefForSummarySeq(session, end);
|
|
623
|
+
const startRef = startBlockRef ?? byRaw[String(start)];
|
|
624
|
+
const endRef = endBlockRef ?? byRaw[String(end)];
|
|
427
625
|
if (startRef === void 0 || endRef === void 0) {
|
|
428
626
|
throw new Error(
|
|
429
627
|
`billion-context-dsh: seq ${start}..${end} has no assigned ref \u2014 the range must be on the current surface (run acp_status for the live seq list)`
|
|
@@ -445,15 +643,44 @@ async function handleCompress(env, args, exec) {
|
|
|
445
643
|
messages: coreMessages,
|
|
446
644
|
state: turn.state,
|
|
447
645
|
config
|
|
646
|
+
// Deliberately NOT overriding protectedMessageIds: with the full log the
|
|
647
|
+
// kernel's recent/last-user protection is computed over the same
|
|
648
|
+
// non-block-covered messages as the visible feed, so default behavior is
|
|
649
|
+
// preserved. Any 'Excluded N protected message(s)' warning is surfaced.
|
|
448
650
|
});
|
|
449
651
|
if (applied.result.errors.length > 0) {
|
|
450
652
|
return { text: `compress failed: ${applied.result.errors.join("; ")}` };
|
|
451
653
|
}
|
|
452
654
|
env.store.set(session, applied.state);
|
|
655
|
+
const previousIds = new Set(turn.state.blocks.map((block) => block.blockId));
|
|
656
|
+
const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId));
|
|
657
|
+
const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]));
|
|
658
|
+
const warningByRangeKey = /* @__PURE__ */ new Map();
|
|
659
|
+
const freeWarnings = [];
|
|
660
|
+
for (const warning of applied.result.warnings) {
|
|
661
|
+
const match = /^Skipped range \((.+?)\.\.(.+?)\)/.exec(warning);
|
|
662
|
+
if (match !== null) {
|
|
663
|
+
const key = `${match[1]}::${match[2]}`;
|
|
664
|
+
const list = warningByRangeKey.get(key) ?? [];
|
|
665
|
+
list.push(warning);
|
|
666
|
+
warningByRangeKey.set(key, list);
|
|
667
|
+
} else {
|
|
668
|
+
freeWarnings.push(warning);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
453
671
|
const lines = [];
|
|
672
|
+
let skippedRanges = 0;
|
|
454
673
|
for (let index = 0; index < ranges.length; index += 1) {
|
|
455
674
|
const range = ranges[index];
|
|
456
675
|
const original = args.content[index];
|
|
676
|
+
const key = `${range.startRef}::${range.endRef}`;
|
|
677
|
+
const block = blockByRangeKey.get(key);
|
|
678
|
+
if (block === void 0) {
|
|
679
|
+
skippedRanges += 1;
|
|
680
|
+
const warnings = warningByRangeKey.get(key) ?? [];
|
|
681
|
+
for (const warning of warnings) lines.push(` ${warning}`);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
457
684
|
const { start, end } = range;
|
|
458
685
|
const shadowed = shadowedSeqsOf(session, start, end);
|
|
459
686
|
let shadowedTokens = 0;
|
|
@@ -461,6 +688,8 @@ async function handleCompress(env, args, exec) {
|
|
|
461
688
|
const event = session.events[seq];
|
|
462
689
|
if (event !== void 0) shadowedTokens += defaultCountTokens2(extractEventText(event));
|
|
463
690
|
}
|
|
691
|
+
const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1;
|
|
692
|
+
const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds);
|
|
464
693
|
const { compactionId } = runCompactionTransaction(session, {
|
|
465
694
|
start,
|
|
466
695
|
end,
|
|
@@ -468,17 +697,27 @@ async function handleCompress(env, args, exec) {
|
|
|
468
697
|
summary: [{ type: "text", text: original.summary }],
|
|
469
698
|
shadowedTokenCount: shadowedTokens,
|
|
470
699
|
provider: agent.options.provider ?? "",
|
|
471
|
-
model: agent.options.model ?? ""
|
|
700
|
+
model: agent.options.model ?? "",
|
|
701
|
+
tier,
|
|
702
|
+
kernelBlockId: block.blockId,
|
|
703
|
+
...parentBlockIds.length === 0 ? {} : { parentBlockIds },
|
|
704
|
+
// Record the kernel block's raw coverage so a restarted engine
|
|
705
|
+
// rehydrates the SAME effective messages (a tier-2 block's coverage is
|
|
706
|
+
// its parents' originals, not the checkpoint node).
|
|
707
|
+
directMessageIds: block.directMessageIds,
|
|
708
|
+
effectiveMessageIds: block.effectiveMessageIds
|
|
472
709
|
});
|
|
473
710
|
const adjusted = start !== range.startSeq || end !== range.endSeq;
|
|
711
|
+
const tierLabel = tier === 1 ? "" : `, tier ${tier}`;
|
|
474
712
|
lines.push(
|
|
475
|
-
` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed` + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : "")
|
|
713
|
+
` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}` + (adjusted ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)` : "")
|
|
476
714
|
);
|
|
477
715
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
${
|
|
481
|
-
}
|
|
716
|
+
const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`;
|
|
717
|
+
const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...lines];
|
|
718
|
+
const footer = skippedRanges > 0 ? ` (${skippedRanges} range(s) skipped \u2014 see warnings above)` : "";
|
|
719
|
+
return { text: `${summaryLine}
|
|
720
|
+
${[...warningLines, footer].filter((line) => line !== "").join("\n")}` };
|
|
482
721
|
}
|
|
483
722
|
var decompressParameters = {
|
|
484
723
|
blockId: { type: "string", required: true, description: "Block id from acp_status or search_context (the compaction id)." }
|
|
@@ -491,13 +730,14 @@ function handleDecompress(_env, args, exec) {
|
|
|
491
730
|
return { text: `decompress: block "${args.blockId}" not found (see acp_status for the block list)` };
|
|
492
731
|
}
|
|
493
732
|
const parts = [];
|
|
494
|
-
for (const seq of block.
|
|
733
|
+
for (const seq of expandShadowedSeqs(session, block.blockId)) {
|
|
495
734
|
const event = session.events[seq];
|
|
496
735
|
const text = event === void 0 ? "" : extractEventText(event);
|
|
497
736
|
if (text.length > 0) parts.push(`[seq ${seq}] ${text}`);
|
|
498
737
|
}
|
|
738
|
+
const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : "";
|
|
499
739
|
return {
|
|
500
|
-
text: `Block ${block.blockId} \u2014 ${block.summary}
|
|
740
|
+
text: `Block ${block.blockId} \u2014 ${block.summary}${tierNote}
|
|
501
741
|
|
|
502
742
|
${parts.join("\n\n") || "(no recoverable content)"}`
|
|
503
743
|
};
|
|
@@ -528,18 +768,21 @@ ${original}`.toLowerCase();
|
|
|
528
768
|
};
|
|
529
769
|
}
|
|
530
770
|
var statusParameters = {};
|
|
531
|
-
function handleStatus(env, _args, exec) {
|
|
532
|
-
const
|
|
771
|
+
async function handleStatus(env, _args, exec) {
|
|
772
|
+
const agent = requireAgent(exec);
|
|
773
|
+
const session = agent.session;
|
|
533
774
|
const ledger = rebuildBlockLedger(session.events);
|
|
534
775
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
535
776
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
536
777
|
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
537
|
-
const
|
|
778
|
+
const window = env.windowFor === void 0 ? { limit: env.modelContextLimit, source: "explicit" } : await env.windowFor(agent);
|
|
779
|
+
const limit = window.limit;
|
|
538
780
|
const lines = [
|
|
539
781
|
`ACP status \u2014 session ${session.id}`,
|
|
540
782
|
` blocks: ${ledger.length}`,
|
|
541
783
|
` tokens compressed: ${totalTokens}`,
|
|
542
784
|
` estimated context: ${estimated} / ${limit} (${Math.round(estimated / limit * 100)}%)`,
|
|
785
|
+
` context window: ${limit} (${windowSourceLabel(window)})`,
|
|
543
786
|
` surface: ${surfaceSummary(session)}`
|
|
544
787
|
];
|
|
545
788
|
for (const block of ledger.slice(0, 10)) {
|
|
@@ -582,7 +825,7 @@ function makeTools(env) {
|
|
|
582
825
|
parameters: statusParameters,
|
|
583
826
|
output: textOutput(),
|
|
584
827
|
execute(args, exec) {
|
|
585
|
-
return
|
|
828
|
+
return handleStatus(env, args, exec);
|
|
586
829
|
}
|
|
587
830
|
})
|
|
588
831
|
];
|
|
@@ -590,21 +833,24 @@ function makeTools(env) {
|
|
|
590
833
|
|
|
591
834
|
// src/commands.ts
|
|
592
835
|
import { defaultCountTokens as defaultCountTokens3 } from "acp-kernel";
|
|
593
|
-
function statusText(env, agent) {
|
|
836
|
+
async function statusText(env, agent) {
|
|
594
837
|
const session = agent.session;
|
|
595
838
|
const ledger = rebuildBlockLedger(session.events);
|
|
596
839
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
597
840
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
598
841
|
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens3(message.text ?? ""), 0);
|
|
599
|
-
const
|
|
842
|
+
const window = env.windowFor === void 0 ? { limit: env.modelContextLimit, source: "explicit" } : await env.windowFor(agent);
|
|
843
|
+
const limit = window.limit;
|
|
600
844
|
const lines = [
|
|
601
845
|
`ACP status \u2014 session ${session.id}`,
|
|
602
846
|
` blocks: ${ledger.length}`,
|
|
603
847
|
` tokens compressed: ${totalTokens}`,
|
|
604
|
-
` estimated context: ${estimated} / ${limit} (${Math.round(estimated / limit * 100)}%)
|
|
848
|
+
` estimated context: ${estimated} / ${limit} (${Math.round(estimated / limit * 100)}%)`,
|
|
849
|
+
` context window: ${limit} (${windowSourceLabel(window)})`
|
|
605
850
|
];
|
|
606
851
|
for (const block of ledger.slice(0, 10)) {
|
|
607
|
-
|
|
852
|
+
const tier = block.tier > 1 ? ` [T${block.tier}]` : "";
|
|
853
|
+
lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} \u2014 ${block.summary.slice(0, 80)}`);
|
|
608
854
|
}
|
|
609
855
|
return lines.join("\n");
|
|
610
856
|
}
|
|
@@ -620,6 +866,9 @@ function compressText(env, agent, args) {
|
|
|
620
866
|
}
|
|
621
867
|
const session = agent.session;
|
|
622
868
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
869
|
+
if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {
|
|
870
|
+
return "/acp compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp compress";
|
|
871
|
+
}
|
|
623
872
|
const shadowed = shadowedSeqsOf(session, startSeq, endSeq);
|
|
624
873
|
let shadowedTokens = 0;
|
|
625
874
|
for (const seq of shadowed) {
|
|
@@ -643,7 +892,7 @@ function decompressText(_env, agent, args) {
|
|
|
643
892
|
const ledger = rebuildBlockLedger(session.events);
|
|
644
893
|
const block = ledger.find((entry) => entry.blockId.startsWith(args[0]));
|
|
645
894
|
if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
|
|
646
|
-
const parts = block.
|
|
895
|
+
const parts = expandShadowedSeqs(session, block.blockId).map((seq) => extractEventText(session.events[seq])).filter((text) => text.length > 0);
|
|
647
896
|
return `Block ${block.blockId} \u2014 ${block.summary}
|
|
648
897
|
|
|
649
898
|
${parts.join("\n\n") || "(no recoverable content)"}`;
|
|
@@ -655,7 +904,7 @@ function acpCommand(env) {
|
|
|
655
904
|
handler: async (invocation) => {
|
|
656
905
|
const raw = invocation.rawInput.trim();
|
|
657
906
|
if (raw === "" || raw === "status") {
|
|
658
|
-
return { kind: "success", text: statusText(env, invocation.agent) };
|
|
907
|
+
return { kind: "success", text: await statusText(env, invocation.agent) };
|
|
659
908
|
}
|
|
660
909
|
if (raw.startsWith("compress")) {
|
|
661
910
|
return { kind: "success", text: compressText(env, invocation.agent, raw.slice("compress".length).trim().split(/\s+/)) };
|
|
@@ -694,8 +943,9 @@ function measuredTokenCount(agent, coreMessages) {
|
|
|
694
943
|
function buildNudge(agent, env, lastNudgeTurn) {
|
|
695
944
|
const session = agent.session;
|
|
696
945
|
const state = env.store.stateFor(session);
|
|
697
|
-
const coreMessages =
|
|
698
|
-
const
|
|
946
|
+
const coreMessages = allLogMessages(session);
|
|
947
|
+
const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
948
|
+
const tokenCount = measuredTokenCount(agent, surfaceMessages);
|
|
699
949
|
const config = kernelConfigFor(env);
|
|
700
950
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
701
951
|
env.store.set(session, turn.state);
|
|
@@ -717,7 +967,18 @@ function buildNudgeText(nudge, emergency, session) {
|
|
|
717
967
|
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
718
968
|
const frame = emergency ? `\u26A0\uFE0F Context usage is at ${pct}% of the window \u2014 nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.` : `Context usage is at ${pct}%. This is a suggestion, not a requirement \u2014 you decide whether and when to compress.`;
|
|
719
969
|
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
720
|
-
|
|
970
|
+
const parts = [frame, "", guidance];
|
|
971
|
+
if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {
|
|
972
|
+
const targets = nudge.tierTargetBlocks;
|
|
973
|
+
const summarySeqs = targets.map((block) => summarySeqOfKernelBlock(session, block.blockId)).filter((seq) => seq !== null);
|
|
974
|
+
const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3;
|
|
975
|
+
const tokens = typeof pending === "number" ? pending : 0;
|
|
976
|
+
parts.push(
|
|
977
|
+
`Tier ${nudge.tier}: ${targets.length} tier-${nudge.tier - 1} block(s) distillable (${tokens} tokens) \u2014 compress their summary node(s) [seqs ${summarySeqs.join(", ")}] to reclaim the original messages.`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
parts.push(rangeTable(session));
|
|
981
|
+
return parts.join("\n");
|
|
721
982
|
}
|
|
722
983
|
|
|
723
984
|
// src/system-prompt.ts
|
|
@@ -734,15 +995,24 @@ Compression tools (refs are SURFACE SEQS, not ids):
|
|
|
734
995
|
- search_context: find information inside compressed blocks BEFORE decompressing. search_context({ query }).
|
|
735
996
|
- acp_status: current context usage and the live compressible-range list. Run it before compressing when in doubt.
|
|
736
997
|
|
|
998
|
+
Tiered compression: each compressed block appears on the surface as one summary node. Compressing that node again DISTILLS the block (tier 2): the parent summary folds into your new summary and the original messages are freed. Distilling a tier-2 block yields tier 3. Distill when a summary itself is consumed \u2014 decompress on the tier-2 block recovers the full originals.
|
|
999
|
+
|
|
737
1000
|
When you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs \u2014 the surface moves as messages land and compress; verify with acp_status.`;
|
|
738
1001
|
var ACP_SYSTEM_PROMPT_ORDER = 150;
|
|
739
1002
|
|
|
740
1003
|
// src/index.ts
|
|
741
1004
|
var DEFAULT_CONFIG = {
|
|
742
|
-
|
|
1005
|
+
autoModelContextLimit: true,
|
|
743
1006
|
autoTools: true,
|
|
744
1007
|
autoCommand: true,
|
|
745
|
-
autoNudge: true
|
|
1008
|
+
autoNudge: true,
|
|
1009
|
+
// Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the
|
|
1010
|
+
// kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before
|
|
1011
|
+
// the API rejects, and the host's compaction-basic line (thresholdRatio
|
|
1012
|
+
// 0.80) shadows it in standard/code/cordis modes; 0.70 keeps the forced
|
|
1013
|
+
// over-limit nudge ahead of that 80% line. Explicit values always win.
|
|
1014
|
+
nudgeMaxContextLimitPct: 0.7,
|
|
1015
|
+
nudgeEmergencyThresholdPct: 0.85
|
|
746
1016
|
};
|
|
747
1017
|
function resolveAcpConfig(config = {}) {
|
|
748
1018
|
return { ...DEFAULT_CONFIG, ...config };
|
|
@@ -755,6 +1025,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
755
1025
|
/** Resolved engine configuration. */
|
|
756
1026
|
config;
|
|
757
1027
|
lastNudgeTurn = /* @__PURE__ */ new Map();
|
|
1028
|
+
/** Per provider/model route the resolved window (probe failures cached too). */
|
|
1029
|
+
windowCache = /* @__PURE__ */ new Map();
|
|
758
1030
|
constructor(ctx, config = {}) {
|
|
759
1031
|
super(ctx);
|
|
760
1032
|
this.config = resolveAcpConfig(config);
|
|
@@ -763,11 +1035,13 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
763
1035
|
const env = {
|
|
764
1036
|
kernel: this.kernel,
|
|
765
1037
|
store: this.store,
|
|
766
|
-
|
|
1038
|
+
// Initial value before any probe; windowFor() replaces it per pre-step.
|
|
1039
|
+
modelContextLimit: this.config.modelContextLimit ?? DEFAULT_CONTEXT_WINDOW,
|
|
767
1040
|
nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,
|
|
768
1041
|
nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,
|
|
769
1042
|
nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,
|
|
770
|
-
coreOverrides: this.config.coreOverrides
|
|
1043
|
+
coreOverrides: this.config.coreOverrides,
|
|
1044
|
+
windowFor: (agent) => this.windowFor(agent)
|
|
771
1045
|
};
|
|
772
1046
|
const tools = ctx.get("tools");
|
|
773
1047
|
if (tools !== void 0) {
|
|
@@ -805,7 +1079,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
805
1079
|
ctx.on("agent/pre-step", async (payload, next) => {
|
|
806
1080
|
const decision = await next();
|
|
807
1081
|
if (decision.kind === "reject") return decision;
|
|
808
|
-
const
|
|
1082
|
+
const window = await this.windowFor(payload.agent);
|
|
1083
|
+
const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn);
|
|
809
1084
|
if (outcome === null) return decision;
|
|
810
1085
|
return { kind: "enter", messages: [...decision.messages, outcome.message] };
|
|
811
1086
|
});
|
|
@@ -819,6 +1094,32 @@ var AcpCompactionEngine = class extends CompactionEngine {
|
|
|
819
1094
|
});
|
|
820
1095
|
}
|
|
821
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Resolve the effective context window for an agent. An explicitly
|
|
1099
|
+
* configured `modelContextLimit` always wins (no probe). Otherwise probe the
|
|
1100
|
+
* model's real window via `agent.ctx.llm.resolveModelInfo` (cached per
|
|
1101
|
+
* provider/model route, probe failures cached too) and fall back to
|
|
1102
|
+
* DEFAULT_CONTEXT_WINDOW when auto-detection is disabled or unavailable.
|
|
1103
|
+
*/
|
|
1104
|
+
async windowFor(agent) {
|
|
1105
|
+
if (this.config.modelContextLimit !== void 0) {
|
|
1106
|
+
return { limit: this.config.modelContextLimit, source: "explicit" };
|
|
1107
|
+
}
|
|
1108
|
+
const provider = agent.options.provider ?? "";
|
|
1109
|
+
const model = agent.options.model ?? "";
|
|
1110
|
+
const key = `${provider}\0${model}`;
|
|
1111
|
+
const cached = this.windowCache.get(key);
|
|
1112
|
+
if (cached !== void 0) return cached;
|
|
1113
|
+
let window;
|
|
1114
|
+
if (!this.config.autoModelContextLimit) {
|
|
1115
|
+
window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model };
|
|
1116
|
+
} else {
|
|
1117
|
+
const detected = await detectContextWindow(agent, provider, model);
|
|
1118
|
+
window = detected === null ? { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model } : { limit: detected, source: "auto", provider, model };
|
|
1119
|
+
}
|
|
1120
|
+
this.windowCache.set(key, window);
|
|
1121
|
+
return window;
|
|
1122
|
+
}
|
|
822
1123
|
/** ACP is model-driven: automatic pressure policy never summarizes by itself. */
|
|
823
1124
|
async compactIfNeeded(_agent, _trigger, signal) {
|
|
824
1125
|
signal.throwIfAborted();
|
|
@@ -848,11 +1149,17 @@ export {
|
|
|
848
1149
|
ACP_SYSTEM_PROMPT_ORDER,
|
|
849
1150
|
AcpCompactionEngine,
|
|
850
1151
|
AcpStateStore,
|
|
1152
|
+
DEFAULT_CONTEXT_WINDOW,
|
|
851
1153
|
acpCommand,
|
|
852
1154
|
assertNoActiveCompaction,
|
|
1155
|
+
blockRefForSummarySeq,
|
|
1156
|
+
blockRegistry,
|
|
853
1157
|
buildNudge,
|
|
1158
|
+
compactionIdsOfKernelBlocks,
|
|
854
1159
|
index_default as default,
|
|
1160
|
+
detectContextWindow,
|
|
855
1161
|
eventsToCoreMessages,
|
|
1162
|
+
expandShadowedSeqs,
|
|
856
1163
|
extractEventText,
|
|
857
1164
|
findOpenTurn,
|
|
858
1165
|
kernelConfigFor,
|
|
@@ -863,6 +1170,8 @@ export {
|
|
|
863
1170
|
resolveSurfaceRange,
|
|
864
1171
|
runCompactionTransaction,
|
|
865
1172
|
shadowedSeqsOf,
|
|
866
|
-
|
|
1173
|
+
summarySeqOfKernelBlock,
|
|
1174
|
+
surfaceEventsOf,
|
|
1175
|
+
windowSourceLabel
|
|
867
1176
|
};
|
|
868
1177
|
//# sourceMappingURL=index.js.map
|