Skip to content

Commit cea5d40

Browse files
authored
Merge pull request #567 from Opencode-DCP/dev
merge dev into main
2 parents 0657cd2 + 434bba2 commit cea5d40

25 files changed

Lines changed: 2381 additions & 187 deletions

.github/workflows/publish.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Publish
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- master
8+
9+
permissions:
10+
contents: read
11+
id-token: write
12+
13+
concurrency:
14+
group: publish-${{ github.ref }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
npm:
19+
name: Publish to npm
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v4
25+
26+
- name: Setup Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: "22.x"
30+
cache: npm
31+
registry-url: https://registry.npmjs.org
32+
33+
- name: Check npm version
34+
id: package
35+
run: |
36+
NAME=$(node -p "require('./package.json').name")
37+
VERSION=$(node -p "require('./package.json').version")
38+
echo "name=${NAME}" >> "$GITHUB_OUTPUT"
39+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
40+
41+
if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
42+
echo "published=true" >> "$GITHUB_OUTPUT"
43+
echo "${NAME}@${VERSION} is already published."
44+
else
45+
echo "published=false" >> "$GITHUB_OUTPUT"
46+
echo "${NAME}@${VERSION} is not published yet."
47+
fi
48+
49+
- name: Install dependencies
50+
if: steps.package.outputs.published == 'false'
51+
run: npm ci
52+
53+
- name: Format check
54+
if: steps.package.outputs.published == 'false'
55+
run: npm run format:check
56+
57+
- name: Type check
58+
if: steps.package.outputs.published == 'false'
59+
run: npm run typecheck
60+
61+
- name: Test
62+
if: steps.package.outputs.published == 'false'
63+
run: npm test
64+
65+
- name: Publish
66+
if: steps.package.outputs.published == 'false'
67+
run: npx --yes --package npm@11 npm publish --access public --provenance

.npmignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ ANALYSIS.md
1313
docs/
1414
notes/
1515

16-
# Source files (since we're shipping dist/)
16+
# Server source file (server runtime ships dist/)
1717
index.ts
18-
lib/
1918

2019
# Git
2120
.git/

README.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ opencode plugin @tarquinen/opencode-dcp@latest --global
1717

1818
This installs the package and adds it to your global OpenCode config.
1919

20+
## Project Status
21+
22+
Development on DCP has slowed because most new context-management work has moved to [Sleev](https://sleev.ai) and the `sleev` CLI. Sleev is a local proxy for Claude Code, Codex, and OpenCode that builds on DCP's core ideas with newer context-management features and will work with any harness/client.
23+
24+
DCP remains available for OpenCode plugin users, but new features are landing in Sleev first. If you are starting fresh, we recommend trying Sleev:
25+
26+
```bash
27+
npm i -g sleev
28+
sleev
29+
```
30+
2031
## How It Works
2132

2233
DCP reduces context size through a compress tool and automatic cleanup. Your session history is never modified — DCP replaces pruned content with placeholders before sending requests to your LLM.
@@ -176,16 +187,10 @@ Each level overrides the previous, so project settings take priority over global
176187

177188
### Commands
178189

179-
DCP provides a `/dcp` slash command:
190+
DCP provides a TUI panel and one prompt-producing slash command:
180191

181-
- `/dcp` — Shows available DCP commands
182-
- `/dcp context` — Shows a breakdown of your current session's token usage by category (system, user, assistant, tools, etc.) and how much has been saved through pruning.
183-
- `/dcp stats` — Shows cumulative pruning statistics across all sessions.
184-
- `/dcp sweep` — Prunes all tools since the last user message. Accepts an optional count: `/dcp sweep 10` prunes the last 10 tools. Respects `commands.protectedTools`.
185-
- `/dcp manual [on|off]` — Toggle manual mode or set explicit state. When on, the AI will not autonomously use context management tools.
186-
- `/dcp compress [focus]` — Trigger a single compress tool execution. Optional focus text directs what content to compress, following the active `compress.mode`.
187-
- `/dcp decompress <n>` — Restore a specific active compression by ID (for example `/dcp decompress 2`). Running without an argument shows available compression IDs, token sizes, and topics.
188-
- `/dcp recompress <n>` — Re-apply a user-decompressed compression by ID (for example `/dcp recompress 2`). Running without an argument shows recompressible IDs, token sizes, and topics.
192+
- `/dcp` — Opens the DCP panel with context, stats, and manual-mode controls.
193+
- `/dcp-compress [focus]` — Asks the model to run one compression pass. Optional focus text directs what content to compress, following the active `compress.mode`.
189194

190195
### Prompt Overrides
191196

index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ const server: Plugin = (async (ctx) => {
9696

9797
if (config.commands.enabled && config.compress.permission !== "deny") {
9898
opencodeConfig.command ??= {}
99-
opencodeConfig.command["dcp"] = {
99+
opencodeConfig.command["dcp-compress"] = {
100100
template: "",
101-
description: "Show available DCP commands",
101+
description: "Trigger DCP manual compression with: /dcp-compress [focus]",
102102
}
103103
}
104104

lib/commands/context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ interface TokenBreakdown {
7070
total: number
7171
}
7272

73-
function analyzeTokens(state: SessionState, messages: WithParts[]): TokenBreakdown {
73+
export function analyzeContextTokens(state: SessionState, messages: WithParts[]): TokenBreakdown {
7474
const breakdown: TokenBreakdown = {
7575
system: 0,
7676
user: 0,
@@ -235,7 +235,7 @@ function createBar(value: number, maxValue: number, width: number, char: string
235235
return bar
236236
}
237237

238-
function formatContextMessage(breakdown: TokenBreakdown): string {
238+
export function formatContextMessage(breakdown: TokenBreakdown): string {
239239
const lines: string[] = []
240240
const barWidth = 30
241241

@@ -296,7 +296,7 @@ function formatContextMessage(breakdown: TokenBreakdown): string {
296296
export async function handleContextCommand(ctx: ContextCommandContext): Promise<void> {
297297
const { client, state, logger, sessionId, messages } = ctx
298298

299-
const breakdown = analyzeTokens(state, messages)
299+
const breakdown = analyzeContextTokens(state, messages)
300300

301301
const message = formatContextMessage(breakdown)
302302

lib/commands/help.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,29 @@ export interface HelpCommandContext {
1919
messages: WithParts[]
2020
}
2121

22-
const BASE_COMMANDS: [string, string][] = [
23-
["/dcp context", "Show token usage breakdown for current session"],
24-
["/dcp stats", "Show DCP pruning statistics"],
25-
["/dcp sweep [n]", "Prune tools since last user message, or last n tools"],
26-
["/dcp manual [on|off]", "Toggle manual mode or set explicit state"],
22+
const TUI_COMMANDS: [string, string][] = [
23+
["DCP Context", "Show token usage breakdown for current session"],
24+
["DCP Stats", "Show DCP pruning statistics"],
25+
["DCP Help", "Show this help in a modal"],
2726
]
2827

2928
const TOOL_COMMANDS: Record<string, [string, string]> = {
30-
compress: ["/dcp compress [focus]", "Trigger manual compress tool execution"],
29+
compress: ["/dcp-compress [focus]", "Trigger manual compress tool execution"],
3130
decompress: ["/dcp decompress <n>", "Restore selected compression"],
3231
recompress: ["/dcp recompress <n>", "Re-apply a user-decompressed compression"],
3332
}
3433

3534
function getVisibleCommands(state: SessionState, config: PluginConfig): [string, string][] {
36-
const commands = [...BASE_COMMANDS]
35+
const commands = [...TUI_COMMANDS]
3736

3837
if (compressPermission(state, config) !== "deny") {
3938
commands.push(TOOL_COMMANDS.compress)
40-
commands.push(TOOL_COMMANDS.decompress)
41-
commands.push(TOOL_COMMANDS.recompress)
4239
}
4340

4441
return commands
4542
}
4643

47-
function formatHelpMessage(state: SessionState, config: PluginConfig): string {
44+
export function formatHelpMessage(state: SessionState, config: PluginConfig): string {
4845
const commands = getVisibleCommands(state, config)
4946
const colWidth = Math.max(...commands.map(([cmd]) => cmd.length)) + 4
5047
const lines: string[] = []
@@ -55,6 +52,9 @@ function formatHelpMessage(state: SessionState, config: PluginConfig): string {
5552
lines.push("")
5653
lines.push(` ${"Manual mode:".padEnd(colWidth)}${state.manualMode ? "ON" : "OFF"}`)
5754
lines.push("")
55+
lines.push(" Open the command palette for DCP modal commands.")
56+
lines.push(" Use /dcp-compress [focus] when you want DCP to ask the model to run compression.")
57+
lines.push("")
5858
for (const [cmd, desc] of commands) {
5959
lines.push(` ${cmd.padEnd(colWidth)}${desc}`)
6060
}

lib/commands/manual.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
*
55
* Usage:
66
* /dcp manual [on|off] - Toggle manual mode or set explicit state
7-
* /dcp compress [focus] - Trigger manual compress execution
7+
* /dcp-compress [focus] - Trigger manual compress execution
88
*/
99

1010
import type { Logger } from "../logger"
1111
import type { SessionState, WithParts } from "../state"
1212
import type { PluginConfig } from "../config"
1313
import { sendIgnoredMessage } from "../ui/notification"
14+
import { saveManualModeSetting } from "../state/persistence"
1415
import { getCurrentParams } from "../token-utils"
1516
import { buildCompressedBlockGuidance } from "../prompts/extensions/nudge"
1617
import { isIgnoredUserMessage } from "../messages/query"
1718

18-
const MANUAL_MODE_ON = "Manual mode is now ON. Use /dcp compress to trigger context tools manually."
19+
const MANUAL_MODE_ON = "Manual mode is now ON. Use /dcp-compress to trigger context tools manually."
1920

2021
const MANUAL_MODE_OFF = "Manual mode is now OFF."
2122

@@ -76,6 +77,7 @@ export async function handleManualToggleCommand(
7677
params,
7778
logger,
7879
)
80+
await saveManualModeSetting(sessionId, !!state.manualMode, logger)
7981

8082
logger.info("Manual mode toggled", { manualMode: state.manualMode })
8183
}

lib/commands/stats.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface StatsCommandContext {
1919
messages: WithParts[]
2020
}
2121

22-
function formatStatsMessage(
22+
export function formatStatsMessage(
2323
sessionTokens: number,
2424
sessionSummaryTokens: number,
2525
sessionTools: number,
@@ -92,7 +92,32 @@ function formatCompressionTime(ms: number): string {
9292
export async function handleStatsCommand(ctx: StatsCommandContext): Promise<void> {
9393
const { client, state, logger, sessionId, messages } = ctx
9494

95-
// Session stats from in-memory state
95+
const report = await buildStatsReport(state, logger)
96+
const message = formatStatsMessage(
97+
report.sessionTokens,
98+
report.sessionSummaryTokens,
99+
report.sessionTools,
100+
report.sessionMessages,
101+
report.sessionDurationMs,
102+
report.allTime,
103+
)
104+
105+
const params = getCurrentParams(state, messages, logger)
106+
await sendIgnoredMessage(client, sessionId, message, params, logger)
107+
108+
logger.info("Stats command executed", {
109+
sessionTokens: report.sessionTokens,
110+
sessionSummaryTokens: report.sessionSummaryTokens,
111+
sessionTools: report.sessionTools,
112+
sessionMessages: report.sessionMessages,
113+
sessionDurationMs: report.sessionDurationMs,
114+
allTimeTokens: report.allTime.totalTokens,
115+
allTimeTools: report.allTime.totalTools,
116+
allTimeMessages: report.allTime.totalMessages,
117+
})
118+
}
119+
120+
export async function buildStatsReport(state: SessionState, logger: Logger) {
96121
const sessionTokens = state.stats.totalPruneTokens
97122
const sessionSummaryTokens = Array.from(state.prune.messages.blocksById.values()).reduce(
98123
(total, block) => (block.active ? total + block.summaryTokens : total),
@@ -123,26 +148,12 @@ export async function handleStatsCommand(ctx: StatsCommandContext): Promise<void
123148
// All-time stats from storage files
124149
const allTime = await loadAllSessionStats(logger)
125150

126-
const message = formatStatsMessage(
151+
return {
127152
sessionTokens,
128153
sessionSummaryTokens,
129154
sessionTools,
130155
sessionMessages,
131156
sessionDurationMs,
132157
allTime,
133-
)
134-
135-
const params = getCurrentParams(state, messages, logger)
136-
await sendIgnoredMessage(client, sessionId, message, params, logger)
137-
138-
logger.info("Stats command executed", {
139-
sessionTokens,
140-
sessionSummaryTokens,
141-
sessionTools,
142-
sessionMessages,
143-
sessionDurationMs,
144-
allTimeTokens: allTime.totalTokens,
145-
allTimeTools: allTime.totalTools,
146-
allTimeMessages: allTime.totalMessages,
147-
})
158+
}
148159
}

lib/compress/pipeline.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { WithParts } from "../state"
2-
import { ensureSessionInitialized } from "../state"
2+
import { ensureSessionInitialized, refreshManualMode } from "../state"
33
import { saveSessionState } from "../state/persistence"
44
import { assignMessageRefs } from "../message-ids"
55
import { isIgnoredUserMessage } from "../messages/query"
@@ -39,6 +39,8 @@ export async function prepareSession(
3939
toolCtx: RunContext,
4040
title: string,
4141
): Promise<PreparedSession> {
42+
await refreshManualMode(ctx.state, toolCtx.sessionID, ctx.logger, ctx.config.manualMode.enabled)
43+
4244
if (ctx.state.manualMode && ctx.state.manualMode !== "compress-pending") {
4345
throw new Error(
4446
"Manual mode: compress blocked. Do not retry until `<compress triggered manually>` appears in user context.",

0 commit comments

Comments
 (0)