billion-context-dsh 0.2.24 → 0.2.25

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [English](./README.en.md) | [中文](./README.md)
4
4
 
5
5
  > **⚠️ Beta notice — not for production use**
6
- > This project (**v0.2.24**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
6
+ > This project (**v0.2.25**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
7
7
 
8
8
  <p align="center">
9
9
  <strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
@@ -64,9 +64,9 @@ dsh plugin --profile web add billion-context-dsh
64
64
  The command installs the package and automatically layers this package's bundle patch ([cordis.patch.yml](cordis.patch.yml)) into the profile's composition. The patch does two things:
65
65
 
66
66
  - **Disables the host `compaction-basic`** — so two backends do not both register `ctx.compaction` in the same realm (modern DSH web bundles already ship this disable; the row is an idempotent safety net that holds on every supported DSH);
67
- - **Mounts the ACP engine at the HOST plane** — the four model tools (`compress` / `decompress` / `search_context` / `acp_status`), the `/acp` command, the advisory nudge, and the ACP guidance section reach **EVERY mode** of the profile (standard / code / minimal / cordis / custom presets). Window auto-detection and the tools/command/nudge defaults are all on — **no manual configuration needed**.
67
+ - **Mounts the ACP engine at the HOST plane** — the four model tools (`compress` / `decompress` / `search_context` / `acp_status`), the `/acp-prune` command, the advisory nudge, and the ACP guidance section reach **EVERY mode** of the profile (standard / code / minimal / cordis / custom presets). Window auto-detection and the tools/command/nudge defaults are all on — **no manual configuration needed**.
68
68
 
69
- Restart `dsh` afterwards (bundle layers are composed at startup), open a new session, and verify: ask the model to call `acp_status`, or run `/acp status`. Shipped presets (standard / code / cordis) keep their realm-local `compaction-basic` fallback (automatic pressure compression still runs there; the ACP tools and nudge coexist); minimal and presets without a compaction realm use this engine directly.
69
+ Restart `dsh` afterwards (bundle layers are composed at startup), open a new session, and verify: ask the model to call `acp_status`, or run `/acp-prune status`. Shipped presets (standard / code / cordis) keep their realm-local `compaction-basic` fallback (automatic pressure compression still runs there; the ACP tools and nudge coexist); minimal and presets without a compaction realm use this engine directly.
70
70
 
71
71
  > **DSH version compatibility.** The package declares all five runtime seam
72
72
  > packages (`dsh-compaction` / `dsh-session` / `dsh-llm` / `dsh-tools` /
@@ -100,7 +100,7 @@ This only installs the package into your project/global store; it does **not** t
100
100
  **Install from the git source (`github:` spec — the form the plugin store shows).** The prebuilt `dist/` artifacts are committed to this repository, so a git-source install also works out of the box — **no build step needed**, and pnpm 11's default build-script blocking (`allowBuilds`) never applies to this package:
101
101
 
102
102
  ```bash
103
- dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.24
103
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.25
104
104
  ```
105
105
 
106
106
  Prefer a `#<tag>` ref to get artifacts identical to that npm release; without a ref you get the latest default-branch build. Only building the repo yourself (`npm run build`) requires approving build scripts. Background and trade-offs: [docs/git-source-install-design.md](docs/git-source-install-design.md) (issue #92).
@@ -121,7 +121,7 @@ Two audiences: ① Path B (plain npm install) users, who must write a compositio
121
121
  **Global — host plane, every mode** (recommended). This is what Path A's bundle already does; plain-npm users add all of the following (bundle users skip the first two rows):
122
122
 
123
123
  ```yaml
124
- # ACP as the global compaction backend: four model tools + `/acp` command +
124
+ # ACP as the global compaction backend: four model tools + `/acp-prune` command +
125
125
  # nudge + ACP guidance section for EVERY mode
126
126
  # (standard / code / minimal / cordis / custom presets).
127
127
  # Must also disable the host compaction-basic: two backends providing
@@ -151,7 +151,7 @@ Two audiences: ① Path B (plain npm install) users, who must write a compositio
151
151
 
152
152
  See [docs/configurable-prompts-design.md](docs/configurable-prompts-design.md) for the full slot list, per-slot placeholders, and the empty-string/`null` semantics. Deployments that omit `prompts` use the kernel rendering directly (aligned with kernel/pi; see design doc v6).
153
153
 
154
- **(Optional) Runtime settings — edit `~/.dsh/settings.yaml` or use `/acp config`; no restart.** Six scalar keys (`modelContextLimit`, `autoModelContextLimit`, `nudgeMinContextLimitPct`, `nudgeMaxContextLimitPct`, `nudgeEmergencyThresholdPct`, `autoNudge` — the rows marked “runtime-adjustable” in the Configuration table) have a hot-editable copy in the host settings layer: editing the settings file or `/acp config` takes effect **immediately on running sessions** (the composition-row `config:` stays the starting point — the layering is schema default → composition row → user settings section):
154
+ **(Optional) Runtime settings — edit `~/.dsh/settings.yaml` or use `/acp-prune config`; no restart.** Six scalar keys (`modelContextLimit`, `autoModelContextLimit`, `nudgeMinContextLimitPct`, `nudgeMaxContextLimitPct`, `nudgeEmergencyThresholdPct`, `autoNudge` — the rows marked “runtime-adjustable” in the Configuration table) have a hot-editable copy in the host settings layer: editing the settings file or `/acp-prune config` takes effect **immediately on running sessions** (the composition-row `config:` stays the starting point — the layering is schema default → composition row → user settings section):
155
155
 
156
156
  ```yaml
157
157
  # ~/.dsh/settings.yaml
@@ -160,14 +160,14 @@ compaction-acp:
160
160
  ```
161
161
 
162
162
  ```text
163
- /acp config # list the six keys + source layer (user / base / default)
164
- /acp config set nudgeMaxContextLimitPct 0.72 # change one key live
165
- /acp config set autoNudge false # boolean keys accept false
166
- /acp config reset nudgeMaxContextLimitPct # back to the composition row / engine default
167
- /acp config reset all
163
+ /acp-prune config # list the six keys + source layer (user / base / default)
164
+ /acp-prune config set nudgeMaxContextLimitPct 0.72 # change one key live
165
+ /acp-prune config set autoNudge false # boolean keys accept false
166
+ /acp-prune config reset nudgeMaxContextLimitPct # back to the composition row / engine default
167
+ /acp-prune config reset all
168
168
  ```
169
169
 
170
- Changing a window key (`modelContextLimit` / `autoModelContextLimit`) clears the window-probe cache — the next pre-step re-probes under the new values (probe failures are cached too, so this is also how a fixed gateway gets re-probed). In provider-less plain-npm compositions `/acp config` degrades to advice text; `settingsEnabled: false` disables the integration entirely (composition-row-only — the switch is deliberately NOT part of the settings layer: it cannot turn itself off). Design details: [docs/settings-integration-design.md](docs/settings-integration-design.md).
170
+ Changing a window key (`modelContextLimit` / `autoModelContextLimit`) clears the window-probe cache — the next pre-step re-probes under the new values (probe failures are cached too, so this is also how a fixed gateway gets re-probed). In provider-less plain-npm compositions `/acp-prune config` degrades to advice text; `settingsEnabled: false` disables the integration entirely (composition-row-only — the switch is deliberately NOT part of the settings layer: it cannot turn itself off). Design details: [docs/settings-integration-design.md](docs/settings-integration-design.md).
171
171
 
172
172
  **Per-mode — an agent preset's `compaction` realm.** First *disable (or delete) the realm's existing `dsh-compaction-basic` row*, then mount this engine — two backends cannot coexist in the same realm:
173
173
 
@@ -201,7 +201,7 @@ DSH derives every model request from its append-only session log (the *surface*)
201
201
  | tiered distillation (T2/T3) | re-compressing a block's summary node distills that block (tier 2); distilling a tier-2 block yields tier 3. Tier + kernel block ids are persisted to the log, so kernel state rehydrates from the log after a restart and stays distillable |
202
202
  | compression accounting (shadow price) | `shadowedTokenCount` (what the host occupancy display deducts) is priced with the **host token-meter's fixed-heuristic price** (`ctx.tokenMeter.measure` preferred, reading the `heuristicTokens ?? tokens` fixed-heuristic basis; exact mirror in `src/host-tokens.ts` as fallback) — never the plugin's internal CJK-aware estimate (that is display currency; mixing it into the host ledger can drive `messageTokens` negative and brick a CJK-heavy session, issue #54), and never the route-repriced `node.tokens` either (under 0.1.2+ image route pricing that is request-pressure currency; summing it overstates an image range's claim and folds the same ledger negative, issue #103) |
203
203
  | image/file block visibility | `image`/`file` blocks carry no characters, and the projection used to drop them silently — so they had no ref in the compressible-range table, could not act as a boundary, were invisible to the kernel's "last user message" protection, and were priced at 0 tokens (on a screenshot session the last question could be compressed away entirely; issue #117). `extractText` now renders a deterministic one-line placeholder from durable attachment metadata (`[image image/png shot.png 800x600 4.2KB]`, `[file notes.txt 1.0KB]` — the same idea as the host's own handle-text projection for files), the media price is the difference between the host token-meter node's two prices (`tokens` − `heuristicTokens`: the second is the route-independent fixed heuristic, the first carries the adapter's declared visual price for a media occurrence, so the difference is exactly what the route charged on top) **plus** the fixed structural price the host heuristic charges for a media reference (`hostMediaStructuralPrice`, a mirror of the host's own `estimateStructuralBlock`) — no adapter declares a visual price today, so the difference alone would leave pictures looking free again, and range rows carry `[+N images]`; a session with no media pays nothing extra |
204
- | injected-instruction hygiene | the host injects policy files (AGENTS.md etc.) onto the session surface; compressing the **current copy** of one makes the host re-inject the same file immediately — a compress → re-inject → compress loop (issue #71). The compressible-range table treats injected rows as barriers: never offered, never part of a span; a manual compress — the model's compress tool AND the human /acp compress command alike — that covers a file's current copy is **rejected outright** (the error names the seq(s) and points out that stale copies are fine to compress) — compressing a current copy reclaims nothing because the host always re-pastes it. Rows are grouped by `source.changes[].scope` (= one file) and only each group's newest copy is protected — older copies superseded by an update stay safely compressible |
204
+ | injected-instruction hygiene | the host injects policy files (AGENTS.md etc.) onto the session surface; compressing the **current copy** of one makes the host re-inject the same file immediately — a compress → re-inject → compress loop (issue #71). The compressible-range table treats injected rows as barriers: never offered, never part of a span; a manual compress — the model's compress tool AND the human /acp-prune compress command alike — that covers a file's current copy is **rejected outright** (the error names the seq(s) and points out that stale copies are fine to compress) — compressing a current copy reclaims nothing because the host always re-pastes it. Rows are grouped by `source.changes[].scope` (= one file) and only each group's newest copy is protected — older copies superseded by an update stay safely compressible |
205
205
 
206
206
  The load-bearing compression guidance (tools, philosophy, summary rules, tier rules) is registered as a one-time system-prompt section; each nudge carries a condensed version (efficiency note + philosophy + context breakdown + HOW_TO_COMPRESS_RULES + range table + batch tip). There is deliberately **no automatic summarization**: automatic policy only nudges the model (`compactIfNeeded` returns null).
207
207
 
@@ -219,7 +219,7 @@ A walkthrough of the ACP philosophy this project inherits — how active context
219
219
  | `decompress` | Restore a previously compressed block's original content (read-only); accepts the `bN` ref shown by acp_status or a compaction id. Large blocks are paged by size — each page stays under the host tool-result trim budget (~7K chars, up to 100 messages) — so a normal page comes back intact; pass `offset`/`limit` and follow the continue hint in the result to walk a very large block |
220
220
  | `search_context` | Search compressed block summaries and originals by keyword (acp-kernel hybrid retrieval: stemming + CJK bigrams + fuzzy); hits link back to the owning block |
221
221
  | `acp_status` | CONTEXT BREAKDOWN (tool/text/summaries shares of the visible total) + compressed-block ledger + nudge decision line + a `Checkpoint seqs` row mapping each ACTIVE block's kernel ref (`bN`) to its checkpoint summary seq (the distill entry point, issue #60); no context-window rows. Drilldown supported: `scope:"compressed"` per block, `scope:"uncompressed"` + `view:"messages"`/`"ranges"` per message/range, with `tool` filter, `sort` order and `limit` cap. Drilldown row refs are kernel ids (mN) — feed them straight to `compress` as `startSeq`/`endSeq` (auto-mapped to the live surface seq); `Surface:` seqs work too |
222
- | `/acp` | status / compress / decompress from the command bar; status also shows human-side window info (estimated context, window source, compressed-block ledger, and **nudge arbitration** — `nudge: idle/ACTIVE — reason` plus how many tokens remain until the next nudge, decided by the same kernel turn as the nudge path) |
222
+ | `/acp-prune` | status / compress / decompress from the command bar; status also shows human-side window info (estimated context, window source, compressed-block ledger, and **nudge arbitration** — `nudge: idle/ACTIVE — reason` plus how many tokens remain until the next nudge, decided by the same kernel turn as the nudge path) |
223
223
 
224
224
  - **Summary source framing**: every compaction summary is prefixed at write time with `[Model-written summary — not user words; re-verify any obligations before relying on them]` — written into BOTH the durable summary event and its checkpoint node (one identical text), with an idempotent projection-time safety net for legacy blocks. Purpose: stop the model from executing obligation-like sentences inside a summary as if they were the user's own words.
225
225
  - **Slim nudges**: the compression philosophy/rules sections are no longer repeated in every nudge body (they already live in the system prompt); a nudge carries only the trigger frame + context breakdown + range table. The template path (`config.prompts.nudge`) strips the same sections. Design background: [docs/injection-governance-design.md](docs/injection-governance-design.md).
@@ -241,16 +241,16 @@ This project reuses `acp-kernel`'s compression core and `billion-context-pi`'s d
241
241
 
242
242
  | Key | Default | Meaning |
243
243
  |---|---|---|
244
- | `modelContextLimit` | auto-detected (fallback `128000`) | Context window used for the kernel's pressure decisions; an explicit value wins and skips detection. When omitted, the host session projection `contextPressure.contextWindow` is read first — the capacity disclosed for the **current real route** (a session that switched models follows automatically, no restart needed) — and the model API is probed only when the projection discloses no window; an explicit value also skips the output-reservation subtraction (the operator owns the denominator) (runtime-adjustable: `/acp config`) |
245
- | `autoModelContextLimit` | `true` | Resolve the real context window automatically: the host projection first (`windowFor` → `projectedContextWindow`, `src/window.ts`), then the model API probe (`agent.ctx.llm.resolveModelInfo`); both are skipped when `autoModelContextLimit: false`. On probe failure it falls back to the default, and the `/acp` command shows the window source (the `acp_status` model tool carries no window info). A failed probe is surfaced in the host log and the `/acp` panel (`restart to re-probe`) — the failure is cached like a success, so fixing the gateway requires a restart or an explicit `modelContextLimit` before the probe retries. On a successful probe the adapter's per-request output cap (`defaultMaxTokens` — the output reservation the provider guarantees at the end of the window) is SUBTRACTED, so every downstream pressure decision (nudge tiers, truncate, growth) measures usage against the SUSTAINABLE input budget (window − reservation): a 96K window with a 16K cap carries at most 80K of input, and the raw denominator understated usage by cap/window (≈17% there — and the ratio is far higher on short-window models, where the same cap is a quarter or more of the window). When the cap is undisclosed, the limit is explicit, or the probe fails, the raw-window behavior is kept; `/acp status` shows the subtraction (raw − reservation). Changing `modelContextLimit`/`autoModelContextLimit` via `/acp config` clears the window cache so the probe re-runs immediately |
246
- | `nudgeMinContextLimitPct` | kernel default `0.45` | Nudge window lower bound (usage fraction) — validation only; the growth-driven trigger has no percentage floor — same default as billion-context-pi (runtime-adjustable: `/acp config`) |
247
- | `nudgeMaxContextLimitPct` | engine default `0.70` (kernel/pi default `0.75`) | Over-limit line: above this the nudge fires regardless of growth — deliberately below the host compaction-basic 80% auto-compaction line so the forced nudge fires first; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) (runtime-adjustable: `/acp config`) |
248
- | `nudgeEmergencyThresholdPct` | engine default `0.85` (kernel/pi default `0.95`) | Emergency nudge (bypasses the per-turn dedup, but is capped at 3 injections per user turn — issue #108) — lowered from `0.95`: at 95% the model has no room to act and the 80% auto-compaction line shadows it; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) (runtime-adjustable: `/acp config`) |
249
- | `preset` | — | (optional) Pick the nudge aggressiveness in one word: `preserve` / `relaxed` / `balanced` / `efficient` / `aggressive` (see “Presets” below). Fills ONLY the three nudge thresholds you did not set explicitly; precedence is explicit value > `preset` > engine default. An unknown name fails construction, and so does a merged window that ends up inverted (wrong `min` / `max` / `emergency` order). Does not touch any other knob (`modelContextLimit` / `autoNudge` / `prompts` / `coreOverrides`) (composition-only: not yet wired into `/acp config` — a follow-up on issue #75) |
244
+ | `modelContextLimit` | auto-detected (fallback `128000`) | Context window used for the kernel's pressure decisions; an explicit value wins and skips detection. When omitted, the host session projection `contextPressure.contextWindow` is read first — the capacity disclosed for the **current real route** (a session that switched models follows automatically, no restart needed) — and the model API is probed only when the projection discloses no window; an explicit value also skips the output-reservation subtraction (the operator owns the denominator) (runtime-adjustable: `/acp-prune config`) |
245
+ | `autoModelContextLimit` | `true` | Resolve the real context window automatically: the host projection first (`windowFor` → `projectedContextWindow`, `src/window.ts`), then the model API probe (`agent.ctx.llm.resolveModelInfo`); both are skipped when `autoModelContextLimit: false`. On probe failure it falls back to the default, and the `/acp-prune` command shows the window source (the `acp_status` model tool carries no window info). A failed probe is surfaced in the host log and the `/acp-prune` panel (`restart to re-probe`) — the failure is cached like a success, so fixing the gateway requires a restart or an explicit `modelContextLimit` before the probe retries. On a successful probe the adapter's per-request output cap (`defaultMaxTokens` — the output reservation the provider guarantees at the end of the window) is SUBTRACTED, so every downstream pressure decision (nudge tiers, truncate, growth) measures usage against the SUSTAINABLE input budget (window − reservation): a 96K window with a 16K cap carries at most 80K of input, and the raw denominator understated usage by cap/window (≈17% there — and the ratio is far higher on short-window models, where the same cap is a quarter or more of the window). When the cap is undisclosed, the limit is explicit, or the probe fails, the raw-window behavior is kept; `/acp-prune status` shows the subtraction (raw − reservation). Changing `modelContextLimit`/`autoModelContextLimit` via `/acp-prune config` clears the window cache so the probe re-runs immediately |
246
+ | `nudgeMinContextLimitPct` | kernel default `0.45` | Nudge window lower bound (usage fraction) — validation only; the growth-driven trigger has no percentage floor — same default as billion-context-pi (runtime-adjustable: `/acp-prune config`) |
247
+ | `nudgeMaxContextLimitPct` | engine default `0.70` (kernel/pi default `0.75`) | Over-limit line: above this the nudge fires regardless of growth — deliberately below the host compaction-basic 80% auto-compaction line so the forced nudge fires first; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) (runtime-adjustable: `/acp-prune config`) |
248
+ | `nudgeEmergencyThresholdPct` | engine default `0.85` (kernel/pi default `0.95`) | Emergency nudge (bypasses the per-turn dedup, but is capped at 3 injections per user turn — issue #108) — lowered from `0.95`: at 95% the model has no room to act and the 80% auto-compaction line shadows it; an explicit value wins (a same-name key in `coreOverrides.nudge` outranks it — see below) (runtime-adjustable: `/acp-prune config`) |
249
+ | `preset` | — | (optional) Pick the nudge aggressiveness in one word: `preserve` / `relaxed` / `balanced` / `efficient` / `aggressive` (see “Presets” below). Fills ONLY the three nudge thresholds you did not set explicitly; precedence is explicit value > `preset` > engine default. An unknown name fails construction, and so does a merged window that ends up inverted (wrong `min` / `max` / `emergency` order). Does not touch any other knob (`modelContextLimit` / `autoNudge` / `prompts` / `coreOverrides`) (composition-only: not yet wired into `/acp-prune config` — a follow-up on issue #75) |
250
250
  | `coreOverrides` | — | Any other acp-kernel `Config` override (billion-context-pi's `coreOverrides` escape hatch). Merge order: kernel defaults → top-level pct knobs → `coreOverrides.nudge` lands last — same-name keys take its value (read-only: composition-row-only, not exposed through settings) |
251
251
  | `autoTools` | `true` | Register the four model tools on `ctx.tools` |
252
- | `autoCommand` | `true` | Register the `/acp` command on `ctx.commands` |
253
- | `autoNudge` | `true` | Inject the nudge into `agent/pre-step` (runtime-adjustable: `/acp config`) |
252
+ | `autoCommand` | `true` | Register the `/acp-prune` command on `ctx.commands` |
253
+ | `autoNudge` | `true` | Inject the nudge into `agent/pre-step` (runtime-adjustable: `/acp-prune config`) |
254
254
  | `settingsEnabled` | `true` (enabled when unset) | (optional) Disable the runtime-settings integration entirely (composition-row-only, deliberately NOT in the settings layer — the switch cannot turn itself off; with it off the composition-row `config:` stays the only effective channel) |
255
255
  | `prompts` | — | (optional) Custom prompt copy: per-slot overrides for nudge / range table / system prompt / tool descriptions (template + named placeholders, validated at construction; see “Custom prompt copy” above and [docs/configurable-prompts-design.md](docs/configurable-prompts-design.md)) |
256
256
 
@@ -268,9 +268,9 @@ If you do not want to tune three percentages by hand, pick the nudge aggressiven
268
268
 
269
269
  - **Fills only what you left unset**: `preset` fills ONLY the `nudge*ContextLimitPct` values you did not set explicitly; if you set both a `preset` and one of those thresholds, your explicit value wins (precedence: explicit > preset > default).
270
270
  - **No other knob is touched**: `modelContextLimit`, `autoNudge`, `prompts`, and `coreOverrides` are unaffected; `coreOverrides.nudge` still lands last and its same-name keys outrank everything.
271
- - **See the active tier**: `/acp status` prints the effective `preset` and the three thresholds **actually in force** — the line mirrors `kernelConfigFor`'s merge order, so any explicit override you made on top of the preset AND any same-name key in `coreOverrides.nudge` are shown as they really apply.
271
+ - **See the active tier**: `/acp-prune status` prints the effective `preset` and the three thresholds **actually in force** — the line mirrors `kernelConfigFor`'s merge order, so any explicit override you made on top of the preset AND any same-name key in `coreOverrides.nudge` are shown as they really apply.
272
272
  - **A typo fails loudly**: an unknown name throws at engine construction and lists the valid tiers (the same fail-fast contract as custom prompt templates) — it never silently falls back to the defaults. The bundle row itself carries no `config`, so a `preset` can only come from your own same-id `compaction-acp` row; if that row fails to construct, the profile stays down until you fix it (intended fail-fast, not a defect).
273
- - **Runtime hot-swap**: presets are set at composition time today (install / `cordis.patch.yml`); once #75's settings.yaml hot-reload lands they can be changed from `/acp config`. This PR makes them available at the composition layer first.
273
+ - **Runtime hot-swap**: presets are set at composition time today (install / `cordis.patch.yml`); once #75's settings.yaml hot-reload lands they can be changed from `/acp-prune config`. This PR makes them available at the composition layer first.
274
274
  - **Two knobs deliberately left out**: the original request also named `growthRatio` (exists in acp-kernel as `nudge.growthRatio`, reachable via `coreOverrides`) and `protectedLastMessages` (≈ kernel `preserveRecentMessages`). Neither is a first-class engine knob here; adopting them as named keys / UI items is an owner decision, so they are not baked into the presets.
275
275
  - **An inverted window is rejected**: if merging with explicit thresholds produces `min > max`, `max > emergency` or `min > emergency` (e.g. `preset: 'preserve'` with `nudgeMaxContextLimitPct: 0.5`), the engine throws at construction and lists the three values. The kernel only warns about such a config, so this check is added by the engine in `resolveAcpConfig`.
276
276
  - **`max` is the value that races the host's 80% line**: each tier's onset is decided by `max`; the `preserve` / `relaxed` emergency values (0.93 / 0.90) sit above the host compaction-basic 80% line and act only as a label upgrade past it, so they are never reached when the host compacts first.
@@ -304,7 +304,7 @@ src/
304
304
  ├── system-prompt.ts# M4: one-time ACP guidance section (keeps nudges short)
305
305
  ├── config.ts # kernel config assembly (thresholds + coreOverrides)
306
306
  ├── window.ts # auto context-window detection (session projection first, LLM runtime probe fallback, default 128000) + output-reservation probe (defaultMaxTokens, subtracted in windowFor)
307
- └── commands.ts # M4: /acp slash command
307
+ └── commands.ts # M4: /acp-prune slash command
308
308
  ```
309
309
 
310
310
  ## License
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [中文](./README.md) | [English](./README.en.md)
4
4
 
5
5
  > **⚠️ 测试版声明——请勿用于生产环境**
6
- > 本项目(**v0.2.24**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
6
+ > 本项目(**v0.2.25**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
7
7
 
8
8
  <p align="center">
9
9
  <strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
@@ -67,9 +67,9 @@ dsh plugin --profile web add billion-context-dsh
67
67
  命令内部会装包并把本包的 bundle 补丁([cordis.patch.yml](cordis.patch.yml))自动挂进该 profile 的层栈。补丁做了两件事:
68
68
 
69
69
  - **禁用 host 的 `compaction-basic`**——避免同一 realm 内两个后端同时注册 `ctx.compaction` 冲突(现代 DSH 的 web bundle 已自带该禁用,此行为幂等兜底,任何受支持版本下都成立);
70
- - **把 ACP 引擎挂到 host 平面**——四种模型工具(`compress` / `decompress` / `search_context` / `acp_status`)、`/acp` 命令、nudge、ACP 提示词段对该 profile 的**所有模式**(standard / code / minimal / cordis / 自定义预设)生效。窗口自动探测、工具/命令/nudge 默认全开,**无需任何手工配置**。
70
+ - **把 ACP 引擎挂到 host 平面**——四种模型工具(`compress` / `decompress` / `search_context` / `acp_status`)、`/acp-prune` 命令、nudge、ACP 提示词段对该 profile 的**所有模式**(standard / code / minimal / cordis / 自定义预设)生效。窗口自动探测、工具/命令/nudge 默认全开,**无需任何手工配置**。
71
71
 
72
- 装完**重启 `dsh`**(bundle 层在启动时组合),新开会话即可用——让模型调用 `acp_status` 或执行 `/acp status` 自证。shipped 预设(standard / code / cordis)内部的 realm 级 `compaction-basic` 自动压缩兜底仍然保留(这些模式里"自动摘要"照旧,ACP 工具与 nudge 并存);minimal 等不带 compaction realm 的预设直接使用本引擎。
72
+ 装完**重启 `dsh`**(bundle 层在启动时组合),新开会话即可用——让模型调用 `acp_status` 或执行 `/acp-prune status` 自证。shipped 预设(standard / code / cordis)内部的 realm 级 `compaction-basic` 自动压缩兜底仍然保留(这些模式里"自动摘要"照旧,ACP 工具与 nudge 并存);minimal 等不带 compaction realm 的预设直接使用本引擎。
73
73
 
74
74
  > **与 DSH 版本的兼容性。** 包把五个运行期 seam 包(`dsh-compaction` /
75
75
  > `dsh-session` / `dsh-llm` / `dsh-tools` / `dsh-settings`)都声明为 peer
@@ -99,7 +99,7 @@ npm install billion-context-dsh
99
99
  **git 源安装(`github:` 规格,插件商店展示的形态)。** 预构建产物 `dist/` 已提交到仓库,从 git 源安装同样开箱即用——**无需任何构建步骤**,pnpm 11 默认拦截构建脚本(`allowBuilds`)的机制对这个包不构成障碍:
100
100
 
101
101
  ```bash
102
- dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.24
102
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.25
103
103
  ```
104
104
 
105
105
  建议带 `#<tag>` 安装,拿到与对应 npm 版本完全一致的产物;不带 ref 则装默认分支的最新构建。只有 clone 仓库自行从源码构建(`npm run build`)才需要放行构建。背景与方案取舍见 [docs/git-source-install-design.md](docs/git-source-install-design.md)(issue #92)。
@@ -120,7 +120,7 @@ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.24
120
120
  **全局生效(host 平面,所有模式)——推荐**。这是方式一 bundle 的默认行为;纯 npm 安装的用户在 profile 补丁中追加以下全部内容(bundle 用户跳过前两行):
121
121
 
122
122
  ```yaml
123
- # ACP 作为全局压缩后端:四个模型工具 + `/acp` 命令 + nudge + ACP 提示词段,
123
+ # ACP 作为全局压缩后端:四个模型工具 + `/acp-prune` 命令 + nudge + ACP 提示词段,
124
124
  # 对所有模式(standard / code / minimal / cordis / 自定义预设)生效。
125
125
  # 必须同时禁用 host 的 compaction-basic:同一 realm 内两个后端同时
126
126
  # provide `ctx.compaction` 会冲突。(bundle 安装已自动带上这两行。)
@@ -148,7 +148,7 @@ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.24
148
148
 
149
149
  可配置槽位清单、每槽可用占位符、空串/`null` 语义见 [docs/configurable-prompts-design.md](docs/configurable-prompts-design.md)。未配置 `prompts` 的部署直接使用 kernel 渲染(对齐 kernel/pi,见设计文档 v6)。
150
150
 
151
- **(可选)运行时设置 —— 编辑 `~/.dsh/settings.yaml` 或 `/acp config`,无需重启。** 六个标量键(`modelContextLimit`、`autoModelContextLimit`、`nudgeMinContextLimitPct`、`nudgeMaxContextLimitPct`、`nudgeEmergencyThresholdPct`、`autoNudge`,见「配置」表中带「运行时热调」标记的行)在宿主 settings 层有一份可热改的副本:编辑 settings 文件或 `/acp config` 会**立即生效于运行中的会话**(组合行 `config:` 仍是起点——分层为 schema 默认 → 组合行 → 用户 settings 段):
151
+ **(可选)运行时设置 —— 编辑 `~/.dsh/settings.yaml` 或 `/acp-prune config`,无需重启。** 六个标量键(`modelContextLimit`、`autoModelContextLimit`、`nudgeMinContextLimitPct`、`nudgeMaxContextLimitPct`、`nudgeEmergencyThresholdPct`、`autoNudge`,见「配置」表中带「运行时热调」标记的行)在宿主 settings 层有一份可热改的副本:编辑 settings 文件或 `/acp-prune config` 会**立即生效于运行中的会话**(组合行 `config:` 仍是起点——分层为 schema 默认 → 组合行 → 用户 settings 段):
152
152
 
153
153
  ```yaml
154
154
  # ~/.dsh/settings.yaml
@@ -157,14 +157,14 @@ compaction-acp:
157
157
  ```
158
158
 
159
159
  ```text
160
- /acp config # 列出六个键当前值 + 来源层(user / base / default)
161
- /acp config set nudgeMaxContextLimitPct 0.72 # 热改一个键
162
- /acp config set autoNudge false # 布尔键(false 是合法值)
163
- /acp config reset nudgeMaxContextLimitPct # 退回组合行 / 引擎默认
164
- /acp config reset all
160
+ /acp-prune config # 列出六个键当前值 + 来源层(user / base / default)
161
+ /acp-prune config set nudgeMaxContextLimitPct 0.72 # 热改一个键
162
+ /acp-prune config set autoNudge false # 布尔键(false 是合法值)
163
+ /acp-prune config reset nudgeMaxContextLimitPct # 退回组合行 / 引擎默认
164
+ /acp-prune config reset all
165
165
  ```
166
166
 
167
- 窗口相关键(`modelContextLimit` / `autoModelContextLimit`)改动会清空窗口探测缓存——下一次 pre-step 按新值重新探测(探测失败也会被缓存,正是靠这个机制在修复网关后重新探测)。无 settings provider 的纯 npm 安装组合下 `/acp config` 降级为指引文案;`settingsEnabled: false` 可整体关闭该集成(组合行专用,不进 settings 层——开关不能关掉自己)。设计细节见 [docs/settings-integration-design.md](docs/settings-integration-design.md)。
167
+ 窗口相关键(`modelContextLimit` / `autoModelContextLimit`)改动会清空窗口探测缓存——下一次 pre-step 按新值重新探测(探测失败也会被缓存,正是靠这个机制在修复网关后重新探测)。无 settings provider 的纯 npm 安装组合下 `/acp-prune config` 降级为指引文案;`settingsEnabled: false` 可整体关闭该集成(组合行专用,不进 settings 层——开关不能关掉自己)。设计细节见 [docs/settings-integration-design.md](docs/settings-integration-design.md)。
168
168
 
169
169
  **单模式生效(agent preset 的 `compaction` realm)**。先在该 realm 内*禁用(或删除)原有的 `dsh-compaction-basic` 行*,再插入本引擎——同一 realm 内两个后端不能并存:
170
170
 
@@ -198,7 +198,7 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
198
198
  | 分层蒸馏(T2/T3) | 再次压缩某块的摘要节点 = 蒸馏该块(tier 2),蒸馏 tier-2 块得 tier 3;tier 与内核块 id 持久化进日志,重启后内核状态从日志再水合、可继续蒸馏 |
199
199
  | 压缩记账(影子价格) | `shadowedTokenCount`(宿主占用率据此扣减)**用宿主 token-meter 的固定启发价计价**(`ctx.tokenMeter.measure` 优先、按 `heuristicTokens ?? tokens` 读固定启发价基准,`src/host-tokens.ts` 精确镜像兜底)——绝不混用插件内部的 CJK 感知估算(那是展示货币,混用会把宿主账本扣成负数、卡死中文会话,issue #54),也不按路由重定价的 `node.tokens` 计价(0.1.2+ 图片路由计价下那是请求压力价,读它会让含图片区间的 claim 虚报视觉价、同样扣穿账本,issue #103) |
200
200
  | 图片/文件块可见性 | `image`/`file` 块没有字符,早先被投影层静默丢弃——于是它们在压缩范围表里没有 ref、不能当边界、不受内核"最近一条 user 消息"保护,还按 0 token 计价(截图会话里最后一条提问可能被整段压掉,issue #117)。现在 `extractText` 用附件的持久元数据生成一行确定性占位符(`[image image/png shot.png 800x600 4.2KB]`、`[file notes.txt 1.0KB]`,与宿主对文件的 handle 文本投影同源),媒体价格读宿主 token-meter 节点上 `tokens` 与 `heuristicTokens` 的差值(该节点只暴露这两个价格:`tokens` 是当前路由下的请求压力,媒体出现时带适配器声明的视觉价,`heuristicTokens` 是与路由无关的固定启发式——差值即路由多收的那部分),并在此之上**叠加宿主固定启发式对媒体引用的结构价**(`hostMediaStructuralPrice`,镜像宿主自己的 `estimateStructuralBlock`):今天所有适配器都不声明视觉价,差值恒为 0,只读差值会让图片重新看起来"免费",范围表行尾标注 `[+N images]`;无媒体的会话不做任何额外测量 |
201
- | 注入指令行卫生 | 宿主会把 AGENTS.md 等策略文件注入会话表面;压缩其**当前副本**会让宿主立刻重注入同一份文件——压缩→重注→压缩死循环(issue #71)。可压缩范围表把注入行当屏障:永不提供、永不计入段;手动压缩(模型的 compress 工具、人用的 /acp compress 命令)若覆盖某文件的当前副本都会被**直接拒绝**(报错点名行号,并提示过期的旧副本可以压),因为压当前副本没有收益——宿主必重贴。按 `source.changes[].scope`(= 一个文件)分组,只有每组最新副本受保护——被更新取代的旧副本可以安全压缩 |
201
+ | 注入指令行卫生 | 宿主会把 AGENTS.md 等策略文件注入会话表面;压缩其**当前副本**会让宿主立刻重注入同一份文件——压缩→重注→压缩死循环(issue #71)。可压缩范围表把注入行当屏障:永不提供、永不计入段;手动压缩(模型的 compress 工具、人用的 /acp-prune compress 命令)若覆盖某文件的当前副本都会被**直接拒绝**(报错点名行号,并提示过期的旧副本可以压),因为压当前副本没有收益——宿主必重贴。按 `source.changes[].scope`(= 一个文件)分组,只有每组最新副本受保护——被更新取代的旧副本可以安全压缩 |
202
202
 
203
203
  承载性的压缩指引(工具、哲学、摘要规则、tier 蒸馏/浓缩规则)注册为一次性系统提示段;每条 nudge 携带精简版(效率提示 + 哲学 + 上下文分解 + 压缩规则 + 范围表 + 批量提示)。刻意**不做自动摘要**:自动策略只 nudge 模型(`compactIfNeeded` 返回 null)。
204
204
 
@@ -216,7 +216,7 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
216
216
  | `decompress` | 恢复已压缩块的原始内容(只读);接受 acp_status 显示的 `bN` 或 compaction id。大块按字符预算分页(默认每页约 7K 字符、至多 100 条),使普通页面低于宿主 tool-result 截断阈值(8192);`offset`/`limit` + 续页提示走完全块 |
217
217
  | `search_context` | 按关键词搜索压缩块摘要与原文(acp-kernel hybrid 检索:词干化 + CJK bigram + 模糊);命中回链所属块 |
218
218
  | `acp_status` | CONTEXT BREAKDOWN(tool/text/summaries 占可见总量)+ 压缩块账本 + nudge 决策行 + `Checkpoint seqs` 行(active 块的 `bN → seq` 蒸馏入口,issue #60);不含上下文窗口。支持钻取:`scope:"compressed"` 逐块、`scope:"uncompressed"` + `view:"messages"`/`"ranges"` 逐消息/区间,`tool` 过滤、`sort` 排序、`limit` 截断。钻取行 ref 是内核 mN——可直接作为 `compress` 的 `startSeq`/`endSeq`(自动映射为 live surface seq);`Surface:` 的 seq 同样可用 |
219
- | `/acp` | 从命令栏执行 status / compress / decompress;status 额外展示 human-side 窗口信息(estimated context、context window 来源、压缩账本、**nudge 仲裁**——`nudge: idle/ACTIVE — reason` 及距下一次 nudge 还差多少 token,与 nudge 路径同一内核判定) |
219
+ | `/acp-prune` | 从命令栏执行 status / compress / decompress;status 额外展示 human-side 窗口信息(estimated context、context window 来源、压缩账本、**nudge 仲裁**——`nudge: idle/ACTIVE — reason` 及距下一次 nudge 还差多少 token,与 nudge 路径同一内核判定) |
220
220
 
221
221
  - **摘要标源(模型自写摘要的框架行)**:每条压缩摘要在写入时前置 `[Model-written summary — not user words; re-verify any obligations before relying on them]`——同时写进持久化摘要事件与其 checkpoint 节点(同一份文本),投影路径对旧块幂等补框。目的:阻止模型把摘要里的义务句当成用户原话直接执行。
222
222
  - **瘦身 nudge**:压缩哲学/规则段不再随每次 nudge 重复(它们已在系统提示里);nudge 正文只保留触发框架 + 上下文分解 + 范围表。模板路径(`config.prompts.nudge`)同样摘除这些段落。设计背景:[docs/injection-governance-design.md](docs/injection-governance-design.md)。
@@ -238,16 +238,16 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
238
238
 
239
239
  | 键 | 默认值 | 含义 |
240
240
  |---|---|---|
241
- | `modelContextLimit` | 自动探测(回退 `128000`) | 用于内核压力决策的上下文窗口;显式配置时优先且跳过探测。省略时优先读宿主会话投影 `contextPressure.contextWindow`(按**当前真实路由**披露的新窗口,切模型会话自动跟随,无需重启),无投影时再从模型 API 探测;显式配置同样跳过输出预留扣减(分母完全由操作者定义)(运行时热调:`/acp config`) |
242
- | `autoModelContextLimit` | `true` | 从模型 API 自动探测真实窗口(`agent.ctx.llm.resolveModelInfo`);探测失败回退默认值,`/acp` 命令展示窗口来源(模型工具 `acp_status` 不含窗口信息)。省略时窗口先读宿主投影(`windowFor` → `projectedContextWindow`,`src/window.ts`)再走探测;投影与探测在 `autoModelContextLimit: false` 时均跳过。探测失败会在宿主日志与 `/acp` 面板提示(`restart to re-probe`)——失败结果同样被缓存,修复网关后需重启或显式设置 `modelContextLimit` 才会重新探测。探测成功后还会**扣减 adapter 的每请求输出上限**(`defaultMaxTokens`,窗口末端每请求保证的输出预留):所有下游压力决策(nudge 档位、truncate、growth)以「可持续输入预算」(窗口 − 输出预留)为分母——96K 窗口 + 16K 上限实际最多承载 80K 输入,原裸窗口分母会把用量低估 cap/window(此处 ≈17%;小上下文窗口模型比例更高);上限未披露、显式配置或探测失败时保持裸窗口行为,`/acp status` 展示扣减明细(raw − reservation)。经 `/acp config` 改动 `modelContextLimit`/`autoModelContextLimit` 会清空窗口缓存,改完即重探 |
243
- | `nudgeMinContextLimitPct` | 内核默认 `0.45` | Nudge 窗口下界(用量占比)——仅作配置校验,增长路径的触发没有百分比下限——与 billion-context-pi 相同的默认值(运行时热调:`/acp config`) |
244
- | `nudgeMaxContextLimitPct` | engine 默认 `0.70`(内核/pi 默认 `0.75`) | 过限线:超过此值则无论增长与否都触发 nudge——刻意低于宿主 compaction-basic 的 80% 自动压缩线,保证强制 nudge 先触发;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下)(运行时热调:`/acp config`) |
245
- | `nudgeEmergencyThresholdPct` | engine 默认 `0.85`(内核/pi 默认 `0.95`) | 紧急 nudge(绕过每轮去重,但每个 user turn 最多注入 3 次——issue #108)——从 `0.95` 下调:95% 时模型已无操作空间且会被 80% 自动压缩线遮蔽;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下)(运行时热调:`/acp config`) |
246
- | `preset` | — | (可选)一句话选择 nudge 的激进程度:`preserve` / `relaxed` / `balanced` / `efficient` / `aggressive`(详见下文「预设」)。只填充你**未显式设置**的三个 nudge 阈值,优先级 显式值 > `preset` > engine 默认;未知名称在构造期报错,与显式阈值合并后若窗口反向(`min` / `max` / `emergency` 顺序错误)同样在构造期报错。不影响其他键(`modelContextLimit` / `autoNudge` / `prompts` / `coreOverrides`) (组合行专用:`preset` 尚未接入 `/acp config`,见 issue #75 后续) |
241
+ | `modelContextLimit` | 自动探测(回退 `128000`) | 用于内核压力决策的上下文窗口;显式配置时优先且跳过探测。省略时优先读宿主会话投影 `contextPressure.contextWindow`(按**当前真实路由**披露的新窗口,切模型会话自动跟随,无需重启),无投影时再从模型 API 探测;显式配置同样跳过输出预留扣减(分母完全由操作者定义)(运行时热调:`/acp-prune config`) |
242
+ | `autoModelContextLimit` | `true` | 从模型 API 自动探测真实窗口(`agent.ctx.llm.resolveModelInfo`);探测失败回退默认值,`/acp-prune` 命令展示窗口来源(模型工具 `acp_status` 不含窗口信息)。省略时窗口先读宿主投影(`windowFor` → `projectedContextWindow`,`src/window.ts`)再走探测;投影与探测在 `autoModelContextLimit: false` 时均跳过。探测失败会在宿主日志与 `/acp-prune` 面板提示(`restart to re-probe`)——失败结果同样被缓存,修复网关后需重启或显式设置 `modelContextLimit` 才会重新探测。探测成功后还会**扣减 adapter 的每请求输出上限**(`defaultMaxTokens`,窗口末端每请求保证的输出预留):所有下游压力决策(nudge 档位、truncate、growth)以「可持续输入预算」(窗口 − 输出预留)为分母——96K 窗口 + 16K 上限实际最多承载 80K 输入,原裸窗口分母会把用量低估 cap/window(此处 ≈17%;小上下文窗口模型比例更高);上限未披露、显式配置或探测失败时保持裸窗口行为,`/acp-prune status` 展示扣减明细(raw − reservation)。经 `/acp-prune config` 改动 `modelContextLimit`/`autoModelContextLimit` 会清空窗口缓存,改完即重探 |
243
+ | `nudgeMinContextLimitPct` | 内核默认 `0.45` | Nudge 窗口下界(用量占比)——仅作配置校验,增长路径的触发没有百分比下限——与 billion-context-pi 相同的默认值(运行时热调:`/acp-prune config`) |
244
+ | `nudgeMaxContextLimitPct` | engine 默认 `0.70`(内核/pi 默认 `0.75`) | 过限线:超过此值则无论增长与否都触发 nudge——刻意低于宿主 compaction-basic 的 80% 自动压缩线,保证强制 nudge 先触发;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下)(运行时热调:`/acp-prune config`) |
245
+ | `nudgeEmergencyThresholdPct` | engine 默认 `0.85`(内核/pi 默认 `0.95`) | 紧急 nudge(绕过每轮去重,但每个 user turn 最多注入 3 次——issue #108)——从 `0.95` 下调:95% 时模型已无操作空间且会被 80% 自动压缩线遮蔽;显式配置优先(`coreOverrides.nudge` 同名键优先级更高,见下)(运行时热调:`/acp-prune config`) |
246
+ | `preset` | — | (可选)一句话选择 nudge 的激进程度:`preserve` / `relaxed` / `balanced` / `efficient` / `aggressive`(详见下文「预设」)。只填充你**未显式设置**的三个 nudge 阈值,优先级 显式值 > `preset` > engine 默认;未知名称在构造期报错,与显式阈值合并后若窗口反向(`min` / `max` / `emergency` 顺序错误)同样在构造期报错。不影响其他键(`modelContextLimit` / `autoNudge` / `prompts` / `coreOverrides`) (组合行专用:`preset` 尚未接入 `/acp-prune config`,见 issue #75 后续) |
247
247
  | `coreOverrides` | — | 任何其他 acp-kernel `Config` 覆盖(billion-context-pi 的 `coreOverrides` 逃生口)。合并顺序:内核默认 → 顶层 pct 配置 → `coreOverrides.nudge` 最后落地——同名键以它为准(只读:组合行专用,不经 settings 层) |
248
248
  | `autoTools` | `true` | 在 `ctx.tools` 注册四个模型工具 |
249
- | `autoCommand` | `true` | 在 `ctx.commands` 注册 `/acp` 命令 |
250
- | `autoNudge` | `true` | 当内核建议时向 `agent/pre-step` 注入 nudge(运行时热调:`/acp config`) |
249
+ | `autoCommand` | `true` | 在 `ctx.commands` 注册 `/acp-prune` 命令 |
250
+ | `autoNudge` | `true` | 当内核建议时向 `agent/pre-step` 注入 nudge(运行时热调:`/acp-prune config`) |
251
251
  | `settingsEnabled` | `true`(未配置即启用) | (可选)整体关闭运行时设置集成(组合行专用,不进 settings 层——开关不能关掉自己;关闭后组合行 `config:` 仍是唯一生效通道) |
252
252
  | `prompts` | — | (可选)自定义提示词文案:nudge / 范围表 / system prompt / 工具描述按槽位覆盖(模板 + 命名占位符,构造期校验;见上文「自定义提示词文案」与 [docs/configurable-prompts-design.md](docs/configurable-prompts-design.md)) |
253
253
 
@@ -265,9 +265,9 @@ DSH 的每个模型请求都派生自其 append-only 会话日志(*surface*)
265
265
 
266
266
  - **只填未设的阈值**:`preset` 仅填充你没有显式设置的 `nudge*ContextLimitPct`;同时写了 `preset` 和某个阈值时,该阈值以你的显式值为准(优先级 显式 > preset > 默认)。
267
267
  - **不碰其他旋钮**:`modelContextLimit`、`autoNudge`、`prompts`、`coreOverrides` 完全不受影响;`coreOverrides.nudge` 仍最后落地、同名键最高优先。
268
- - **查看当前档位**:`/acp status` 会打印生效的 `preset` 及其**真实生效的**三个阈值——这一行镜像 `kernelConfigFor` 的合并顺序,所以你在其上做的显式覆盖、以及 `coreOverrides.nudge` 里的同名键都会如实显示。
268
+ - **查看当前档位**:`/acp-prune status` 会打印生效的 `preset` 及其**真实生效的**三个阈值——这一行镜像 `kernelConfigFor` 的合并顺序,所以你在其上做的显式覆盖、以及 `coreOverrides.nudge` 里的同名键都会如实显示。
269
269
  - **拼错即报错**:未知名称在引擎构造期直接抛错并列出合法值(与自定义提示词模板同一约定),不会静默回退默认。注意 bundle 行本身不带 `config`,`preset` 只能由你自己的同 id `compaction-acp` 行提供;该行构造失败即挂载失败,profile 会在你修好配置前一直起不来(fail-fast 的既定行为)。
270
- - **运行时热切换**:预设目前走组合配置(安装 / `cordis.patch.yml`);待 #75 的 settings.yaml 热加载落地后,可在 `/acp config` 里改。本 PR 先让它在组合层可用。
270
+ - **运行时热切换**:预设目前走组合配置(安装 / `cordis.patch.yml`);待 #75 的 settings.yaml 热加载落地后,可在 `/acp-prune config` 里改。本 PR 先让它在组合层可用。
271
271
  - **反向窗口直接报错**:与显式阈值合并后若出现 `min > max`、`max > emergency` 或 `min > emergency`(例如 `preset: 'preserve'` 配 `nudgeMaxContextLimitPct: 0.5`),引擎在构造期抛错并列出三个值。内核对这种配置只打警告、不会拒绝,所以这道校验由引擎在 `resolveAcpConfig` 里补上。
272
272
  - **与宿主 80% 线赛跑的是 `max`**:反复触发的过限提醒(`OVER-LIMIT`)由 `max` 决定;`preserve` / `relaxed` 的 emergency(0.93 / 0.90)高于宿主 compaction-basic 的 80% 线,只是超过它之后的标签升级,宿主先压缩时不会到达。
273
273
  - **`min` 是首见提醒与 T2/T3 块数触发的地板**:内核 0.0.63 在运行时读它两处——① `firstSightMassReady`(从未提醒过、还没有基线、用量 ≥ `min` 且待压内容达到增长下限时立刻提醒一次,理由串带 `[first-sight mass]`);② `tierCountUsageFloor`(T2/T3 的「块数达标」触发同样要求用量 ≥ `min`)。常规 T1 增长提醒不看 `min`(由 `max` 与 `growthRatio` 决定),所以五档之间的主要差异仍来自 `max` 与 `emergency`,但更低的 `min` 会让首见提醒来得更早。
@@ -301,7 +301,7 @@ src/
301
301
  ├── system-prompt.ts# M4: 一次性 ACP 指引段(让 nudge 保持简短)
302
302
  ├── config.ts # 内核配置组装(阈值 + coreOverrides)
303
303
  ├── window.ts # 自动上下文窗口探测(宿主投影优先,LLM 运行时探测回退,兜底 128000)+ 输出预留探测(defaultMaxTokens,windowFor 内扣除)
304
- └── commands.ts # M4: /acp 斜杠命令
304
+ └── commands.ts # M4: /acp-prune 斜杠命令
305
305
  ```
306
306
 
307
307
  ## License
package/cordis.patch.yml CHANGED
@@ -12,7 +12,7 @@
12
12
  # idempotent and keeps the plugin self-sufficient on every supported DSH.)
13
13
  # 2. `compaction-acp` — the ACP engine, mounted at the HOST plane, so its
14
14
  # model tools (`compress`, `decompress`, `search_context`, `acp_status`),
15
- # the `/acp` command, the advisory nudge, and the guidance section reach
15
+ # the `/acp-prune` command, the advisory nudge, and the guidance section reach
16
16
  # EVERY mode (standard / code / minimal / cordis / custom presets) of the
17
17
  # profile the plugin was installed into.
18
18
  #
@@ -1,10 +1,10 @@
1
1
  /**
2
- * M4 — the `/acp` slash command: a human-friendly window into the same
2
+ * M4 — the `/acp-prune` slash command: a human-friendly window into the same
3
3
  * machinery the model tools expose (status, one-shot compress, decompress,
4
4
  * runtime settings read/write).
5
5
  * @module billion-context-dsh/commands
6
6
  */
7
7
  import type { CommandDefinition } from '@deepseek-ai/dsh-commands';
8
8
  import { type ToolEnvironment } from './tools.ts';
9
- /** Register the /acp command (idempotent per engine). */
9
+ /** Register the /acp-prune command (idempotent per engine). */
10
10
  export declare function acpCommand(env: ToolEnvironment): CommandDefinition;
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@
21
21
  * ```
22
22
  *
23
23
  * The package registers `ctx.compaction` plus the four model tools and the
24
- * `/acp` command when the hosting composition provides `ctx.tools` /
24
+ * `/acp-prune` command when the hosting composition provides `ctx.tools` /
25
25
  * `ctx.commands`.
26
26
  * @module billion-context-dsh
27
27
  */
@@ -111,7 +111,7 @@ export interface AcpConfig {
111
111
  readonly countTokens?: (text: string) => number;
112
112
  /** Register the four model tools on `ctx.tools`. Default true. */
113
113
  readonly autoTools: boolean;
114
- /** Register the `/acp` command on `ctx.commands`. Default true. */
114
+ /** Register the `/acp-prune` command on `ctx.commands`. Default true. */
115
115
  readonly autoCommand: boolean;
116
116
  /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */
117
117
  readonly autoNudge: boolean;
@@ -157,9 +157,9 @@ export declare class AcpCompactionEngine extends CompactionEngine {
157
157
  private readonly windowCache;
158
158
  /** Live settings snapshot thunk (composition → user settings layer); swapped when the settings provider attaches (SettingsProvider.installSection). */
159
159
  private readSettingsSource;
160
- /** The settings service, captured lazily for /acp config (undefined in provider-less processes). */
160
+ /** The settings service, captured lazily for /acp-prune config (undefined in provider-less processes). */
161
161
  private settingsService;
162
- /** /acp config read/write surface. */
162
+ /** /acp-prune config read/write surface. */
163
163
  readonly settingsCommand: SettingsCommandSurface;
164
164
  /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
165
165
  private readonly outputReservationCache;
package/dist/index.js CHANGED
@@ -5022,7 +5022,7 @@ function windowSourceLabel(window) {
5022
5022
  if (window.source === "auto") {
5023
5023
  return `auto-detected from ${window.provider ?? "?"}/${window.model ?? "?"}`;
5024
5024
  }
5025
- if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp config)";
5025
+ if (window.probeFailed === true) return "default (auto-detection failed \u2014 see /acp-prune config)";
5026
5026
  return "default (auto-detection unavailable)";
5027
5027
  }
5028
5028
  function projectedContextWindow(agent) {
@@ -5879,7 +5879,7 @@ async function statusText(env, agent) {
5879
5879
  );
5880
5880
  }
5881
5881
  if (window.probeFailed === true) {
5882
- lines.push(` \u26A0 window auto-detection failed \u2014 using the ${limit} fallback (change modelContextLimit or autoModelContextLimit via /acp config \u2014 or restart \u2014 to re-probe)`);
5882
+ lines.push(` \u26A0 window auto-detection failed \u2014 using the ${limit} fallback (change modelContextLimit or autoModelContextLimit via /acp-prune config \u2014 or restart \u2014 to re-probe)`);
5883
5883
  }
5884
5884
  const state = structuredClone(env.store.stateFor(session));
5885
5885
  const config = kernelConfigFor({ ...env, modelContextLimit: limit });
@@ -5902,18 +5902,18 @@ async function statusText(env, agent) {
5902
5902
  }
5903
5903
  function compressText(env, agent, args) {
5904
5904
  if (args.length < 3) {
5905
- return "/acp compress <startSeq> <endSeq> <summary...>";
5905
+ return "/acp-prune compress <startSeq> <endSeq> <summary...>";
5906
5906
  }
5907
5907
  const startSeq = Number(args[0]);
5908
5908
  const endSeq = Number(args[1]);
5909
5909
  const summary = args.slice(2).join(" ");
5910
5910
  if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {
5911
- return "/acp compress: startSeq and endSeq must be integers";
5911
+ return "/acp-prune compress: startSeq and endSeq must be integers";
5912
5912
  }
5913
5913
  const session = agent.session;
5914
5914
  const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
5915
5915
  if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {
5916
- return "/acp compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp compress";
5916
+ return "/acp-prune compress: the range touches a compressed block summary node \u2014 distill it with the compress tool (seq-based batch), not /acp-prune compress";
5917
5917
  }
5918
5918
  const shadowed = shadowedSeqsOf(session, start, end);
5919
5919
  const instructionHits = guardedRowsInSpan(guardedSurfaceSeqsOf(session), shadowed);
@@ -5933,7 +5933,7 @@ function compressText(env, agent, args) {
5933
5933
  });
5934
5934
  return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`;
5935
5935
  }
5936
- var DECOMPRESS_USAGE = "/acp decompress <blockId> [offset] [limit]";
5936
+ var DECOMPRESS_USAGE = "/acp-prune decompress <blockId> [offset] [limit]";
5937
5937
  function decompressText(_env, agent, args) {
5938
5938
  if (args.length < 1) return DECOMPRESS_USAGE;
5939
5939
  const offset = args[1] === void 0 ? 0 : Number(args[1]);
@@ -5944,7 +5944,7 @@ function decompressText(_env, agent, args) {
5944
5944
  const blockId = blockIdOfKernelRef(session, args[0]);
5945
5945
  const ledger = rebuildBlockLedger(sessionEventsOf(session));
5946
5946
  const block = blockId === null ? ledger.find((entry) => entry.blockId.startsWith(args[0])) : ledger.find((entry) => entry.blockId === blockId);
5947
- if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
5947
+ if (block === void 0) return `block "${args[0]}" not found (see /acp-prune status)`;
5948
5948
  const expanded = expandShadowedSeqs(session, block.blockId);
5949
5949
  const page = sliceDecompressPage(
5950
5950
  expanded,
@@ -5964,14 +5964,14 @@ function decompressText(_env, agent, args) {
5964
5964
  `Block ${block.blockId} \u2014 ${block.summary}`,
5965
5965
  `[messages ${page.offset + 1}..${page.offset + page.seqs.length} of ${page.total}]`
5966
5966
  ];
5967
- if (!page.exhausted) lines.push(`Continue with: /acp decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
5967
+ if (!page.exhausted) lines.push(`Continue with: /acp-prune decompress ${block.blockId.slice(0, 8)} ${page.offset + page.seqs.length}`);
5968
5968
  lines.push("", parts.join("\n\n") || "(no recoverable content)");
5969
5969
  return lines.join("\n");
5970
5970
  }
5971
5971
  function acpCommand(env) {
5972
5972
  return {
5973
- name: "acp",
5974
- description: "Active Context Pruning \u2014 model-driven context compression. Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId> [offset] [limit] | /acp config [list|set <key> <value>|reset <key>|all]",
5973
+ name: "acp-prune",
5974
+ description: "Active Context Pruning \u2014 model-driven context compression. Usage: /acp-prune status | /acp-prune compress <startSeq> <endSeq> <summary> | /acp-prune decompress <blockId> [offset] [limit] | /acp-prune config [list|set <key> <value>|reset <key>|all]",
5975
5975
  handler: async (invocation) => {
5976
5976
  const raw = invocation.rawInput.trim();
5977
5977
  if (raw === "" || raw === "status") {
@@ -5986,7 +5986,7 @@ function acpCommand(env) {
5986
5986
  if (raw.startsWith("decompress")) {
5987
5987
  return { kind: "success", text: decompressText(env, invocation.agent, raw.slice("decompress".length).trim().split(/\s+/)) };
5988
5988
  }
5989
- return { kind: "error", text: `unknown /acp subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
5989
+ return { kind: "error", text: `unknown /acp-prune subcommand "${raw.split(/\s+/)[0]}" \u2014 use status | compress | decompress | config` };
5990
5990
  }
5991
5991
  };
5992
5992
  }
@@ -5998,7 +5998,7 @@ function isSettingsKey(key) {
5998
5998
  }
5999
5999
  function settingsWriteFailure(error) {
6000
6000
  if (error instanceof SettingsConflictError) {
6001
- return "conflict: another writer changed this setting at the same time \u2014 run /acp config again";
6001
+ return "conflict: another writer changed this setting at the same time \u2014 run /acp-prune config again";
6002
6002
  }
6003
6003
  return `rejected: ${String(error)}`;
6004
6004
  }
@@ -6026,7 +6026,7 @@ function configListText(surface) {
6026
6026
  }
6027
6027
  lines.push("", " changes apply to running sessions immediately (no restart)");
6028
6028
  lines.push(" coreOverrides (composition layer) merge LAST and beat these values on same-name keys");
6029
- lines.push(" /acp config reset <key> returns the key to the composition row / engine default");
6029
+ lines.push(" /acp-prune config reset <key> returns the key to the composition row / engine default");
6030
6030
  return lines.join("\n");
6031
6031
  }
6032
6032
  async function configSetText(surface, key, rawValue) {
@@ -6088,13 +6088,13 @@ async function configText(env, rest) {
6088
6088
  const verb = args[0] ?? "list";
6089
6089
  if (verb === "list") return configListText(surface);
6090
6090
  if (verb === "set") {
6091
- if (args.length < 3) return "usage: /acp config set <key> <value> (e.g. /acp config set nudgeMaxContextLimitPct 0.72)";
6091
+ if (args.length < 3) return "usage: /acp-prune config set <key> <value> (e.g. /acp-prune config set nudgeMaxContextLimitPct 0.72)";
6092
6092
  return configSetText(surface, args[1], args.slice(2).join(" "));
6093
6093
  }
6094
6094
  if (verb === "reset") {
6095
6095
  return configResetText(surface, args[1] ?? "all");
6096
6096
  }
6097
- return `unknown /acp config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
6097
+ return `unknown /acp-prune config verb "${verb}" \u2014 use list | set <key> <value> | reset <key>|all`;
6098
6098
  }
6099
6099
 
6100
6100
  // src/system-prompt.ts
@@ -6171,9 +6171,9 @@ var AcpCompactionEngine = class extends CompactionEngine {
6171
6171
  windowCache = /* @__PURE__ */ new Map();
6172
6172
  /** Live settings snapshot thunk (composition → user settings layer); swapped when the settings provider attaches (SettingsProvider.installSection). */
6173
6173
  readSettingsSource = () => resolveAcpSettings({});
6174
- /** The settings service, captured lazily for /acp config (undefined in provider-less processes). */
6174
+ /** The settings service, captured lazily for /acp-prune config (undefined in provider-less processes). */
6175
6175
  settingsService;
6176
- /** /acp config read/write surface. */
6176
+ /** /acp-prune config read/write surface. */
6177
6177
  settingsCommand;
6178
6178
  /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */
6179
6179
  outputReservationCache = /* @__PURE__ */ new Map();
@@ -6223,7 +6223,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6223
6223
  kernel: this.kernel,
6224
6224
  store: this.store,
6225
6225
  // The settings-exposed knobs read LIVE from the settings source, so a
6226
- // settings.yaml edit (or /acp config set) hot-applies to every
6226
+ // settings.yaml edit (or /acp-prune config set) hot-applies to every
6227
6227
  // subsequent call — consumers never see stale numbers. (ToolEnvironment
6228
6228
  // fields are readonly properties; getters satisfy them.)
6229
6229
  get modelContextLimit() {
@@ -6240,7 +6240,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6240
6240
  },
6241
6241
  coreOverrides: this.config.coreOverrides,
6242
6242
  // Display-only: which named preset produced the thresholds above (if any),
6243
- // so /acp status can name it. The resolved pct values above are what the
6243
+ // so /acp-prune status can name it. The resolved pct values above are what the
6244
6244
  // kernel actually reads — this field never feeds kernelConfigFor.
6245
6245
  preset: this.config.preset,
6246
6246
  windowFor: (agent) => this.windowFor(agent),
@@ -6381,7 +6381,7 @@ var AcpCompactionEngine = class extends CompactionEngine {
6381
6381
  cap = probe.outputReservation;
6382
6382
  if (probe.contextWindow === null) {
6383
6383
  this.ctx.logger.warn(
6384
- `billion-context-dsh: context-window auto-detection failed for ${provider}/${model} \u2014 using the ${DEFAULT_CONTEXT_WINDOW} fallback (change modelContextLimit or autoModelContextLimit via /acp config \u2014 or restart \u2014 to re-probe)`
6384
+ `billion-context-dsh: context-window auto-detection failed for ${provider}/${model} \u2014 using the ${DEFAULT_CONTEXT_WINDOW} fallback (change modelContextLimit or autoModelContextLimit via /acp-prune config \u2014 or restart \u2014 to re-probe)`
6385
6385
  );
6386
6386
  window = { limit: DEFAULT_CONTEXT_WINDOW, source: "default", provider, model, probeFailed: true };
6387
6387
  cap = null;