opencode-acp 1.9.2 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -65
- package/README.zh-CN.md +117 -60
- package/dist/index.js +524 -368
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/message-utils.d.ts.map +1 -1
- package/dist/lib/compress/protected-content.d.ts +3 -1
- package/dist/lib/compress/protected-content.d.ts.map +1 -1
- package/dist/lib/compress/range-utils.d.ts +2 -1
- package/dist/lib/compress/range-utils.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/index.d.ts +1 -1
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/prune.d.ts.map +1 -1
- package/dist/lib/messages/utils.d.ts +2 -1
- package/dist/lib/messages/utils.d.ts.map +1 -1
- package/dist/lib/prompts/compress-message.d.ts +1 -1
- package/dist/lib/prompts/compress-message.d.ts.map +1 -1
- package/dist/lib/prompts/compress-range.d.ts +1 -1
- package/dist/lib/prompts/compress-range.d.ts.map +1 -1
- package/dist/lib/prompts/compression-rules.d.ts +14 -0
- package/dist/lib/prompts/compression-rules.d.ts.map +1 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +1 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,17 +44,17 @@ an average prompt-cache hit ratio above 85%.** (That average — not per-session
|
|
|
44
44
|
is explained in [Impact on Prompt Caching](#impact-on-prompt-caching), where it
|
|
45
45
|
turns out to save far more tokens than traditional compression.)
|
|
46
46
|
|
|
47
|
-
|
|
|
48
|
-
|
|
49
|
-
| **Messages**
|
|
50
|
-
| **Total tokens processed** | 582 M
|
|
51
|
-
| **Prompt-cache hit ratio** | 86.2%
|
|
52
|
-
| **Context p50 (median)**
|
|
53
|
-
| **Context p75**
|
|
54
|
-
| **Context p90**
|
|
55
|
-
| **Context p95**
|
|
56
|
-
| **Context p99**
|
|
57
|
-
| **Peak**
|
|
47
|
+
| | Session 1 | Session 2 |
|
|
48
|
+
| -------------------------- | ----------- | ----------- |
|
|
49
|
+
| **Messages** | 3,024 | 2,028 |
|
|
50
|
+
| **Total tokens processed** | 582 M | 463 M |
|
|
51
|
+
| **Prompt-cache hit ratio** | 86.2% | 89.0% |
|
|
52
|
+
| **Context p50 (median)** | 1.2 K (<1%) | 1.8 K (<1%) |
|
|
53
|
+
| **Context p75** | 2.8 K | 3.5 K |
|
|
54
|
+
| **Context p90** | 108 K (11%) | 58 K (6%) |
|
|
55
|
+
| **Context p95** | 251 K (25%) | 335 K (34%) |
|
|
56
|
+
| **Context p99** | 425 K (43%) | 442 K (44%) |
|
|
57
|
+
| **Peak** | 488 K (49%) | 769 K (77%) |
|
|
58
58
|
|
|
59
59
|
(Context percentages are of the 1M window.)
|
|
60
60
|
|
|
@@ -70,9 +70,9 @@ Or add to your opencode config:
|
|
|
70
70
|
|
|
71
71
|
```json
|
|
72
72
|
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
"plugin": {
|
|
74
|
+
"opencode-acp": "latest"
|
|
75
|
+
}
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -153,16 +153,16 @@ ensures key context information is not lost.
|
|
|
153
153
|
|
|
154
154
|
ACP provides an `/acp` slash command (also accepts `/dcp` for backward compatibility):
|
|
155
155
|
|
|
156
|
-
| Command
|
|
157
|
-
|
|
158
|
-
| `/acp`
|
|
159
|
-
| `/acp context`
|
|
160
|
-
| `/acp stats`
|
|
161
|
-
| `/acp sweep [n]`
|
|
162
|
-
| `/acp manual [on\|off]` | Toggle manual mode. When on, the AI will not autonomously use context management tools
|
|
163
|
-
| `/acp compress [focus]` | Trigger a single compress tool execution. Optional focus text directs what content to compress, following the active `compress.mode`
|
|
164
|
-
| `/acp decompress <n>`
|
|
165
|
-
| `/acp recompress <n>`
|
|
156
|
+
| Command | Description |
|
|
157
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
158
|
+
| `/acp` | Shows available ACP commands |
|
|
159
|
+
| `/acp context` | Token usage breakdown by category (system, user, assistant, tools, etc.) and how much has been saved through pruning |
|
|
160
|
+
| `/acp stats` | Cumulative pruning statistics across all sessions |
|
|
161
|
+
| `/acp sweep [n]` | Prunes all tools since the last user message. Optional count: `/acp sweep 10` prunes the last 10 tools. Respects `commands.protectedTools` |
|
|
162
|
+
| `/acp manual [on\|off]` | Toggle manual mode. When on, the AI will not autonomously use context management tools |
|
|
163
|
+
| `/acp compress [focus]` | Trigger a single compress tool execution. Optional focus text directs what content to compress, following the active `compress.mode` |
|
|
164
|
+
| `/acp decompress <n>` | Restore a specific active compression by ID. Running without an argument shows available compression IDs, token sizes, and topics |
|
|
165
|
+
| `/acp recompress <n>` | Re-apply a user-decompressed compression by ID. Running without an argument shows recompressible IDs, token sizes, and topics |
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
@@ -183,9 +183,9 @@ Each level overrides the previous, so project settings take priority over global
|
|
|
183
183
|
>
|
|
184
184
|
> ```jsonc
|
|
185
185
|
> {
|
|
186
|
-
>
|
|
187
|
-
>
|
|
188
|
-
>
|
|
186
|
+
> "compaction": {
|
|
187
|
+
> "auto": false,
|
|
188
|
+
> },
|
|
189
189
|
> }
|
|
190
190
|
> ```
|
|
191
191
|
>
|
|
@@ -350,7 +350,7 @@ By default, these tools are always protected from pruning:
|
|
|
350
350
|
|
|
351
351
|
The `protectedTools` arrays in `commands` and `strategies` add to this default list.
|
|
352
352
|
|
|
353
|
-
For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are
|
|
353
|
+
For the `compress` tool, `compress.protectedTools` ensures specific tool outputs are **hard-excluded** from compression ranges (v1.10.0+). When the model compresses a range that includes a protected tool message, that message survives intact in visible context — only the surrounding non-protected messages are compressed. By default `compress.protectedTools` includes `task`, `skill`, `todowrite`, `todoread`, and `decompress`.
|
|
354
354
|
|
|
355
355
|
---
|
|
356
356
|
|
|
@@ -381,37 +381,38 @@ ACP auto-migrates config from `dcp.jsonc` to `acp.jsonc` and prompts from `dcp-p
|
|
|
381
381
|
---
|
|
382
382
|
|
|
383
383
|
<details>
|
|
384
|
-
<summary><strong>Bug Fixes (
|
|
385
|
-
|
|
386
|
-
| #
|
|
387
|
-
|
|
388
|
-
| 1
|
|
389
|
-
| 2
|
|
390
|
-
| 3
|
|
391
|
-
| 4
|
|
392
|
-
| 5
|
|
393
|
-
| 6
|
|
394
|
-
| 7
|
|
395
|
-
| 8
|
|
396
|
-
| 9
|
|
397
|
-
| 10
|
|
398
|
-
| 11
|
|
399
|
-
| 12
|
|
400
|
-
| 13
|
|
401
|
-
| 14
|
|
402
|
-
| 15
|
|
403
|
-
| 16
|
|
404
|
-
| 17
|
|
405
|
-
| 18
|
|
406
|
-
| 19
|
|
407
|
-
| 20
|
|
408
|
-
| 21
|
|
409
|
-
| 22
|
|
410
|
-
| 23--34 | MEDIUM
|
|
411
|
-
| 35
|
|
412
|
-
| 36
|
|
413
|
-
| 37
|
|
414
|
-
| 38
|
|
384
|
+
<summary><strong>Bug Fixes (39 total)</strong> -- applied on top of DCP v3.1.11</summary>
|
|
385
|
+
|
|
386
|
+
| # | Severity | Summary |
|
|
387
|
+
| ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
388
|
+
| 1 | CRITICAL | State not persisted across restarts -- messageIds, block deactivation, save errors silently lost |
|
|
389
|
+
| 2 | CRITICAL | resetOnCompaction() clears all compression blocks -- undoes all pruning work |
|
|
390
|
+
| 3 | CRITICAL | prune silently drops summary -- DATA LOSS when no user message precedes anchor |
|
|
391
|
+
| 4 | CRITICAL | getCurrentTokenUsage returns 0 -- prevents nudge from ever triggering |
|
|
392
|
+
| 5 | HIGH | loadPruneMessagesState duplicates activeBlockIds + reasoning-strip undefined guard |
|
|
393
|
+
| 6 | HIGH | Synthetic summary messages get mNNNN refs but are invisible to boundary lookup |
|
|
394
|
+
| 7 | HIGH | State not persisted across restarts -- messageIds, block deactivation, and save errors silently lost |
|
|
395
|
+
| 8 | HIGH | isMessageCompacted() inconsistent with compaction summary message handling |
|
|
396
|
+
| 9 | HIGH | Compressed block summaries retain stale mNNNN message ID tags -- model copies stale IDs |
|
|
397
|
+
| 10 | HIGH | Model uses stale mNNNN IDs from nudges/summaries -- compress fails with "startId not available" |
|
|
398
|
+
| 11 | HIGH | Major GC skips legacy blocks without generation field -- oversized blocks never collected |
|
|
399
|
+
| 12 | HIGH | Percentage-based thresholds calculated against effective input context instead of full model context window |
|
|
400
|
+
| 13 | HIGH | Context window leaks -- compressed messages reappear after /compact |
|
|
401
|
+
| 14 | HIGH | Compression notifications write full block summaries to DB -- can reach 150KB+ per notification |
|
|
402
|
+
| 15 | HIGH | npm auto-install overwrites fork with upstream package |
|
|
403
|
+
| 16 | HIGH | Summary mNNNN refs in compress output -- model copies stale message IDs |
|
|
404
|
+
| 17 | HIGH | Synthetic messages not in messageIdToBlockId -- compress fails to find them |
|
|
405
|
+
| 18 | HIGH | Compress stops model from responding after compression completes |
|
|
406
|
+
| 19 | HIGH | Dynamic block guidance breaks API prefix cache |
|
|
407
|
+
| 20 | HIGH | GC never deactivates old blocks -- dead-weight accumulates indefinitely |
|
|
408
|
+
| 21 | HIGH | Logger + tokenizer 20-50s per-turn latency (268x slowdown) |
|
|
409
|
+
| 22 | HIGH | compress throws hard error on reversed block boundaries -- model gives up |
|
|
410
|
+
| 23--34 | MEDIUM | Various fixes for dedup, purge errors, schema validation, hook timing, etc. |
|
|
411
|
+
| 35 | HIGH | Aging warnings shown at low context usage (<50%) -- triggers unnecessary compress, wastes tokens |
|
|
412
|
+
| 36 | HIGH | Compression summary emitted as a standalone user message before the user's real turn -- model reads its own prior assistant output as user input, causing dialog role confusion / self-Q&A loops |
|
|
413
|
+
| 37 | HIGH | Message-transform pipeline runs on OpenCode's hidden title/summary/compaction agent requests -- corrupts the request and shared session state, breaking session title generation |
|
|
414
|
+
| 38 | CRITICAL | pruneToolOutputs/pruneToolInputs/pruneToolErrors mutate existing messages in-place -- invalidates LLM prefix cache, causing 89% of fresh input tokens to be wasted on cache-invalidating re-sends |
|
|
415
|
+
| 39 | HIGH | Protected tool outputs (skill/task/todowrite) only soft-protected during compression -- appended to summary then pruned from context, losing semantic authority and susceptible to GC truncation. Fixed with hard-exclusion in v1.10.0 |
|
|
415
416
|
|
|
416
417
|
For the complete list with root cause analysis, see the [bug tracker](https://github.com/ranxianglei/opencode-acp/issues).
|
|
417
418
|
|
|
@@ -421,6 +422,83 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
|
|
|
421
422
|
|
|
422
423
|
## Changelog
|
|
423
424
|
|
|
425
|
+
### v1.10.1 — Over-Compression Fix & More Compression Candidates
|
|
426
|
+
|
|
427
|
+
Fixes the over-compression bug reported in issue #18 and GitHub #85, where the `toolOutputReminder` nudge bypassed the adaptive 5%-of-context growth protection and fired ~10x too often on large-context models.
|
|
428
|
+
|
|
429
|
+
#### Over-Compression: toolOutputReminder Bypassed 5% Protection (issue #18, GitHub #85, PR #83)
|
|
430
|
+
|
|
431
|
+
**Problem**: ACP has two independent nudge mechanisms. The main growth nudge correctly uses an adaptive threshold (`nudgeGrowthTokens` = 5% of model context, clamped 6K–50K). But a separate `toolOutputReminder` — added in v1.9.0 to surface accumulated tool outputs — used a **hardcoded 5000-token** tool-growth threshold that fired independently of `minContextLimit` / `maxContextLimit`. On a 1M-context model, 5000 tokens is 0.5% of context, so the reminder fired ~10x more often than intended, emitting a strong "compress these ranges **now**" directive every time. This drove severe over-compression: one investigated session hit only 22.6% peak context yet ran 68 compressions across 499 LLM calls.
|
|
432
|
+
|
|
433
|
+
Additionally, the `compress.toolOutputNudgeThreshold` config key was **dead** — declared in the type but missing from the config merge, validation list, and JSON schema, so user overrides were silently dropped.
|
|
434
|
+
|
|
435
|
+
**Fix** (4 coordinated changes):
|
|
436
|
+
- `lib/messages/inject/inject.ts`: `toolOutputThreshold` now defaults to `nudgeGrowthTokens` (adaptive) instead of the hardcoded `5000`.
|
|
437
|
+
- `lib/config.ts` `mergeCompress`: `toolOutputNudgeThreshold` override now flows through the config merge.
|
|
438
|
+
- `lib/config-validation.ts`: `compress.toolOutputNudgeThreshold` registered as a valid config key.
|
|
439
|
+
- `dcp.schema.json`: `toolOutputNudgeThreshold` property added to the schema.
|
|
440
|
+
|
|
441
|
+
Tests: 3 behavior tests (no-fire on small growth, fire on large growth, override respected) + 1 config-validation test in `tests/inject.test.ts` / `tests/config-validation.test.ts`.
|
|
442
|
+
|
|
443
|
+
#### Persist modelContextLimit Across Restart (issue #18, PR #83)
|
|
444
|
+
|
|
445
|
+
**Problem**: `state.modelContextLimit` was runtime-only — set by the system-prompt hook (which runs after the message-transform hook), so on the first turn after restart it was undefined, causing the adaptive thresholds to fall back to the 6000-token floor instead of the correct value (e.g. 50K for a 1M model). This partially reintroduced the over-compression on the first turn after every restart.
|
|
446
|
+
|
|
447
|
+
**Fix**: `modelContextLimit` is now persisted to the state JSON and restored on load. A guard handles old state files without the field (backward-compatible). The system-prompt hook still refreshes it with the live model value every turn, so a stale persisted value self-corrects within one turn after a model switch.
|
|
448
|
+
|
|
449
|
+
Tests: 2 persistence tests (save+reload round-trip, backward-compat with old files) in `tests/inject.test.ts`.
|
|
450
|
+
|
|
451
|
+
#### Systemic Regression Guard (issue #18, PR #83)
|
|
452
|
+
|
|
453
|
+
A regression test that asserts the **invariant**: the same tool-token growth fires the reminder on a small-context model (200K → 10K threshold) but does NOT fire on a large-context model (400K → 20K threshold). Any future change that reverts a threshold to a fixed value fails this test immediately.
|
|
454
|
+
|
|
455
|
+
#### Increase Max Compression Candidates 5 → 15 (issue #13, PR #81)
|
|
456
|
+
|
|
457
|
+
The context breakdown and tool-output reminder only showed 5 compression candidates, causing the model to compress too narrowly (1 message per batch). Increased to 15 (`largestRanges`, `largestToolRanges`, `toolOutputReminder topRanges`) so the model sees more candidates at once and can cover larger ranges in a single compress call.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### v1.10.0 — Hard-Exclusion, Compression Prompt Rewrite, Suffix & Deploy Fixes
|
|
462
|
+
|
|
463
|
+
This release bundles 7 merged PRs. The headline change is **hard-exclusion of protected tool messages** from compression ranges; the rest are fixes and a prompt rewrite that shipped in the same release window.
|
|
464
|
+
|
|
465
|
+
#### Bug 39 — Hard-Exclusion of Protected Tools from Compression (issue #16, PR #75)
|
|
466
|
+
|
|
467
|
+
**Problem**: Protected tool messages (`skill`, `task`, `todowrite`, etc.) were only _soft-protected_ during compression. When the model called `compress` on a range that included a skill output, the original message was pruned from visible context and its content was appended to the summary block. This caused two problems:
|
|
468
|
+
|
|
469
|
+
1. **Semantic loss**: The skill content became historical recap metadata (`[ACP SYSTEM METADATA — recap...]`), not a live instruction. The model read it as a past artifact, not as active guidance.
|
|
470
|
+
2. **Data loss via GC**: When the block was promoted to old-gen and the summary exceeded `maxOldGenSummaryLength` (3000 chars), `runTruncateGC` truncated the entire summary — including the appended skill content. Skill outputs (often 2–10 KB) were silently destroyed.
|
|
471
|
+
|
|
472
|
+
**Fix**: Protected tool messages are now **hard-excluded** from compression ranges. When the model calls `compress(startId, endId)` on a range that contains protected tool outputs, those messages are filtered out of the selection _before_ `applyCompressionState` runs. The protected messages survive intact in visible context; only the surrounding non-protected messages are compressed.
|
|
473
|
+
|
|
474
|
+
The filter runs in both range mode (`lib/compress/range.ts`) and message mode (`lib/compress/message.ts`). It uses the existing `compress.protectedTools` config (default: `task`, `skill`, `todowrite`, `todoread`, `decompress`) and the same `isToolNameProtected` matcher used elsewhere.
|
|
475
|
+
|
|
476
|
+
**Verification**: Live-tested by loading the `git-master` skill, then compressing a range spanning the skill output. The skill message (m00170) survived compression; only 15 of 22 messages in the range were compressed (7 protected messages correctly excluded). Tests: 29 dedicated tests in `tests/compress-protected-exclusion.test.ts`.
|
|
477
|
+
|
|
478
|
+
**Compatibility**: No config changes, no persisted-state schema changes. The existing `appendProtectedTools` soft-protection logic is retained as a fallback for any edge case the filter misses.
|
|
479
|
+
|
|
480
|
+
#### Compression Format Prompt Rewrite (issue #13, PR #72)
|
|
481
|
+
|
|
482
|
+
The `compress` tool's summary-format guidance said "EXHAUSTIVE" in the header but then asked for "LEAN" summaries lower down — contradictory directions that left the model unsure how much detail to keep. Replaced with a clear **KEEP / DROP / PRIORITY** taxonomy that maps each rule to a concrete action, eliminating the ambiguity.
|
|
483
|
+
|
|
484
|
+
#### Drop Empty Synthetic User Message from Suffix (issue #12, PR #71)
|
|
485
|
+
|
|
486
|
+
`injectCompressNudges` sometimes forwarded an empty synthetic suffix user message (the carrier for context-status metadata) to the LLM after it had been merged into the preceding block summary. The model then saw an empty user turn with no content. Now spliced out before the forward, plus a backstop `dropEmptyUserMessages` guard.
|
|
487
|
+
|
|
488
|
+
#### Context Transition Notification Arrow Spacing (issue #68, PR #70)
|
|
489
|
+
|
|
490
|
+
`formatContextTransition` in `lib/ui/notification.ts` rendered `141.9K→111K` with no spaces around the `→`. Added explicit spacing for readability: `141.9K → 111K`.
|
|
491
|
+
|
|
492
|
+
#### Route Placeholder Diagnostic to Logger (issue #67, PR #69)
|
|
493
|
+
|
|
494
|
+
`validateSummaryPlaceholders` in `lib/compress/range-utils.ts` used `console.warn` to surface placeholder mismatches, which leaked to stderr and was rendered inline in the chat dialog. Routed the diagnostic through the plugin logger instead so it lands in the ACP debug log without polluting chat.
|
|
495
|
+
|
|
496
|
+
#### Dev-Deploy Legacy Path Sync (issue #9, PR #64)
|
|
497
|
+
|
|
498
|
+
The stale install at the legacy resolution path `~/.cache/opencode/node_modules/opencode-acp/` shadowed the `@latest` deploy at `~/.cache/opencode/packages/opencode-acp@latest/`. `scripts/dev-deploy.sh` now syncs both paths so a stale legacy copy can't override the freshly built bundle.
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
424
502
|
### v1.9.2 — Persist Nudge Baseline Across Restart (bug #60)
|
|
425
503
|
|
|
426
504
|
**Problem**: After a per-message nudge fired purely on token growth (no compress/decompress around it), the updated `lastPerMessageNudgeTokens` baseline was written to in-memory state but **not persisted to disk** — `saveSessionState()` only ran when `anchorsChanged` was true, and a growth nudge does not always change anchors (turn/iteration anchor sets saturate once seeded, or the last message is an assistant turn with no user turn to anchor). After an OpenCode restart, the stale baseline was reloaded, so `growth = currentTokens − staleBaseline` exceeded the threshold again → the nudge refired on **every single turn** for the rest of the session.
|
|
@@ -434,7 +512,7 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
|
|
|
434
512
|
}
|
|
435
513
|
```
|
|
436
514
|
|
|
437
|
-
After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/opencode/storage/plugin/acp/{sessionId}.json` on every nudge, so a restart computes growth against the real post-nudge baseline and the nudge only refires when
|
|
515
|
+
After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/opencode/storage/plugin/acp/{sessionId}.json` on every nudge, so a restart computes growth against the real post-nudge baseline and the nudge only refires when _actual_ new growth exceeds `nudgeGrowthTokens`. Regression test added in `tests/inject.test.ts` (seeds a stale baseline to disk, fires a growth nudge with `anchorsChanged=false`, reloads, asserts the persisted baseline advanced).
|
|
438
516
|
|
|
439
517
|
**Compatibility**: No schema changes. Existing persisted state loads unchanged. Users hitting #60 should upgrade and the every-turn nudge loop stops on the first nudge after restart.
|
|
440
518
|
|
|
@@ -442,11 +520,11 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
442
520
|
|
|
443
521
|
### v1.9.1 — Disjoint Visible-Range Segments & Nudge Wording (issue #9 root cause)
|
|
444
522
|
|
|
445
|
-
**Problem**: Even after v1.9.0, the model kept calling `compress` against IDs that a prior block had consumed. The root cause was that the suffix advertised a single contiguous span "first visible → last visible" that **straddled compression holes** — so the model's first guess for an `endId` landed inside an already-summarized range. Separately, the suffix's `(+X tokens since last nudge)` growth line was being misread as an
|
|
523
|
+
**Problem**: Even after v1.9.0, the model kept calling `compress` against IDs that a prior block had consumed. The root cause was that the suffix advertised a single contiguous span "first visible → last visible" that **straddled compression holes** — so the model's first guess for an `endId` landed inside an already-summarized range. Separately, the suffix's `(+X tokens since last nudge)` growth line was being misread as an _overflow_ warning, triggering panic compressions of large-but-still-needed ranges.
|
|
446
524
|
|
|
447
525
|
**Fix 1 — disjoint visible-id segments** (PR #57): `injectVisibleIdRange` no longer emits one "first-to-last" span. It builds the actual surviving segments in ascending ref order and truncates to the largest tool-bearing / high-token segments when the count overflows (`compress.maxVisibleSegments`, default `50`, now plumbed through config defaults + merge + validation + schema). The suffix now reads e.g. `[Visible (top 2 of 3 segments, 803 msgs): m00001–m00929, m00944–m00950 | +1 smaller segment (~1.2K tokens, 6 msgs) omitted]`, so the model sees exactly which ranges are compressible and never targets a hole. The formatting logic is extracted into pure, exported, unit-tested functions (`buildVisibleSegments`, `formatVisibleGuidance`).
|
|
448
526
|
|
|
449
|
-
**Fix 2 — nudge wording** (PR #58): The incremental-compression guidance line (`💡 Compress incrementally: target the ranges above...`) moved to
|
|
527
|
+
**Fix 2 — nudge wording** (PR #58): The incremental-compression guidance line (`💡 Compress incrementally: target the ranges above...`) moved to _after_ the largest-ranges list and is reworded to stress that **size alone is not a reason to compress** — a large range that is still needed in full must be kept. Soft efficiency nudges (`growth` / `minLimit` variants) are now prefixed with an explicit _"This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full."_ so the growth delta isn't mistaken for an overflow alarm. The `maxLimit` path keeps its stronger alert and is intentionally excluded from the efficiency framing.
|
|
450
528
|
|
|
451
529
|
**Compatibility**: No persisted-state schema changes. New optional config field `compress.maxVisibleSegments` (number, default `50`); old configs keep working.
|
|
452
530
|
|
|
@@ -454,9 +532,10 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
454
532
|
|
|
455
533
|
### v1.9.0 — Visible-Range Guidance & Compression Failure Recovery
|
|
456
534
|
|
|
457
|
-
**Problem**: On large-context models (1M+) the model repeatedly called `compress(startId=m00930, endId=m00943)` against IDs that a prior block had already consumed. It had no stable view of which `mNNNNN` refs were still compressible, the failure error gave no recovery info, `acp_status` was registered but never mentioned in the prompt, and the suffix nudge reported a bare percentage with no indication of
|
|
535
|
+
**Problem**: On large-context models (1M+) the model repeatedly called `compress(startId=m00930, endId=m00943)` against IDs that a prior block had already consumed. It had no stable view of which `mNNNNN` refs were still compressible, the failure error gave no recovery info, `acp_status` was registered but never mentioned in the prompt, and the suffix nudge reported a bare percentage with no indication of _where_ the tokens were actually spent.
|
|
458
536
|
|
|
459
537
|
**System prompt rewrite**:
|
|
538
|
+
|
|
460
539
|
- All four context tools (`compress`, `decompress`, `search_context`, `acp_status`) now listed with a one-line "when to use" hint each.
|
|
461
540
|
- Explicit compress / do-not-compress scenes replace the imperative "compress obvious waste promptly" wording.
|
|
462
541
|
- New **CONTEXT BREAKDOWN** section explains the 4-category suffix format (`tool | summaries | code | text`), the largest-range candidates, and the incremental "one large consumed range per call" strategy.
|
|
@@ -464,6 +543,7 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
464
543
|
- New **task-phase-end** trigger: when a bug hunt / exploration / research sprint ends, compress the phase's redundant churn while preserving findings, file paths, and decision rationale.
|
|
465
544
|
|
|
466
545
|
**Nudge cadence**:
|
|
546
|
+
|
|
467
547
|
- Dropped the `contextPct >= 15%` floor entirely. Cadence is now pure 5%-of-limit growth with a first-turn baseline (no more forced nudge on turn 1).
|
|
468
548
|
- Baseline auto-resets after a significant post-compression token drop, so the next nudge fires on the post-compression level instead of waiting a full growth cycle.
|
|
469
549
|
- Suffix nudge gains a **3-category composition breakdown** (`tool | summaries | code | text`, no double-counting of code-bearing messages) plus the **largest ranges** in the tool and code categories — concrete compression targets, not a bare percentage.
|
|
@@ -473,6 +553,7 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
473
553
|
**Compress failure recovery**: `resolveBoundaryIds` failures now return the current visible range (first/last ref), the active block count, and a pointer to `acp_status`. Out-of-range `endId` guesses (unregistered refs that parse higher than the last visible message) are **clamped** to the last visible message instead of failing; refs that are registered but already consumed still fail with the recovery hint (clamping them would silently recompress summarized content).
|
|
474
554
|
|
|
475
555
|
**Hardening**:
|
|
556
|
+
|
|
476
557
|
- `maxSummaryLengthHard` default raised `4000 → 8000 → 10000`; the compress-tool schema now sources its display value from config so changes propagate.
|
|
477
558
|
- Removed the stale `MODEL_CONTEXT_LIMITS` 38-entry fallback table — `modelContextLimit` is now sourced solely from the host SDK's `input.model.limit.context`. Providers that omit the field surface `undefined` immediately rather than getting a distorted percentage from a stale guess.
|
|
478
559
|
- `.catch()` added to every fire-and-forget `saveSessionState` call; removed an `anchorsChanged`-on-baseline path that triggered a concurrent-save race.
|
|
@@ -490,6 +571,7 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
490
571
|
**Fix**: Removed `shouldInjectThisTurn` gate from system prompt hook (`hooks.ts:108-112`). System prompt now always injects every turn. Suffix remains gated at `nudgeGrowthTokens` frequency.
|
|
491
572
|
|
|
492
573
|
**Current behavior**:
|
|
574
|
+
|
|
493
575
|
- **System prompt** (compression philosophy, tool awareness): ✅ every turn
|
|
494
576
|
- **Suffix** (context level, block list, Tips): gated at nudgeGrowthTokens frequency
|
|
495
577
|
|
|
@@ -500,31 +582,37 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
500
582
|
**Problem**: Large-context models (1M+) over-compressed at 20-30% context because Tips fired every 6K tokens (0.6% of 1M). System prompt injected every turn added constant pressure.
|
|
501
583
|
|
|
502
584
|
**Adaptive nudgeGrowthTokens**:
|
|
585
|
+
|
|
503
586
|
- Default is now adaptive: 5% of `modelContextLimit`, clamped to [6000, 50000]
|
|
504
|
-
|
|
587
|
+
- 128K → 6.4K, 200K → 10K, 500K → 25K, 1M → 50K, 2M+ → 50K (cap)
|
|
505
588
|
- Users can still set explicit `nudgeGrowthTokens` to override
|
|
506
589
|
- Removed hardcoded `6000` from schema defaults (was shadowing adaptive logic)
|
|
507
590
|
|
|
508
591
|
**System prompt gating**:
|
|
592
|
+
|
|
509
593
|
- SYSTEM prompt + `<dcp-system-reminder>` tags now pulse at `nudgeGrowthTokens` frequency
|
|
510
594
|
- Between nudges: system prompt injects **nothing** — zero compression noise
|
|
511
595
|
- First turn (`undefined` sentinel): always injects (establishes baseline)
|
|
512
596
|
|
|
513
597
|
**New tool: `acp_status`**:
|
|
598
|
+
|
|
514
599
|
- On-demand inspection of all compressed blocks (ID, tokens, age, topic)
|
|
515
600
|
- Replaces verbose block list in suffix with one-liner: `Compressed blocks: N (XK summary, last Ym ago). Use acp_status for details.`
|
|
516
601
|
|
|
517
602
|
**Compress notification improvement**:
|
|
603
|
+
|
|
518
604
|
- Header shows context before→after: `▣ ACP | Context 251.2K→249.3K`
|
|
519
605
|
- No percentage or limit shown (prevents model from anchoring on ceiling)
|
|
520
606
|
|
|
521
607
|
**Bug fixes**:
|
|
608
|
+
|
|
522
609
|
- `lastPerMessageNudgeTokens` reset to `0` after compress bypassed growth gate (feedback loop)
|
|
523
610
|
- Schema default `6000` shadowed `resolveAdaptiveNudgeGrowth()` — adaptive never activated
|
|
524
611
|
- `applyAnchoredNudges` + `injectContextUsage` duplicated context usage text
|
|
525
612
|
- `lastNudgeTokens === 0` sentinel replaced with `undefined` (explicit "never nudged")
|
|
526
613
|
|
|
527
614
|
**Tooling**:
|
|
615
|
+
|
|
528
616
|
- `scripts/dev-deploy.sh` — one-command build + deploy (auto-detects node, typecheck, build, deploy)
|
|
529
617
|
- Post-compress state transition integration tests (3 new)
|
|
530
618
|
- `acp_status` dedicated tests (7 new)
|
|
@@ -533,24 +621,28 @@ After this, `lastPerMessageNudgeTokens` is correctly flushed to `~/.local/share/
|
|
|
533
621
|
|
|
534
622
|
### v1.8.0 — Principle-Driven Prompts
|
|
535
623
|
|
|
536
|
-
**Philosophy**: Replaced verbose context-management guidance with 4 concise principles injected every turn. The model now sees
|
|
624
|
+
**Philosophy**: Replaced verbose context-management guidance with 4 concise principles injected every turn. The model now sees _what matters_ (principles) instead of _what to do_ (rigid rules).
|
|
537
625
|
|
|
538
626
|
**Prompt changes**:
|
|
627
|
+
|
|
539
628
|
- 4 principles replace CONTEXT PRESSURE LEVELS, 7-item priority list, DO NOT RE-COMPRESS rules
|
|
540
629
|
- Context display simplified: absolute token count only, no percentage
|
|
541
630
|
- `<acp-context>` tag wrapping (backward compatible with `<dcp-context>`)
|
|
542
631
|
|
|
543
632
|
**Hybrid Tips frequency**:
|
|
633
|
+
|
|
544
634
|
- 💡 Light Tips (15-45%): Every turn — non-disruptive reminder
|
|
545
635
|
- ⚠️ Warning Tips (45%+): Key nodes only — first crossing or 10pp growth, prevents over-compression
|
|
546
636
|
|
|
547
637
|
**Config simplification**:
|
|
638
|
+
|
|
548
639
|
- Removed `hardNudgeContextPercent` — merged into `minContextLimit`/`maxContextLimit`
|
|
549
640
|
- Removed `perMessageNudgeGrowthPercent` — light Tips show every turn
|
|
550
641
|
- `maxSummaryLength` default: 200 → 2000
|
|
551
642
|
- `maxSummaryLengthHard` default: 3000 → 4000
|
|
552
643
|
|
|
553
644
|
**Bug fixes**:
|
|
645
|
+
|
|
554
646
|
- Windows path validation: `os.tmpdir()` + `path.relative()` (was hardcoded `/tmp/`)
|
|
555
647
|
- Compress after-detection: reset warning tracking
|
|
556
648
|
- Dead code cleanup: `shouldInjectPerMessageNudge`, no-op template
|