opencode-context-dropper-plugin 0.1.5 → 0.1.6

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.
Files changed (3) hide show
  1. package/README.md +39 -10
  2. package/dist/index.js +53 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # OpenCode Context Dropper Plugin
2
2
 
3
- A Context Dropper plugin for OpenCode, built with Bun and TypeScript. It leverages the internal `context-dropper` APIs to efficiently process a large set of files one-by-one entirely within your OpenCode sessions. It automatically tracks context and handles token pruning to allow continuous agent iteration.
3
+ A Context Dropper plugin for OpenCode, built with Bun and TypeScript. It
4
+ leverages the internal `context-dropper` APIs to efficiently process a large set
5
+ of files one-by-one entirely within your OpenCode sessions. It automatically
6
+ tracks context and handles token pruning to allow continuous agent iteration.
4
7
 
5
8
  ## Prerequisites
6
9
 
@@ -13,9 +16,12 @@ You can install the `context-dropper` plugin using one of the following methods.
13
16
 
14
17
  ### 1. Configure OpenCode (Recommended)
15
18
 
16
- You do not need to manually install the package. OpenCode will automatically resolve and install it from the NPM registry when you add it to your configuration.
19
+ You do not need to manually install the package. OpenCode will automatically
20
+ resolve and install it from the NPM registry when you add it to your
21
+ configuration.
17
22
 
18
- You can configure the plugin either globally for all projects, or locally for a single project:
23
+ You can configure the plugin either globally for all projects, or locally for a
24
+ single project:
19
25
 
20
26
  **Option A: Project-Level (Local)**
21
27
 
@@ -30,7 +36,8 @@ You can configure the plugin either globally for all projects, or locally for a
30
36
 
31
37
  **Option B: Global-Level**
32
38
 
33
- 1. Create or edit your global OpenCode config file at `~/.config/opencode/opencode.json`.
39
+ 1. Create or edit your global OpenCode config file at
40
+ `~/.config/opencode/opencode.json`.
34
41
  2. Add the package name to the `plugin` array:
35
42
 
36
43
  ```json
@@ -39,7 +46,9 @@ You can configure the plugin either globally for all projects, or locally for a
39
46
  }
40
47
  ```
41
48
 
42
- (Optional) If you prefer to manage the installation yourself, you can install the plugin globally using Bun (`bun install -g opencode-context-dropper-plugin`) or NPM (`npm install -g opencode-context-dropper-plugin`).
49
+ (Optional) If you prefer to manage the installation yourself, you can install
50
+ the plugin globally using Bun (`bun install -g opencode-context-dropper-plugin`)
51
+ or NPM (`npm install -g opencode-context-dropper-plugin`).
43
52
 
44
53
  ## Usage
45
54
 
@@ -49,14 +58,16 @@ Once installed, start OpenCode:
49
58
  opencode
50
59
  ```
51
60
 
52
- You can invoke the context dropper loop inside chat simply by using the `/drop` slash command:
61
+ You can invoke the context dropper loop inside chat simply by using the `/drop`
62
+ slash command:
53
63
 
54
64
  ```text
55
65
  /drop <filesetName> <instructions>
56
66
  ```
57
67
 
58
68
  - `<filesetName>` is the name of a pre-existing fileset in your project.
59
- - `<instructions>` is the prompt you want the AI to perform on each file sequentially.
69
+ - `<instructions>` is the prompt you want the AI to perform on each file
70
+ sequentially.
60
71
 
61
72
  **Example:**
62
73
 
@@ -68,9 +79,27 @@ You can invoke the context dropper loop inside chat simply by using the `/drop`
68
79
 
69
80
  Once invoked, the plugin completely takes over the context management:
70
81
 
71
- 1. It automatically fetches the first file in the fileset and provides it to the agent along with your instructions.
72
- 2. The agent performs the instructions and automatically calls the `context-dropper.next` tool.
73
- 3. **Context Pruning**: When the tool is called, the file is tagged as processed. The plugin drops the previous file's context from the chat history (saving tokens), and feeds the next file to the agent.
82
+ 1. It automatically fetches the first file in the fileset and provides it to the
83
+ agent along with your instructions.
84
+ 2. The agent performs the instructions and automatically calls the
85
+ `context-dropper.next` tool.
86
+ 3. **Context Pruning**: When the tool is called, the file is tagged as
87
+ processed. The plugin drops the previous file's context from the chat history
88
+ (saving tokens), and feeds the next file to the agent.
74
89
  4. This loop continues until all files are processed.
75
90
 
76
91
  To forcefully stop the loop before it finishes, type **"stop context-dropper"**.
92
+
93
+ ## Logging
94
+
95
+ Plugin activity is written to OpenCode's native log system via the
96
+ `context-dropper` service. To view logs, run OpenCode with debug-level output
97
+ enabled:
98
+
99
+ ```bash
100
+ opencode --log-level debug
101
+ ```
102
+
103
+ Unexpected errors are logged at `error` level and are visible at any log level.
104
+ Operational events (dropper lifecycle, context pruning) are logged at `info`
105
+ level.
package/dist/index.js CHANGED
@@ -12333,14 +12333,10 @@ function tool(input) {
12333
12333
  return input;
12334
12334
  }
12335
12335
  tool.schema = exports_external;
12336
- // src/index.ts
12337
- import * as fs from "node:fs";
12338
- import * as os from "node:os";
12339
- import * as path4 from "node:path";
12340
12336
  // ../package.json
12341
12337
  var package_default = {
12342
12338
  name: "context-dropper",
12343
- version: "0.1.5",
12339
+ version: "0.1.6",
12344
12340
  description: "CLI for iterating through a fixed list of files, tracking position, and tagging progress within an AI agent's session.",
12345
12341
  author: {
12346
12342
  name: "Fardjad Davari",
@@ -12376,10 +12372,30 @@ var package_default = {
12376
12372
  // ../src/version/version.ts
12377
12373
  var getPackageVersion = () => package_default.version;
12378
12374
 
12375
+ // src/logger.ts
12376
+ function createLogger(service, client) {
12377
+ return (msg, extra, level = "info") => {
12378
+ client.app.log({
12379
+ body: {
12380
+ service,
12381
+ level,
12382
+ message: msg,
12383
+ ...extra !== undefined ? { extra } : {}
12384
+ }
12385
+ }).catch((e) => {
12386
+ console.error(`[${service}] Failed to send log: ${e}`);
12387
+ });
12388
+ };
12389
+ }
12390
+
12379
12391
  // src/session.ts
12380
12392
  class SessionManager {
12381
12393
  sessionStates = new Map;
12382
12394
  sessionPruneMap = new Map;
12395
+ log;
12396
+ constructor(log) {
12397
+ this.log = log;
12398
+ }
12383
12399
  setSession(sessionId, state) {
12384
12400
  this.sessionStates.set(sessionId, state);
12385
12401
  }
@@ -12387,10 +12403,12 @@ class SessionManager {
12387
12403
  return this.sessionStates.get(sessionId);
12388
12404
  }
12389
12405
  deleteSession(sessionId) {
12406
+ this.log(`Deleting session ${sessionId}`);
12390
12407
  this.sessionStates.delete(sessionId);
12391
12408
  this.sessionPruneMap.delete(sessionId);
12392
12409
  }
12393
12410
  setPruneMessageId(sessionId, messageId) {
12411
+ this.log(`Prune anchor set`, { sessionId, messageId });
12394
12412
  this.sessionPruneMap.set(sessionId, messageId);
12395
12413
  }
12396
12414
  getPruneMessageId(sessionId) {
@@ -12903,12 +12921,15 @@ class Toolkit {
12903
12921
  dropperService;
12904
12922
  filesetService;
12905
12923
  dataDir;
12906
- constructor(cwd, dropperService, filesetService) {
12924
+ log;
12925
+ constructor(cwd, log, dropperService, filesetService) {
12926
+ this.log = log;
12907
12927
  this.dropperService = dropperService ?? new DefaultDropperService;
12908
12928
  this.filesetService = filesetService ?? new DefaultFilesetService;
12909
12929
  this.dataDir = path3.resolve(cwd, ".context-dropper");
12910
12930
  }
12911
12931
  async createDropper(filesetName, dropperName) {
12932
+ this.log(`Creating dropper`, { dropperName, filesetName });
12912
12933
  await this.dropperService.create({
12913
12934
  dataDir: this.dataDir,
12914
12935
  filesetName,
@@ -12916,6 +12937,7 @@ class Toolkit {
12916
12937
  });
12917
12938
  }
12918
12939
  async removeDropper(dropperName) {
12940
+ this.log(`Removing dropper`, { dropperName });
12919
12941
  try {
12920
12942
  await this.dropperService.remove({
12921
12943
  dataDir: this.dataDir,
@@ -12929,6 +12951,7 @@ class Toolkit {
12929
12951
  }
12930
12952
  }
12931
12953
  async tagProcessed(dropperName) {
12954
+ this.log(`Tagging current file as 'processed'`, { dropperName });
12932
12955
  await this.dropperService.tag({
12933
12956
  dataDir: this.dataDir,
12934
12957
  dropperName,
@@ -12936,6 +12959,7 @@ class Toolkit {
12936
12959
  });
12937
12960
  }
12938
12961
  async isDone(dropperName) {
12962
+ this.log(`Checking if done`, { dropperName });
12939
12963
  try {
12940
12964
  await this.dropperService.isDone({
12941
12965
  dataDir: this.dataDir,
@@ -12947,6 +12971,7 @@ class Toolkit {
12947
12971
  }
12948
12972
  }
12949
12973
  async nextFile(dropperName) {
12974
+ this.log(`Advancing to next file`, { dropperName });
12950
12975
  await this.dropperService.next({
12951
12976
  dataDir: this.dataDir,
12952
12977
  dropperName
@@ -12984,40 +13009,11 @@ ${fileContent}
12984
13009
  }
12985
13010
 
12986
13011
  // src/index.ts
12987
- var opencodeDir = path4.join(os.homedir(), ".opencode");
12988
- if (!fs.existsSync(opencodeDir)) {
12989
- fs.mkdirSync(opencodeDir, { recursive: true });
12990
- }
12991
- var logFile = path4.join(opencodeDir, "context-dropper.log");
12992
- var MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
12993
- var log = (msg, ...args) => {
12994
- const timestamp = new Date().toISOString();
12995
- const formattedArgs = args.length > 0 ? " " + args.map((a) => typeof a === "object" ? JSON.stringify(a) : a).join(" ") : "";
12996
- const logMessage = `[${timestamp}] [ContextDropper] ${msg}${formattedArgs}
12997
- `;
12998
- console.error(`[ContextDropper] ${msg}`, ...args);
12999
- try {
13000
- if (fs.existsSync(logFile)) {
13001
- const stats = fs.statSync(logFile);
13002
- if (stats.size >= MAX_LOG_SIZE_BYTES) {
13003
- const content = fs.readFileSync(logFile, "utf-8");
13004
- const keepLength = Math.floor(content.length / 2);
13005
- const rotatedContent = content.slice(-keepLength);
13006
- const firstNewlineIndex = rotatedContent.indexOf(`
13007
- `);
13008
- const cleanContent = firstNewlineIndex !== -1 ? rotatedContent.slice(firstNewlineIndex + 1) : rotatedContent;
13009
- fs.writeFileSync(logFile, cleanContent);
13010
- }
13011
- }
13012
- fs.appendFileSync(logFile, logMessage);
13013
- } catch (e) {
13014
- console.error("Failed to write/rotate plugin log file", e);
13015
- }
13016
- };
13017
- var sessionManager = new SessionManager;
13018
- var toolkit = new Toolkit(process.cwd());
13019
13012
  var ContextDropperPlugin = async (ctx) => {
13020
13013
  const version2 = getPackageVersion();
13014
+ const log = createLogger("context-dropper", ctx.client);
13015
+ const toolkit = new Toolkit(ctx.worktree, log);
13016
+ const sessionManager = new SessionManager(log);
13021
13017
  log(`Plugin initializing! Version: ${version2}`);
13022
13018
  setTimeout(() => {
13023
13019
  ctx.client.tui.showToast({
@@ -13027,8 +13023,8 @@ var ContextDropperPlugin = async (ctx) => {
13027
13023
  variant: "success",
13028
13024
  duration: 5000
13029
13025
  }
13030
- }).catch((e) => log("Failed to show toast", e));
13031
- log("Initialization toast sent");
13026
+ }).catch((e) => log("Failed to show toast", { error: String(e) }, "warn"));
13027
+ log("Initialization complete", { worktree: ctx.worktree, version: version2 });
13032
13028
  }, 1000);
13033
13029
  return {
13034
13030
  tool: {
@@ -13045,13 +13041,11 @@ var ContextDropperPlugin = async (ctx) => {
13045
13041
  instructions: args.instructions
13046
13042
  });
13047
13043
  try {
13048
- log(`Removing existing dropper ${dropperName}`);
13049
13044
  await toolkit.removeDropper(dropperName);
13050
- log(`Creating dropper ${dropperName} from fileset ${args.filesetName}`);
13051
13045
  await toolkit.createDropper(args.filesetName, dropperName);
13052
13046
  return await toolkit.getFilePrompt(dropperName, args.instructions, false);
13053
13047
  } catch (error45) {
13054
- log(`Error in tool execution: ${error45.message}`);
13048
+ log(`Error in tool execution`, { error: error45.message }, "error");
13055
13049
  return `Error initializing context-dropper: ${error45.message}`;
13056
13050
  }
13057
13051
  }
@@ -13066,22 +13060,19 @@ var ContextDropperPlugin = async (ctx) => {
13066
13060
  return "No active context-dropper session found. Please initialize one first.";
13067
13061
  }
13068
13062
  try {
13069
- log(`Tagging ${state.dropperName} current file as 'processed'`);
13070
13063
  await toolkit.tagProcessed(state.dropperName);
13071
- log(`Checking if ${state.dropperName} is done`);
13072
13064
  const isDone = await toolkit.isDone(state.dropperName);
13073
13065
  if (isDone) {
13074
- log(`Session ${state.dropperName} completed.`);
13066
+ log(`Session completed`, { dropperName: state.dropperName });
13075
13067
  sessionManager.deleteSession(sessionId);
13076
13068
  return `[Context-Dropper: All files have been processed. Task complete.]`;
13077
13069
  }
13078
- log(`Advancing to next file in ${state.dropperName}`);
13079
13070
  await toolkit.nextFile(state.dropperName);
13080
13071
  const prompt = await toolkit.getFilePrompt(state.dropperName, state.instructions, true);
13081
13072
  sessionManager.setPruneMessageId(sessionId, context.messageID);
13082
13073
  return prompt;
13083
13074
  } catch (error45) {
13084
- log(`Error during 'next' processing: ${error45.message}`);
13075
+ log(`Error during 'next' processing`, { error: error45.message }, "error");
13085
13076
  return `[Context-Dropper Error: ${error45.message}]`;
13086
13077
  }
13087
13078
  }
@@ -13092,7 +13083,10 @@ var ContextDropperPlugin = async (ctx) => {
13092
13083
  for (const part of output.parts) {
13093
13084
  if (part.type === "text") {
13094
13085
  const text = part.text.trim().toLowerCase();
13095
- log(`Processing message chunk in session ${sessionId}. Starts with /drop? ${text.startsWith("/drop")}`);
13086
+ log(`Processing message chunk`, {
13087
+ sessionId,
13088
+ startsWithDrop: text.startsWith("/drop")
13089
+ });
13096
13090
  if (text.startsWith("/drop ")) {
13097
13091
  const originalText = part.text.trim();
13098
13092
  const match = originalText.match(/^\/drop\s+([^\s]+)\s+(.+)$/is);
@@ -13105,9 +13099,7 @@ var ContextDropperPlugin = async (ctx) => {
13105
13099
  instructions
13106
13100
  });
13107
13101
  try {
13108
- log(`Removing existing dropper ${dropperName}`);
13109
13102
  await toolkit.removeDropper(dropperName);
13110
- log(`Creating dropper ${dropperName} from fileset ${filesetName}`);
13111
13103
  await toolkit.createDropper(filesetName, dropperName);
13112
13104
  const prompt = await toolkit.getFilePrompt(dropperName, instructions, false);
13113
13105
  part.text = prompt;
@@ -13115,7 +13107,9 @@ var ContextDropperPlugin = async (ctx) => {
13115
13107
  sessionManager.setPruneMessageId(sessionId, output.message.id);
13116
13108
  }
13117
13109
  } catch (error45) {
13118
- log(`Error starting context-dropper via /drop: ${error45.message}`);
13110
+ log(`Error starting context-dropper via /drop`, {
13111
+ error: error45.message
13112
+ }, "error");
13119
13113
  part.text = `Error starting context-dropper: ${error45.message}`;
13120
13114
  }
13121
13115
  } else {
@@ -13144,9 +13138,16 @@ var ContextDropperPlugin = async (ctx) => {
13144
13138
  return;
13145
13139
  const pruneStartId = sessionManager.getPruneMessageId(sessionId);
13146
13140
  if (pruneStartId) {
13141
+ const totalBefore = output.messages.length;
13147
13142
  const index = output.messages.findIndex((m) => m.info && m.info.id === pruneStartId);
13148
13143
  if (index !== -1) {
13149
13144
  output.messages.splice(0, index);
13145
+ log(`Context pruned`, {
13146
+ sessionId,
13147
+ removed: index,
13148
+ totalBefore,
13149
+ remaining: output.messages.length
13150
+ });
13150
13151
  }
13151
13152
  }
13152
13153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-context-dropper-plugin",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A Context Dropper plugin for OpenCode that automates file iteration context management.",
5
5
  "author": {
6
6
  "name": "Fardjad Davari",