@tuanhung303/opencode-acp 2.1.1 → 2.2.0
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 +4 -4
- package/dist/index.js +3 -3
- package/dist/lib/commands/help.d.ts +2 -2
- package/dist/lib/commands/help.js +6 -6
- package/dist/lib/commands/sweep.d.ts +3 -3
- package/dist/lib/commands/sweep.js +5 -5
- package/dist/lib/config.js +15 -15
- package/dist/lib/hooks.js +6 -6
- package/dist/lib/logger.js +1 -1
- package/dist/lib/state/persistence.d.ts +2 -2
- package/dist/lib/state/persistence.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ This fork utilizes more aggressive defaults than the upstream version to maximiz
|
|
|
79
79
|
|
|
80
80
|
## Configuration File
|
|
81
81
|
|
|
82
|
-
The plugin automatically creates a config at `~/.config/opencode/
|
|
82
|
+
The plugin automatically creates a config at `~/.config/opencode/acp.jsonc`. You can also use project-level overrides in `.opencode/acp.jsonc`.
|
|
83
83
|
|
|
84
84
|
```jsonc
|
|
85
85
|
{
|
|
@@ -97,9 +97,9 @@ The plugin automatically creates a config at `~/.config/opencode/dcp.jsonc`. You
|
|
|
97
97
|
|
|
98
98
|
## Commands
|
|
99
99
|
|
|
100
|
-
- `/
|
|
101
|
-
- `/
|
|
102
|
-
- `/
|
|
100
|
+
- `/acp context`: Visualize token usage and savings.
|
|
101
|
+
- `/acp stats`: Cumulative efficiency report.
|
|
102
|
+
- `/acp sweep`: Force a manual cleanup of the current session.
|
|
103
103
|
|
|
104
104
|
## License
|
|
105
105
|
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const plugin = (async (ctx) => {
|
|
|
10
10
|
}
|
|
11
11
|
const logger = new Logger(config.debug);
|
|
12
12
|
const state = createSessionState();
|
|
13
|
-
logger.info("
|
|
13
|
+
logger.info("ACP initialized", {
|
|
14
14
|
strategies: config.strategies,
|
|
15
15
|
});
|
|
16
16
|
return {
|
|
@@ -46,9 +46,9 @@ const plugin = (async (ctx) => {
|
|
|
46
46
|
config: async (opencodeConfig) => {
|
|
47
47
|
if (config.commands.enabled) {
|
|
48
48
|
opencodeConfig.command ??= {};
|
|
49
|
-
opencodeConfig.command["
|
|
49
|
+
opencodeConfig.command["acp"] = {
|
|
50
50
|
template: "",
|
|
51
|
-
description: "Show available
|
|
51
|
+
description: "Show available ACP commands",
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
const toolsToAdd = [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Shows available
|
|
2
|
+
* ACP Help command handler.
|
|
3
|
+
* Shows available ACP commands and their descriptions.
|
|
4
4
|
*/
|
|
5
5
|
import type { Logger } from "../logger";
|
|
6
6
|
import type { SessionState, WithParts } from "../state";
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Shows available
|
|
2
|
+
* ACP Help command handler.
|
|
3
|
+
* Shows available ACP commands and their descriptions.
|
|
4
4
|
*/
|
|
5
5
|
import { sendIgnoredMessage } from "../ui/notification";
|
|
6
6
|
import { getCurrentParams } from "../strategies/utils";
|
|
7
7
|
function formatHelpMessage() {
|
|
8
8
|
const lines = [];
|
|
9
9
|
lines.push("╭───────────────────────────────────────────────────────────╮");
|
|
10
|
-
lines.push("│
|
|
10
|
+
lines.push("│ ACP Commands │");
|
|
11
11
|
lines.push("╰───────────────────────────────────────────────────────────╯");
|
|
12
12
|
lines.push("");
|
|
13
|
-
lines.push(" /
|
|
14
|
-
lines.push(" /
|
|
15
|
-
lines.push(" /
|
|
13
|
+
lines.push(" /acp context Show token usage breakdown for current session");
|
|
14
|
+
lines.push(" /acp stats Show ACP pruning statistics");
|
|
15
|
+
lines.push(" /acp sweep [n] Prune tools since last user message, or last n tools");
|
|
16
16
|
lines.push("");
|
|
17
17
|
return lines.join("\n");
|
|
18
18
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ACP Sweep command handler.
|
|
3
3
|
* Prunes tool outputs since the last user message, or the last N tools.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* /
|
|
7
|
-
* /
|
|
6
|
+
* /acp sweep - Prune all tools since the previous user message
|
|
7
|
+
* /acp sweep 10 - Prune the last 10 tools
|
|
8
8
|
*/
|
|
9
9
|
import type { Logger } from "../logger";
|
|
10
10
|
import type { SessionState, WithParts } from "../state";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ACP Sweep command handler.
|
|
3
3
|
* Prunes tool outputs since the last user message, or the last N tools.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* /
|
|
7
|
-
* /
|
|
6
|
+
* /acp sweep - Prune all tools since the previous user message
|
|
7
|
+
* /acp sweep 10 - Prune the last 10 tools
|
|
8
8
|
*/
|
|
9
9
|
import { sendIgnoredMessage } from "../ui/notification";
|
|
10
10
|
import { formatPrunedItemsList } from "../ui/utils";
|
|
@@ -43,7 +43,7 @@ function collectToolIdsAfterIndex(state, messages, afterIndex) {
|
|
|
43
43
|
function formatNoUserMessage() {
|
|
44
44
|
const lines = [];
|
|
45
45
|
lines.push("╭───────────────────────────────────────────────────────────╮");
|
|
46
|
-
lines.push("│
|
|
46
|
+
lines.push("│ ACP Sweep │");
|
|
47
47
|
lines.push("╰───────────────────────────────────────────────────────────╯");
|
|
48
48
|
lines.push("");
|
|
49
49
|
lines.push("Nothing swept: no user message found.");
|
|
@@ -52,7 +52,7 @@ function formatNoUserMessage() {
|
|
|
52
52
|
function formatSweepMessage(toolCount, tokensSaved, mode, toolIds, toolMetadata, workingDirectory, skippedProtected) {
|
|
53
53
|
const lines = [];
|
|
54
54
|
lines.push("╭───────────────────────────────────────────────────────────╮");
|
|
55
|
-
lines.push("│
|
|
55
|
+
lines.push("│ ACP Sweep │");
|
|
56
56
|
lines.push("╰───────────────────────────────────────────────────────────╯");
|
|
57
57
|
lines.push("");
|
|
58
58
|
if (toolCount === 0) {
|
package/dist/lib/config.js
CHANGED
|
@@ -431,7 +431,7 @@ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
|
|
|
431
431
|
try {
|
|
432
432
|
ctx.client.tui.showToast({
|
|
433
433
|
body: {
|
|
434
|
-
title: `
|
|
434
|
+
title: `ACP: Invalid ${configType}`,
|
|
435
435
|
message: `${configPath}\n${messages.join("\n")}`,
|
|
436
436
|
variant: "warning",
|
|
437
437
|
duration: 7000,
|
|
@@ -505,8 +505,8 @@ export const DEFAULT_CONFIG = {
|
|
|
505
505
|
},
|
|
506
506
|
};
|
|
507
507
|
const GLOBAL_CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
508
|
-
const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, "
|
|
509
|
-
const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, "
|
|
508
|
+
const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, "acp.jsonc");
|
|
509
|
+
const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, "acp.json");
|
|
510
510
|
function findOpencodeDir(startDir) {
|
|
511
511
|
let current = startDir;
|
|
512
512
|
while (current !== "/") {
|
|
@@ -522,7 +522,7 @@ function findOpencodeDir(startDir) {
|
|
|
522
522
|
return null;
|
|
523
523
|
}
|
|
524
524
|
function getConfigPaths(ctx) {
|
|
525
|
-
// Global: ~/.config/opencode/
|
|
525
|
+
// Global: ~/.config/opencode/acp.jsonc|json
|
|
526
526
|
let globalPath = null;
|
|
527
527
|
if (existsSync(GLOBAL_CONFIG_PATH_JSONC)) {
|
|
528
528
|
globalPath = GLOBAL_CONFIG_PATH_JSONC;
|
|
@@ -530,12 +530,12 @@ function getConfigPaths(ctx) {
|
|
|
530
530
|
else if (existsSync(GLOBAL_CONFIG_PATH_JSON)) {
|
|
531
531
|
globalPath = GLOBAL_CONFIG_PATH_JSON;
|
|
532
532
|
}
|
|
533
|
-
// Custom config directory: $OPENCODE_CONFIG_DIR/
|
|
533
|
+
// Custom config directory: $OPENCODE_CONFIG_DIR/acp.jsonc|json
|
|
534
534
|
let configDirPath = null;
|
|
535
535
|
const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
|
|
536
536
|
if (opencodeConfigDir) {
|
|
537
|
-
const configJsonc = join(opencodeConfigDir, "
|
|
538
|
-
const configJson = join(opencodeConfigDir, "
|
|
537
|
+
const configJsonc = join(opencodeConfigDir, "acp.jsonc");
|
|
538
|
+
const configJson = join(opencodeConfigDir, "acp.json");
|
|
539
539
|
if (existsSync(configJsonc)) {
|
|
540
540
|
configDirPath = configJsonc;
|
|
541
541
|
}
|
|
@@ -543,13 +543,13 @@ function getConfigPaths(ctx) {
|
|
|
543
543
|
configDirPath = configJson;
|
|
544
544
|
}
|
|
545
545
|
}
|
|
546
|
-
// Project: <project>/.opencode/
|
|
546
|
+
// Project: <project>/.opencode/acp.jsonc|json
|
|
547
547
|
let projectPath = null;
|
|
548
548
|
if (ctx?.directory) {
|
|
549
549
|
const opencodeDir = findOpencodeDir(ctx.directory);
|
|
550
550
|
if (opencodeDir) {
|
|
551
|
-
const projectJsonc = join(opencodeDir, "
|
|
552
|
-
const projectJson = join(opencodeDir, "
|
|
551
|
+
const projectJsonc = join(opencodeDir, "acp.jsonc");
|
|
552
|
+
const projectJson = join(opencodeDir, "acp.json");
|
|
553
553
|
if (existsSync(projectJsonc)) {
|
|
554
554
|
projectPath = projectJsonc;
|
|
555
555
|
}
|
|
@@ -565,7 +565,7 @@ function createDefaultConfig() {
|
|
|
565
565
|
mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
566
566
|
}
|
|
567
567
|
const configContent = `{
|
|
568
|
-
"$schema": "https://raw.githubusercontent.com/
|
|
568
|
+
"$schema": "https://raw.githubusercontent.com/tuanhung303/opencode-agent-context-pruning/master/acp.schema.json"
|
|
569
569
|
}
|
|
570
570
|
`;
|
|
571
571
|
writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, "utf-8");
|
|
@@ -740,7 +740,7 @@ export function getConfig(ctx) {
|
|
|
740
740
|
try {
|
|
741
741
|
ctx.client.tui.showToast({
|
|
742
742
|
body: {
|
|
743
|
-
title: "
|
|
743
|
+
title: "ACP: Invalid config",
|
|
744
744
|
message: `${configPaths.global}\n${result.parseError}\nUsing default values`,
|
|
745
745
|
variant: "warning",
|
|
746
746
|
duration: 7000,
|
|
@@ -777,7 +777,7 @@ export function getConfig(ctx) {
|
|
|
777
777
|
// No config exists, create default
|
|
778
778
|
createDefaultConfig();
|
|
779
779
|
}
|
|
780
|
-
// Load and merge $OPENCODE_CONFIG_DIR/
|
|
780
|
+
// Load and merge $OPENCODE_CONFIG_DIR/acp.jsonc|json (overrides global)
|
|
781
781
|
if (configPaths.configDir) {
|
|
782
782
|
const result = loadConfigFile(configPaths.configDir);
|
|
783
783
|
if (result.parseError) {
|
|
@@ -785,7 +785,7 @@ export function getConfig(ctx) {
|
|
|
785
785
|
try {
|
|
786
786
|
ctx.client.tui.showToast({
|
|
787
787
|
body: {
|
|
788
|
-
title: "
|
|
788
|
+
title: "ACP: Invalid configDir config",
|
|
789
789
|
message: `${configPaths.configDir}\n${result.parseError}\nUsing global/default values`,
|
|
790
790
|
variant: "warning",
|
|
791
791
|
duration: 7000,
|
|
@@ -826,7 +826,7 @@ export function getConfig(ctx) {
|
|
|
826
826
|
try {
|
|
827
827
|
ctx.client.tui.showToast({
|
|
828
828
|
body: {
|
|
829
|
-
title: "
|
|
829
|
+
title: "ACP: Invalid project config",
|
|
830
830
|
message: `${configPaths.project}\n${result.parseError}\nUsing global/default values`,
|
|
831
831
|
variant: "warning",
|
|
832
832
|
duration: 7000,
|
package/dist/lib/hooks.js
CHANGED
|
@@ -19,7 +19,7 @@ export function createSystemPromptHandler(state, logger, config) {
|
|
|
19
19
|
}
|
|
20
20
|
const systemText = output.system.join("\n");
|
|
21
21
|
if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) {
|
|
22
|
-
logger.info("Skipping
|
|
22
|
+
logger.info("Skipping ACP system prompt injection for internal agent");
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
const discardEnabled = config.tools.discard.enabled;
|
|
@@ -68,7 +68,7 @@ export function createCommandExecuteHandler(client, state, logger, config, worki
|
|
|
68
68
|
if (!config.commands.enabled) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
if (input.command === "
|
|
71
|
+
if (input.command === "acp") {
|
|
72
72
|
const args = (input.arguments || "").trim().split(/\s+/).filter(Boolean);
|
|
73
73
|
const subcommand = args[0]?.toLowerCase() || "";
|
|
74
74
|
const _subArgs = args.slice(1);
|
|
@@ -84,7 +84,7 @@ export function createCommandExecuteHandler(client, state, logger, config, worki
|
|
|
84
84
|
sessionId: input.sessionID,
|
|
85
85
|
messages,
|
|
86
86
|
});
|
|
87
|
-
throw new Error("
|
|
87
|
+
throw new Error("__ACP_CONTEXT_HANDLED__");
|
|
88
88
|
}
|
|
89
89
|
if (subcommand === "stats") {
|
|
90
90
|
await handleStatsCommand({
|
|
@@ -94,7 +94,7 @@ export function createCommandExecuteHandler(client, state, logger, config, worki
|
|
|
94
94
|
sessionId: input.sessionID,
|
|
95
95
|
messages,
|
|
96
96
|
});
|
|
97
|
-
throw new Error("
|
|
97
|
+
throw new Error("__ACP_STATS_HANDLED__");
|
|
98
98
|
}
|
|
99
99
|
if (subcommand === "sweep") {
|
|
100
100
|
await handleSweepCommand({
|
|
@@ -107,7 +107,7 @@ export function createCommandExecuteHandler(client, state, logger, config, worki
|
|
|
107
107
|
args: _subArgs,
|
|
108
108
|
workingDirectory,
|
|
109
109
|
});
|
|
110
|
-
throw new Error("
|
|
110
|
+
throw new Error("__ACP_SWEEP_HANDLED__");
|
|
111
111
|
}
|
|
112
112
|
await handleHelpCommand({
|
|
113
113
|
client,
|
|
@@ -116,7 +116,7 @@ export function createCommandExecuteHandler(client, state, logger, config, worki
|
|
|
116
116
|
sessionId: input.sessionID,
|
|
117
117
|
messages,
|
|
118
118
|
});
|
|
119
|
-
throw new Error("
|
|
119
|
+
throw new Error("__ACP_HELP_HANDLED__");
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
122
|
}
|
package/dist/lib/logger.js
CHANGED
|
@@ -8,7 +8,7 @@ export class Logger {
|
|
|
8
8
|
constructor(enabled) {
|
|
9
9
|
this.enabled = enabled;
|
|
10
10
|
const opencodeConfigDir = join(homedir(), ".config", "opencode");
|
|
11
|
-
this.logDir = join(opencodeConfigDir, "logs", "
|
|
11
|
+
this.logDir = join(opencodeConfigDir, "logs", "acp");
|
|
12
12
|
}
|
|
13
13
|
async ensureLogDir() {
|
|
14
14
|
if (!existsSync(this.logDir)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* State persistence module for
|
|
2
|
+
* State persistence module for ACP plugin.
|
|
3
3
|
* Persists pruned tool IDs across sessions so they survive OpenCode restarts.
|
|
4
|
-
* Storage location: ~/.local/share/opencode/storage/plugin/
|
|
4
|
+
* Storage location: ~/.local/share/opencode/storage/plugin/acp/{sessionId}.json
|
|
5
5
|
*/
|
|
6
6
|
import type { SessionState, SessionStats, Prune } from "./types";
|
|
7
7
|
import type { Logger } from "../logger";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* State persistence module for
|
|
2
|
+
* State persistence module for ACP plugin.
|
|
3
3
|
* Persists pruned tool IDs across sessions so they survive OpenCode restarts.
|
|
4
|
-
* Storage location: ~/.local/share/opencode/storage/plugin/
|
|
4
|
+
* Storage location: ~/.local/share/opencode/storage/plugin/acp/{sessionId}.json
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "fs/promises";
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
8
|
import { homedir } from "os";
|
|
9
9
|
import { join } from "path";
|
|
10
|
-
const STORAGE_DIR = join(homedir(), ".local", "share", "opencode", "storage", "plugin", "
|
|
10
|
+
const STORAGE_DIR = join(homedir(), ".local", "share", "opencode", "storage", "plugin", "acp");
|
|
11
11
|
async function ensureStorageDir() {
|
|
12
12
|
if (!existsSync(STORAGE_DIR)) {
|
|
13
13
|
await fs.mkdir(STORAGE_DIR, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@tuanhung303/opencode-acp",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs - Aggressive fork with Head-Tail Truncation, Read Consolidation, Prune Thinking, and Placeholder Compression strategies",
|
|
7
7
|
"main": "./dist/index.js",
|