@code-yeongyu/senpi 2026.9.6 → 2026.9.7-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/dist/core/agent-session.d.ts +51 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +168 -48
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/stuck-overflow.d.ts +11 -0
- package/dist/core/compaction/stuck-overflow.d.ts.map +1 -0
- package/dist/core/compaction/stuck-overflow.js +17 -0
- package/dist/core/compaction/stuck-overflow.js.map +1 -0
- package/dist/core/extensions/builtin/cache-keepalive/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cache-keepalive/index.js +3 -21
- package/dist/core/extensions/builtin/cache-keepalive/index.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js +36 -19
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-binding.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js +21 -10
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts +4 -17
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js +23 -33
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-continuity.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-stream.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js +5 -3
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-sync.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js +8 -3
- package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts +6 -5
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js +6 -5
- package/dist/core/extensions/builtin/claude-sdk-oauth/tools.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts +8 -0
- package/dist/core/extensions/builtin/compaction/circuit-breaker.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/circuit-breaker.js +8 -0
- package/dist/core/extensions/builtin/compaction/circuit-breaker.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +24 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts +18 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js +20 -0
- package/dist/core/extensions/builtin/compaction/fallback-failed-turn-normalization.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/index.js +2 -2
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +3 -0
- package/dist/core/extensions/builtin/compaction/lane-policy.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/lane-policy.js +47 -23
- package/dist/core/extensions/builtin/compaction/lane-policy.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +6 -4
- package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +19 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.js +23 -10
- package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts +3 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +4 -0
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +3 -0
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +14 -3
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +7 -3
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js +2 -2
- package/dist/core/extensions/builtin/hooks/plugin-manifest.js.map +1 -1
- package/dist/core/extensions/builtin/hooks/safety.js +2 -2
- package/dist/core/extensions/builtin/hooks/safety.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.js +5 -22
- package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +5 -7
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +14 -7
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/package-identity.d.ts +12 -0
- package/dist/core/package-identity.d.ts.map +1 -0
- package/dist/core/package-identity.js +57 -0
- package/dist/core/package-identity.js.map +1 -0
- package/dist/core/resource-loader.d.ts +0 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -60
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/tools/bounded-realpath.d.ts +28 -0
- package/dist/core/tools/bounded-realpath.d.ts.map +1 -0
- package/dist/core/tools/bounded-realpath.js +53 -0
- package/dist/core/tools/bounded-realpath.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +15 -13
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/filesystem-policy.d.ts +7 -0
- package/dist/core/tools/filesystem-policy.d.ts.map +1 -1
- package/dist/core/tools/filesystem-policy.js +25 -15
- package/dist/core/tools/filesystem-policy.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +13 -2
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/memory-tips.js +6 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/subagent-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/subagent-tips.js +6 -0
- package/dist/modes/interactive/tips/catalog/subagent-tips.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +31 -5
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-watchdog.d.ts +11 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/host-watchdog.js +16 -2
- package/dist/modes/rpc/host-watchdog.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +54 -16
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/socket-ownership.d.ts +46 -0
- package/dist/modes/rpc/socket-ownership.d.ts.map +1 -0
- package/dist/modes/rpc/socket-ownership.js +178 -0
- package/dist/modes/rpc/socket-ownership.js.map +1 -0
- package/dist/utils/fs-watch.d.ts +17 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +38 -10
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/paths.d.ts +33 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +109 -3
- package/dist/utils/paths.js.map +1 -1
- package/docs/compaction-guide.md +2 -2
- package/docs/compaction.md +2 -2
- package/docs/development.md +3 -2
- package/docs/providers.md +1 -1
- package/docs/rpc.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +14 -1
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/runtime-factory.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/AGENTS.md +7 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +6 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +2 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +10 -6
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.d.ts +16 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.js +44 -8
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +3 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +11 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +6 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/session-env.ts +56 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/runtime-asset.ts +40 -14
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-contract.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +9 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +26 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +12 -3
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +14 -3
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -35,6 +35,7 @@ import { executeBashWithOperations } from "./bash-executor.js";
|
|
|
35
35
|
import { envValue } from "./brand.js";
|
|
36
36
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, resolveThresholdContextTokens, shouldCompact, } from "./compaction/index.js";
|
|
37
37
|
import { CompactionLifecycleCoordinator } from "./compaction/lifecycle.js";
|
|
38
|
+
import { isTurnStuckOnContextOverflow } from "./compaction/stuck-overflow.js";
|
|
38
39
|
import { isWarmSummaryAnchorValid } from "./compaction/warm-anchor.js";
|
|
39
40
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
40
41
|
import { buildDynamicSystemPrompt } from "./dynamic-prompt/index.js";
|
|
@@ -42,7 +43,7 @@ import { areExperimentalFeaturesEnabled } from "./experimental.js";
|
|
|
42
43
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
43
44
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
44
45
|
import { ModelUsabilityBudgetError, projectModelUsabilityBudget, } from "./extensions/builtin/compaction/model-usability-budget.js";
|
|
45
|
-
import { resolveReserveTokens } from "./extensions/builtin/compaction/policy.js";
|
|
46
|
+
import { resolveEffectiveReserveTokens, resolveReserveTokens, shouldTriggerCompaction, } from "./extensions/builtin/compaction/policy.js";
|
|
46
47
|
import { WAKE_SOURCE_STATE_EVENT } from "./extensions/builtin/monitor-state-event.js";
|
|
47
48
|
import { CODEX_RESPONSES_API } from "./extensions/builtin/service-tier.js";
|
|
48
49
|
import { deriveExtensionRegistrationId } from "./extensions/builtin/tool-search/engine/marker.js";
|
|
@@ -766,7 +767,7 @@ export class AgentSession {
|
|
|
766
767
|
* session model so compaction never silently breaks.
|
|
767
768
|
*/
|
|
768
769
|
_resolveCompactionModel(sessionModel) {
|
|
769
|
-
const override = this.
|
|
770
|
+
const override = this._getCompactionSettings().model;
|
|
770
771
|
if (!override)
|
|
771
772
|
return sessionModel;
|
|
772
773
|
const slash = override.indexOf("/");
|
|
@@ -1136,6 +1137,31 @@ export class AgentSession {
|
|
|
1136
1137
|
}
|
|
1137
1138
|
_incrementMessageRevision() {
|
|
1138
1139
|
this._messageRevision++;
|
|
1140
|
+
// A revision bump alone is not evidence that the rejected context changed:
|
|
1141
|
+
// releasing here let a synthetic bump retry the unchanged oversized context
|
|
1142
|
+
// (#7921 case 6). Release only once the context actually shrank.
|
|
1143
|
+
this._releaseBlockedPostCompactionAdmissionIfReduced();
|
|
1144
|
+
}
|
|
1145
|
+
/** Byte-derived size of the context an admission decision would carry. */
|
|
1146
|
+
_blockedAdmissionContentTokens() {
|
|
1147
|
+
return estimateMessagesTokens(filterContextExcludedMessages(this.agent.state.messages));
|
|
1148
|
+
}
|
|
1149
|
+
/** A compaction that genuinely reduced the context clears the blocked state. */
|
|
1150
|
+
_releaseBlockedPostCompactionAdmissionIfReduced() {
|
|
1151
|
+
const blocked = this._blockedPostCompactionAssistant;
|
|
1152
|
+
if (blocked === undefined)
|
|
1153
|
+
return;
|
|
1154
|
+
if (this._blockedAdmissionContentTokens() < blocked.contentTokens) {
|
|
1155
|
+
this._blockedPostCompactionAssistant = undefined;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Unconditional release for manual `/compact`, the user's explicit remedy. An
|
|
1160
|
+
* ordinary user prompt is deliberately not a release: it only adds context, so
|
|
1161
|
+
* it cannot make the rejected context admissible, and the pre-prompt gate
|
|
1162
|
+
* already owns its own fail-closed rejection for that route.
|
|
1163
|
+
*/
|
|
1164
|
+
_releaseBlockedPostCompactionAdmission() {
|
|
1139
1165
|
this._blockedPostCompactionAssistant = undefined;
|
|
1140
1166
|
}
|
|
1141
1167
|
getMessageRevision() {
|
|
@@ -1184,7 +1210,7 @@ export class AgentSession {
|
|
|
1184
1210
|
systemPrompt: this.agent.state.systemPrompt,
|
|
1185
1211
|
tools: this.agent.state.tools,
|
|
1186
1212
|
liveContextTokens: this._getDownswitchLiveContextTokens(model),
|
|
1187
|
-
compaction: this.
|
|
1213
|
+
compaction: this._getCompactionSettings(),
|
|
1188
1214
|
});
|
|
1189
1215
|
return projection.usable ? undefined : projection;
|
|
1190
1216
|
}
|
|
@@ -1374,13 +1400,22 @@ export class AgentSession {
|
|
|
1374
1400
|
const messages = filterContextExcludedMessages(this.agent.state.messages);
|
|
1375
1401
|
return resolveThresholdContextTokens(directContextTokens, estimateMessagesTokens(messages));
|
|
1376
1402
|
}
|
|
1403
|
+
/**
|
|
1404
|
+
* `compaction.enabled=false` disables proactive (threshold) compaction only.
|
|
1405
|
+
* A provider-confirmed overflow is an error the session cannot progress past
|
|
1406
|
+
* by any other route, so overflow recovery stays armed regardless (#1422).
|
|
1407
|
+
*/
|
|
1377
1408
|
_getAutoCompactionReason(message) {
|
|
1378
|
-
const settings = this.
|
|
1379
|
-
if (
|
|
1409
|
+
const settings = this._getCompactionSettings();
|
|
1410
|
+
if (message.stopReason === "aborted") {
|
|
1380
1411
|
return undefined;
|
|
1381
1412
|
}
|
|
1382
1413
|
const model = this.model;
|
|
1383
|
-
|
|
1414
|
+
// Narrowed to the stale usage number itself: pre-boundary provenance does
|
|
1415
|
+
// not exempt a context whose own content already exceeds the policy.
|
|
1416
|
+
if (!model)
|
|
1417
|
+
return undefined;
|
|
1418
|
+
if (this._isAssistantFromBeforeLatestCompaction(message) && !this._exceedsPolicyByContentEstimate()) {
|
|
1384
1419
|
return undefined;
|
|
1385
1420
|
}
|
|
1386
1421
|
const sameModel = isSameOverflowSource(message, model, this._modelRuntime.getCompatibilityRequestConfig(model).upstreamModelId);
|
|
@@ -1388,11 +1423,15 @@ export class AgentSession {
|
|
|
1388
1423
|
const currentContextNeedsCompaction = contextUsage !== undefined &&
|
|
1389
1424
|
contextUsage.tokens !== null &&
|
|
1390
1425
|
shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
|
|
1391
|
-
|
|
1392
|
-
|
|
1426
|
+
const isOverflow = (isContextOverflow(message, model.contextWindow) && (sameModel || currentContextNeedsCompaction)) ||
|
|
1427
|
+
this._isCursorPayloadOverflow(message);
|
|
1428
|
+
if (isOverflow) {
|
|
1429
|
+
if (settings.enabled || isTurnStuckOnContextOverflow(message, model.contextWindow))
|
|
1430
|
+
return "overflow";
|
|
1431
|
+
return undefined;
|
|
1393
1432
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
return
|
|
1433
|
+
if (!settings.enabled) {
|
|
1434
|
+
return undefined;
|
|
1396
1435
|
}
|
|
1397
1436
|
let contextTokens;
|
|
1398
1437
|
const directContextTokens = message.usage ? calculateContextTokens(message.usage) : 0;
|
|
@@ -1412,13 +1451,31 @@ export class AgentSession {
|
|
|
1412
1451
|
if (compactionEntry &&
|
|
1413
1452
|
usageMessage?.role === "assistant" &&
|
|
1414
1453
|
this._isAssistantFromBeforeLatestCompaction(usageMessage)) {
|
|
1415
|
-
|
|
1454
|
+
// Drop only the stale usage number; the messages themselves still count.
|
|
1455
|
+
const contentTokens = estimateMessagesTokens(messages);
|
|
1456
|
+
return shouldCompact(contentTokens, model.contextWindow, settings) ? "threshold" : undefined;
|
|
1416
1457
|
}
|
|
1417
1458
|
}
|
|
1418
1459
|
contextTokens = estimate.tokens;
|
|
1419
1460
|
}
|
|
1420
1461
|
return shouldCompact(contextTokens, model.contextWindow, settings) ? "threshold" : undefined;
|
|
1421
1462
|
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Content measured by bytes, ignoring every provider usage number. The
|
|
1465
|
+
* stale-usage exemptions below only claim that a usage figure measured before
|
|
1466
|
+
* the accepted compaction boundary is not evidence of current pressure; they
|
|
1467
|
+
* must not exempt the messages themselves, because fresh post-boundary content
|
|
1468
|
+
* (a large tool result, late steering) is measurable without any usage report
|
|
1469
|
+
* (#7921 case 5).
|
|
1470
|
+
*/
|
|
1471
|
+
_exceedsPolicyByContentEstimate() {
|
|
1472
|
+
const model = this.model;
|
|
1473
|
+
if (!model)
|
|
1474
|
+
return false;
|
|
1475
|
+
const settings = this._getCompactionSettings();
|
|
1476
|
+
const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(this.agent.state.messages));
|
|
1477
|
+
return shouldCompact(contextTokens, model.contextWindow, settings);
|
|
1478
|
+
}
|
|
1422
1479
|
_hasPendingPostCompactionUsageExemption(message) {
|
|
1423
1480
|
return (this._skipNextPostCompactionAssistantCheck &&
|
|
1424
1481
|
!this._assistantsPendingAtCompaction.has(message) &&
|
|
@@ -3642,7 +3699,7 @@ export class AgentSession {
|
|
|
3642
3699
|
systemPrompt: this.agent.state.systemPrompt,
|
|
3643
3700
|
tools: this.agent.state.tools,
|
|
3644
3701
|
liveContextTokens,
|
|
3645
|
-
compaction: this.
|
|
3702
|
+
compaction: this._getCompactionSettings(),
|
|
3646
3703
|
includeSpeculationLead: options?.includeSpeculationLead,
|
|
3647
3704
|
admission: options?.admission,
|
|
3648
3705
|
});
|
|
@@ -3653,7 +3710,7 @@ export class AgentSession {
|
|
|
3653
3710
|
const currentModel = this.model;
|
|
3654
3711
|
if (!currentModel)
|
|
3655
3712
|
return 0;
|
|
3656
|
-
const compaction = this.
|
|
3713
|
+
const compaction = this._getCompactionSettings();
|
|
3657
3714
|
const currentBudget = projectModelUsabilityBudget({
|
|
3658
3715
|
model: currentModel,
|
|
3659
3716
|
systemPrompt: this.agent.state.systemPrompt,
|
|
@@ -4275,8 +4332,11 @@ export class AgentSession {
|
|
|
4275
4332
|
const model = this.model;
|
|
4276
4333
|
if (!model)
|
|
4277
4334
|
throw new Error(formatNoModelSelectedMessage());
|
|
4335
|
+
// Manual compaction is the user's explicit remedy for a blocked admission
|
|
4336
|
+
// (#7921 case 6).
|
|
4337
|
+
this._releaseBlockedPostCompactionAdmission();
|
|
4278
4338
|
const pathEntries = this.sessionManager.getBranch();
|
|
4279
|
-
const settings = cursorOverflowCompactionSettings(this.
|
|
4339
|
+
const settings = cursorOverflowCompactionSettings(this._getCompactionSettings(), model.provider, "manual");
|
|
4280
4340
|
if (!prepareCompaction(pathEntries, settings)) {
|
|
4281
4341
|
const requestId = randomUUID();
|
|
4282
4342
|
const lastEntry = pathEntries[pathEntries.length - 1];
|
|
@@ -4516,7 +4576,7 @@ export class AgentSession {
|
|
|
4516
4576
|
throw new CompactionCancelledError();
|
|
4517
4577
|
}
|
|
4518
4578
|
const pathEntries = this.sessionManager.getBranch();
|
|
4519
|
-
const settings = cursorOverflowCompactionSettings(this.
|
|
4579
|
+
const settings = cursorOverflowCompactionSettings(this._getCompactionSettings(), this.model?.provider, request.reason);
|
|
4520
4580
|
let compactionResult = request.precomputed;
|
|
4521
4581
|
let fromExtension = request.precomputed !== undefined;
|
|
4522
4582
|
if (!compactionResult) {
|
|
@@ -4719,7 +4779,7 @@ export class AgentSession {
|
|
|
4719
4779
|
simulatedMessages = truncateToolResultBodies(simulatedMessages).messages ?? simulatedMessages;
|
|
4720
4780
|
}
|
|
4721
4781
|
const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
|
|
4722
|
-
const settings = this.
|
|
4782
|
+
const settings = this._getCompactionSettings();
|
|
4723
4783
|
const reserveTokens = settings.reserveScalingEnabled === false
|
|
4724
4784
|
? settings.reserveTokens
|
|
4725
4785
|
: resolveReserveTokens(model.contextWindow, settings.reserveTokens);
|
|
@@ -4832,13 +4892,12 @@ export class AgentSession {
|
|
|
4832
4892
|
* @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages
|
|
4833
4893
|
*/
|
|
4834
4894
|
async _enforceCompactionBeforeProvider(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction = false) {
|
|
4895
|
+
this._releaseBlockedPostCompactionAdmissionIfReduced();
|
|
4835
4896
|
const blockedAdmission = this._blockedPostCompactionAssistant;
|
|
4836
|
-
if (blockedAdmission !== undefined &&
|
|
4837
|
-
blockedAdmission.assistant === assistantMessage &&
|
|
4838
|
-
blockedAdmission.revision === this._messageRevision) {
|
|
4897
|
+
if (blockedAdmission !== undefined && blockedAdmission.assistant === assistantMessage) {
|
|
4839
4898
|
throw new RequiredCompactionError();
|
|
4840
4899
|
}
|
|
4841
|
-
const settings = this.
|
|
4900
|
+
const settings = this._getCompactionSettings();
|
|
4842
4901
|
const model = this.model;
|
|
4843
4902
|
const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
|
|
4844
4903
|
const compacted = assistantMessage
|
|
@@ -4867,12 +4926,28 @@ export class AgentSession {
|
|
|
4867
4926
|
}
|
|
4868
4927
|
throw new RequiredCompactionError();
|
|
4869
4928
|
}
|
|
4929
|
+
/**
|
|
4930
|
+
* Pending queued input the provider will also carry on this turn. Steering and
|
|
4931
|
+
* follow-up text enqueued after the admission projection was assembled - by a
|
|
4932
|
+
* `before_agent_start` handler, an extension action, or the user racing the
|
|
4933
|
+
* gate - is drained into the same run, so the final gate must measure it
|
|
4934
|
+
* (#7921 case 5).
|
|
4935
|
+
*/
|
|
4936
|
+
_pendingQueuedInputMessages() {
|
|
4937
|
+
const timestamp = Date.now();
|
|
4938
|
+
return [...this._steeringMessages, ...this._followUpMessages].map((text) => ({
|
|
4939
|
+
role: "user",
|
|
4940
|
+
content: [{ type: "text", text }],
|
|
4941
|
+
timestamp,
|
|
4942
|
+
}));
|
|
4943
|
+
}
|
|
4870
4944
|
/**
|
|
4871
4945
|
* The normal pre-prompt check only estimates persisted session context. This
|
|
4872
4946
|
* final gate also includes turn-local messages which the provider will see:
|
|
4873
|
-
* the current prompt, next-turn custom messages,
|
|
4874
|
-
*
|
|
4875
|
-
*
|
|
4947
|
+
* the current prompt, next-turn custom messages, before_agent_start additions,
|
|
4948
|
+
* and steering or follow-up input queued after that projection was assembled.
|
|
4949
|
+
* Compaction rewrites only session context, so callers retain and reapply
|
|
4950
|
+
* their already-assembled one-shot additions after it succeeds.
|
|
4876
4951
|
*/
|
|
4877
4952
|
async _enforceFinalProviderAdmission(messages) {
|
|
4878
4953
|
// User-only prompts are deliberately admitted without this gate: prompt
|
|
@@ -4880,18 +4955,24 @@ export class AgentSession {
|
|
|
4880
4955
|
// (issues #531/#886), so oversized user prompts rely on threshold
|
|
4881
4956
|
// compaction — which also samples the local transcript estimate — and on
|
|
4882
4957
|
// provider-overflow recovery. This gate closes the separate gap opened by
|
|
4883
|
-
// turn-local custom additions
|
|
4884
|
-
|
|
4958
|
+
// turn-local custom additions and by late queued input, neither of which the
|
|
4959
|
+
// pre-prompt check can observe.
|
|
4960
|
+
const lateQueuedMessages = this._pendingQueuedInputMessages();
|
|
4961
|
+
if (!messages.some((message) => message.role === "custom") && lateQueuedMessages.length === 0)
|
|
4885
4962
|
return;
|
|
4886
4963
|
const model = this.model;
|
|
4887
4964
|
if (!model)
|
|
4888
4965
|
return;
|
|
4889
|
-
const settings = this.
|
|
4890
|
-
const reserveTokens =
|
|
4891
|
-
? settings.reserveTokens
|
|
4892
|
-
: resolveReserveTokens(model.contextWindow, settings.reserveTokens);
|
|
4966
|
+
const settings = this._getCompactionSettings();
|
|
4967
|
+
const reserveTokens = resolveEffectiveReserveTokens(model.contextWindow, settings);
|
|
4893
4968
|
const isOversized = () => {
|
|
4894
|
-
const providerMessages = filterContextExcludedMessages([
|
|
4969
|
+
const providerMessages = filterContextExcludedMessages([
|
|
4970
|
+
...this.agent.state.messages,
|
|
4971
|
+
...messages,
|
|
4972
|
+
// Re-read the queues on every sample: this closure is evaluated again
|
|
4973
|
+
// after compaction, when more input may have arrived.
|
|
4974
|
+
...this._pendingQueuedInputMessages(),
|
|
4975
|
+
]);
|
|
4895
4976
|
const estimate = estimateContextTokens(providerMessages);
|
|
4896
4977
|
const usageMessage = estimate.lastUsageIndex === null ? undefined : providerMessages[estimate.lastUsageIndex];
|
|
4897
4978
|
// Kept assistant usage can describe the pre-compaction request. Once a
|
|
@@ -4920,9 +5001,7 @@ export class AgentSession {
|
|
|
4920
5001
|
}
|
|
4921
5002
|
}
|
|
4922
5003
|
async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason, retryAfterCompaction = false) {
|
|
4923
|
-
const settings = this.
|
|
4924
|
-
if (!settings.enabled)
|
|
4925
|
-
return false;
|
|
5004
|
+
const settings = this._getCompactionSettings();
|
|
4926
5005
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
4927
5006
|
if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
|
|
4928
5007
|
return false;
|
|
@@ -4937,7 +5016,12 @@ export class AgentSession {
|
|
|
4937
5016
|
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
4938
5017
|
// from retriggering compaction on the first prompt after compaction.
|
|
4939
5018
|
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
4940
|
-
|
|
5019
|
+
// The inline (pre-prompt) caller re-samples the current context itself and
|
|
5020
|
+
// owns the fail-closed rejection, so only the automatic route needs the
|
|
5021
|
+
// narrowed exemption here: fresh post-boundary content still counts even
|
|
5022
|
+
// though this message's own usage predates the boundary (#7921 case 5).
|
|
5023
|
+
if (this._isAssistantFromBeforeLatestCompaction(assistantMessage) &&
|
|
5024
|
+
(inlineReason !== undefined || !this._exceedsPolicyByContentEstimate())) {
|
|
4941
5025
|
return false;
|
|
4942
5026
|
}
|
|
4943
5027
|
// Case 1: Overflow - LLM returned context overflow error.
|
|
@@ -4951,6 +5035,9 @@ export class AgentSession {
|
|
|
4951
5035
|
const isOverflow = (isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction)) ||
|
|
4952
5036
|
recoverableLength ||
|
|
4953
5037
|
this._isCursorPayloadOverflow(assistantMessage);
|
|
5038
|
+
if (isOverflow && !settings.enabled && !isTurnStuckOnContextOverflow(assistantMessage, contextWindow)) {
|
|
5039
|
+
return false;
|
|
5040
|
+
}
|
|
4954
5041
|
if (isOverflow &&
|
|
4955
5042
|
assistantMessage.stopReason === "stop" &&
|
|
4956
5043
|
this._consumePostCompactionUsageExemption(assistantMessage)) {
|
|
@@ -4969,7 +5056,7 @@ export class AgentSession {
|
|
|
4969
5056
|
getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
|
|
4970
5057
|
this._blockedPostCompactionAssistant = {
|
|
4971
5058
|
assistant: assistantMessage,
|
|
4972
|
-
|
|
5059
|
+
contentTokens: this._blockedAdmissionContentTokens(),
|
|
4973
5060
|
};
|
|
4974
5061
|
}
|
|
4975
5062
|
return compacted;
|
|
@@ -5021,6 +5108,10 @@ export class AgentSession {
|
|
|
5021
5108
|
}
|
|
5022
5109
|
return compacted;
|
|
5023
5110
|
}
|
|
5111
|
+
// Stuck-overflow recovery above runs regardless of the flag; threshold
|
|
5112
|
+
// compaction below is the proactive path the user switched off (#1422).
|
|
5113
|
+
if (!settings.enabled)
|
|
5114
|
+
return false;
|
|
5024
5115
|
// The first ordinary response can carry provider usage calculated before
|
|
5025
5116
|
// compaction. Consume that exemption only after proving this is not an
|
|
5026
5117
|
// overflow, then retain the decision for later admission re-sampling.
|
|
@@ -5031,6 +5122,7 @@ export class AgentSession {
|
|
|
5031
5122
|
// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
|
|
5032
5123
|
// responses can still compact and do not reset context accounting.
|
|
5033
5124
|
let contextTokens;
|
|
5125
|
+
let staleUsageContentTokens;
|
|
5034
5126
|
if (inlineReason) {
|
|
5035
5127
|
const messages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
|
|
5036
5128
|
contextTokens = estimateContextTokens(messages).tokens;
|
|
@@ -5051,10 +5143,13 @@ export class AgentSession {
|
|
|
5051
5143
|
if (compactionEntry &&
|
|
5052
5144
|
usageMsg.role === "assistant" &&
|
|
5053
5145
|
this._isAssistantFromBeforeLatestCompaction(usageMsg)) {
|
|
5054
|
-
|
|
5146
|
+
// Drop only the stale usage number; the messages themselves still count.
|
|
5147
|
+
staleUsageContentTokens = estimateMessagesTokens(messages);
|
|
5148
|
+
if (!shouldCompact(staleUsageContentTokens, contextWindow, settings))
|
|
5149
|
+
return false;
|
|
5055
5150
|
}
|
|
5056
5151
|
}
|
|
5057
|
-
contextTokens = estimate.tokens;
|
|
5152
|
+
contextTokens = staleUsageContentTokens ?? estimate.tokens;
|
|
5058
5153
|
}
|
|
5059
5154
|
}
|
|
5060
5155
|
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
@@ -5069,7 +5164,7 @@ export class AgentSession {
|
|
|
5069
5164
|
getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
|
|
5070
5165
|
this._blockedPostCompactionAssistant = {
|
|
5071
5166
|
assistant: assistantMessage,
|
|
5072
|
-
|
|
5167
|
+
contentTokens: this._blockedAdmissionContentTokens(),
|
|
5073
5168
|
};
|
|
5074
5169
|
}
|
|
5075
5170
|
return compacted;
|
|
@@ -5153,7 +5248,7 @@ export class AgentSession {
|
|
|
5153
5248
|
}
|
|
5154
5249
|
async _revalidateScheduledContinuationAdmission() {
|
|
5155
5250
|
const model = this.model;
|
|
5156
|
-
const settings = this.
|
|
5251
|
+
const settings = this._getCompactionSettings();
|
|
5157
5252
|
if (!model || !settings.enabled)
|
|
5158
5253
|
return;
|
|
5159
5254
|
const canonicalMessages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
|
|
@@ -5162,11 +5257,27 @@ export class AgentSession {
|
|
|
5162
5257
|
const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
|
|
5163
5258
|
? estimateMessagesTokens(canonicalMessages)
|
|
5164
5259
|
: estimate.tokens;
|
|
5165
|
-
|
|
5260
|
+
// An explicit user prompt passes the proactive policy in the compaction
|
|
5261
|
+
// extension's before_agent_start, which automatic continuations never emit.
|
|
5262
|
+
// Sampling the same predicate here makes both routes compact at the same
|
|
5263
|
+
// usage instead of letting a continuation ride past the threshold up to the
|
|
5264
|
+
// hard valve (#7921 case 4).
|
|
5265
|
+
const atHardLimit = shouldCompact(contextTokens, model.contextWindow, settings);
|
|
5266
|
+
const overProactiveThreshold = shouldTriggerCompaction({
|
|
5267
|
+
tokens: contextTokens,
|
|
5268
|
+
contextWindow: model.contextWindow,
|
|
5269
|
+
percent: model.contextWindow > 0 ? (contextTokens / model.contextWindow) * 100 : 0,
|
|
5270
|
+
}, model.contextWindow, settings);
|
|
5271
|
+
if (!atHardLimit && !overProactiveThreshold)
|
|
5166
5272
|
return;
|
|
5167
5273
|
const compacted = await this._runPrePromptCompaction(this._findLastAssistantMessage(), true, "pre_prompt");
|
|
5168
5274
|
if (!compacted) {
|
|
5169
|
-
|
|
5275
|
+
// Proactive pressure alone must never brick an automatic continuation:
|
|
5276
|
+
// only the hard reserve valve stays fail-closed (#531/#886).
|
|
5277
|
+
if (!atHardLimit ||
|
|
5278
|
+
this._isCompactionOnCooldown() ||
|
|
5279
|
+
this._isCompactionDelegated() ||
|
|
5280
|
+
this._hasSupersedingCompactionClaim()) {
|
|
5170
5281
|
return;
|
|
5171
5282
|
}
|
|
5172
5283
|
throw new RequiredCompactionError();
|
|
@@ -5333,7 +5444,7 @@ export class AgentSession {
|
|
|
5333
5444
|
}
|
|
5334
5445
|
if (!this._ownsCompactionController(autoCompactionController, "auto"))
|
|
5335
5446
|
return false;
|
|
5336
|
-
const preparation = prepareCompaction(this.sessionManager.getBranch(), cursorOverflowCompactionSettings(this.
|
|
5447
|
+
const preparation = prepareCompaction(this.sessionManager.getBranch(), cursorOverflowCompactionSettings(this._getCompactionSettings(), this.model?.provider, reason), reason === "overflow");
|
|
5337
5448
|
if (!preparation) {
|
|
5338
5449
|
return endBeforeExecution();
|
|
5339
5450
|
}
|
|
@@ -5417,12 +5528,21 @@ export class AgentSession {
|
|
|
5417
5528
|
}
|
|
5418
5529
|
}
|
|
5419
5530
|
/**
|
|
5420
|
-
* Toggle auto-compaction
|
|
5531
|
+
* Toggle auto-compaction for this session only. Persisting the flag is a
|
|
5532
|
+
* settings-editor concern; a session-level command (RPC `set_auto_compaction`,
|
|
5533
|
+
* one thread of a multi-session host) must never rewrite the global setting
|
|
5534
|
+
* for every other session on the machine (#1422).
|
|
5421
5535
|
*/
|
|
5422
5536
|
setAutoCompactionEnabled(enabled) {
|
|
5423
|
-
this.
|
|
5537
|
+
this._autoCompactionSessionOverride = enabled;
|
|
5424
5538
|
this._emitSessionSettingsChanged();
|
|
5425
5539
|
}
|
|
5540
|
+
_getCompactionSettings() {
|
|
5541
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
5542
|
+
if (this._autoCompactionSessionOverride === undefined)
|
|
5543
|
+
return settings;
|
|
5544
|
+
return { ...settings, enabled: this._autoCompactionSessionOverride };
|
|
5545
|
+
}
|
|
5426
5546
|
_emitSessionSettingsChanged() {
|
|
5427
5547
|
this._emit({
|
|
5428
5548
|
type: "session_settings_changed",
|
|
@@ -5431,9 +5551,9 @@ export class AgentSession {
|
|
|
5431
5551
|
autoCompactionEnabled: this.autoCompactionEnabled,
|
|
5432
5552
|
});
|
|
5433
5553
|
}
|
|
5434
|
-
/** Whether auto-compaction is enabled */
|
|
5554
|
+
/** Whether auto-compaction is enabled for this session */
|
|
5435
5555
|
get autoCompactionEnabled() {
|
|
5436
|
-
return this.settingsManager.getCompactionEnabled();
|
|
5556
|
+
return this._autoCompactionSessionOverride ?? this.settingsManager.getCompactionEnabled();
|
|
5437
5557
|
}
|
|
5438
5558
|
async bindExtensions(bindings) {
|
|
5439
5559
|
const finishBindingWork = this._sessionWorkBarrier.begin();
|
|
@@ -5670,7 +5790,7 @@ export class AgentSession {
|
|
|
5670
5790
|
this._extensionShutdownHandler?.();
|
|
5671
5791
|
},
|
|
5672
5792
|
getContextUsage: () => this.getContextUsage(),
|
|
5673
|
-
getCompactionSettings: () => this.
|
|
5793
|
+
getCompactionSettings: () => this._getCompactionSettings(),
|
|
5674
5794
|
getPromptCacheSafeWaitSeconds: () => this.resolvePromptCacheSafeWaitSeconds(),
|
|
5675
5795
|
getPromptCacheGoalBackstopMaxSeconds: () => this.settingsManager.getPromptCacheGoalBackstopMaxSeconds(),
|
|
5676
5796
|
getPromptCacheKeepAliveSettings: () => this.settingsManager.getPromptCacheKeepAliveSettings(),
|
|
@@ -6659,7 +6779,7 @@ export class AgentSession {
|
|
|
6659
6779
|
// the prior retry checks. Revalidate canonical session context immediately
|
|
6660
6780
|
// before the continuation so a rejected compaction never admits that model.
|
|
6661
6781
|
const model = this.model;
|
|
6662
|
-
const compactionSettings = this.
|
|
6782
|
+
const compactionSettings = this._getCompactionSettings();
|
|
6663
6783
|
const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
|
|
6664
6784
|
if (compactionSettings.enabled &&
|
|
6665
6785
|
model &&
|