opencode-context-dropper-plugin 0.1.5 → 0.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 +39 -10
- package/dist/index.js +367 -396
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
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.
|
|
12339
|
+
version: "0.2.0",
|
|
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,30 +12372,304 @@ var package_default = {
|
|
|
12376
12372
|
// ../src/version/version.ts
|
|
12377
12373
|
var getPackageVersion = () => package_default.version;
|
|
12378
12374
|
|
|
12375
|
+
// src/logger.ts
|
|
12376
|
+
var toastVariant = {
|
|
12377
|
+
debug: "info",
|
|
12378
|
+
info: "success",
|
|
12379
|
+
warn: "warning",
|
|
12380
|
+
error: "error"
|
|
12381
|
+
};
|
|
12382
|
+
function createLogger(service, client) {
|
|
12383
|
+
return (msg, extra, level = "info") => {
|
|
12384
|
+
client.app.log({
|
|
12385
|
+
body: {
|
|
12386
|
+
service,
|
|
12387
|
+
level,
|
|
12388
|
+
message: msg,
|
|
12389
|
+
...extra !== undefined ? { extra } : {}
|
|
12390
|
+
}
|
|
12391
|
+
}).catch((e) => {
|
|
12392
|
+
console.error(`[${service}] Failed to send log: ${e}`);
|
|
12393
|
+
});
|
|
12394
|
+
if (process.env.CONTEXT_DROPPER_TOAST_LOGS) {
|
|
12395
|
+
client.tui.showToast({
|
|
12396
|
+
body: {
|
|
12397
|
+
title: `[${service}] ${level.toUpperCase()}`,
|
|
12398
|
+
message: msg,
|
|
12399
|
+
variant: toastVariant[level],
|
|
12400
|
+
duration: 4000
|
|
12401
|
+
}
|
|
12402
|
+
}).catch((e) => {
|
|
12403
|
+
console.error(`[${service}] Failed to show toast: ${e}`);
|
|
12404
|
+
});
|
|
12405
|
+
}
|
|
12406
|
+
};
|
|
12407
|
+
}
|
|
12408
|
+
|
|
12409
|
+
// src/message-handler.ts
|
|
12410
|
+
function compilePattern(pattern) {
|
|
12411
|
+
const paramNames = [];
|
|
12412
|
+
const paramTypes = [];
|
|
12413
|
+
const regexSource = pattern.replace(/<(string|number):([^>]+)>/g, (_, type, name) => {
|
|
12414
|
+
paramNames.push(name);
|
|
12415
|
+
paramTypes.push(type);
|
|
12416
|
+
return type === "number" ? "(-?\\d+(?:\\.\\d+)?)" : "(.+?)";
|
|
12417
|
+
});
|
|
12418
|
+
return {
|
|
12419
|
+
regex: new RegExp(`^${regexSource}$`, "is"),
|
|
12420
|
+
paramNames,
|
|
12421
|
+
paramTypes
|
|
12422
|
+
};
|
|
12423
|
+
}
|
|
12424
|
+
|
|
12425
|
+
class MessageHandler {
|
|
12426
|
+
routes = [];
|
|
12427
|
+
use(pattern, handler) {
|
|
12428
|
+
const { regex, paramNames, paramTypes } = compilePattern(pattern);
|
|
12429
|
+
this.routes.push({ regex, paramNames, paramTypes, handler });
|
|
12430
|
+
return this;
|
|
12431
|
+
}
|
|
12432
|
+
get handle() {
|
|
12433
|
+
return async (input, output) => {
|
|
12434
|
+
const sessionId = input.sessionID;
|
|
12435
|
+
const messageId = output.message?.id;
|
|
12436
|
+
for (const part of output.parts) {
|
|
12437
|
+
if (part.type !== "text")
|
|
12438
|
+
continue;
|
|
12439
|
+
const text = part.text.trim();
|
|
12440
|
+
for (const route of this.routes) {
|
|
12441
|
+
const match = text.match(route.regex);
|
|
12442
|
+
if (!match)
|
|
12443
|
+
continue;
|
|
12444
|
+
const params = {};
|
|
12445
|
+
route.paramNames.forEach((name, i) => {
|
|
12446
|
+
const raw = match[i + 1];
|
|
12447
|
+
params[name] = route.paramTypes[i] === "number" ? Number(raw) : raw;
|
|
12448
|
+
});
|
|
12449
|
+
const result = await route.handler(params, {
|
|
12450
|
+
sessionId,
|
|
12451
|
+
messageId,
|
|
12452
|
+
input,
|
|
12453
|
+
output
|
|
12454
|
+
});
|
|
12455
|
+
if (typeof result === "string") {
|
|
12456
|
+
part.text = result;
|
|
12457
|
+
}
|
|
12458
|
+
break;
|
|
12459
|
+
}
|
|
12460
|
+
}
|
|
12461
|
+
};
|
|
12462
|
+
}
|
|
12463
|
+
}
|
|
12464
|
+
|
|
12465
|
+
// src/dropper.ts
|
|
12466
|
+
import path from "node:path";
|
|
12467
|
+
|
|
12468
|
+
class Dropper {
|
|
12469
|
+
filesetName;
|
|
12470
|
+
dropperName;
|
|
12471
|
+
log;
|
|
12472
|
+
dropperService;
|
|
12473
|
+
dataDir;
|
|
12474
|
+
constructor(cwd, filesetName, dropperName, log, dropperService) {
|
|
12475
|
+
this.filesetName = filesetName;
|
|
12476
|
+
this.dropperName = dropperName;
|
|
12477
|
+
this.log = log;
|
|
12478
|
+
this.dropperService = dropperService;
|
|
12479
|
+
this.dataDir = path.resolve(cwd, ".context-dropper");
|
|
12480
|
+
}
|
|
12481
|
+
async create() {
|
|
12482
|
+
this.log(`Creating dropper`, {
|
|
12483
|
+
dropperName: this.dropperName,
|
|
12484
|
+
filesetName: this.filesetName
|
|
12485
|
+
});
|
|
12486
|
+
try {
|
|
12487
|
+
await this.dropperService.remove({
|
|
12488
|
+
dataDir: this.dataDir,
|
|
12489
|
+
dropperName: this.dropperName
|
|
12490
|
+
});
|
|
12491
|
+
} catch (e) {
|
|
12492
|
+
if (e.message && e.message.includes("not found")) {} else {
|
|
12493
|
+
throw e;
|
|
12494
|
+
}
|
|
12495
|
+
}
|
|
12496
|
+
await this.dropperService.create({
|
|
12497
|
+
dataDir: this.dataDir,
|
|
12498
|
+
filesetName: this.filesetName,
|
|
12499
|
+
dropperName: this.dropperName
|
|
12500
|
+
});
|
|
12501
|
+
}
|
|
12502
|
+
async tagProcessed() {
|
|
12503
|
+
this.log(`Tagging current file as 'processed'`, {
|
|
12504
|
+
dropperName: this.dropperName
|
|
12505
|
+
});
|
|
12506
|
+
await this.dropperService.tag({
|
|
12507
|
+
dataDir: this.dataDir,
|
|
12508
|
+
dropperName: this.dropperName,
|
|
12509
|
+
tags: ["processed"]
|
|
12510
|
+
});
|
|
12511
|
+
}
|
|
12512
|
+
async isDone() {
|
|
12513
|
+
this.log(`Checking if done`, { dropperName: this.dropperName });
|
|
12514
|
+
try {
|
|
12515
|
+
await this.dropperService.isDone({
|
|
12516
|
+
dataDir: this.dataDir,
|
|
12517
|
+
dropperName: this.dropperName
|
|
12518
|
+
});
|
|
12519
|
+
return true;
|
|
12520
|
+
} catch (e) {
|
|
12521
|
+
return false;
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
12524
|
+
async nextFile() {
|
|
12525
|
+
this.log(`Advancing to next file`, { dropperName: this.dropperName });
|
|
12526
|
+
await this.dropperService.next({
|
|
12527
|
+
dataDir: this.dataDir,
|
|
12528
|
+
dropperName: this.dropperName
|
|
12529
|
+
});
|
|
12530
|
+
}
|
|
12531
|
+
async getCurrentFile() {
|
|
12532
|
+
const dump = await this.dropperService.dump({
|
|
12533
|
+
dataDir: this.dataDir,
|
|
12534
|
+
dropperName: this.dropperName
|
|
12535
|
+
});
|
|
12536
|
+
const index = dump.pointer.currentIndex;
|
|
12537
|
+
if (index === null) {
|
|
12538
|
+
throw new Error("No current file found");
|
|
12539
|
+
}
|
|
12540
|
+
const filePath = dump.entries[index]?.path;
|
|
12541
|
+
if (!filePath) {
|
|
12542
|
+
throw new Error("No current file found");
|
|
12543
|
+
}
|
|
12544
|
+
let fileContent = "";
|
|
12545
|
+
try {
|
|
12546
|
+
fileContent = await this.dropperService.show({
|
|
12547
|
+
dataDir: this.dataDir,
|
|
12548
|
+
dropperName: this.dropperName
|
|
12549
|
+
});
|
|
12550
|
+
} catch (e) {
|
|
12551
|
+
fileContent = `Error reading file: ${e.message}`;
|
|
12552
|
+
}
|
|
12553
|
+
return { path: filePath, content: fileContent };
|
|
12554
|
+
}
|
|
12555
|
+
}
|
|
12556
|
+
|
|
12379
12557
|
// src/session.ts
|
|
12380
|
-
class
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12558
|
+
class Session {
|
|
12559
|
+
options;
|
|
12560
|
+
log;
|
|
12561
|
+
dropperService;
|
|
12562
|
+
dropper;
|
|
12563
|
+
#pruneMessageId;
|
|
12564
|
+
constructor(options, log, dropperService) {
|
|
12565
|
+
this.options = options;
|
|
12566
|
+
this.log = log;
|
|
12567
|
+
this.dropperService = dropperService;
|
|
12568
|
+
this.dropper = new Dropper(options.cwd, options.filesetName, `opencode-${options.filesetName}-${options.sessionId}`, log, dropperService);
|
|
12569
|
+
}
|
|
12570
|
+
set pruneMessageId(messageId) {
|
|
12571
|
+
this.log(`Prune anchor set`, {
|
|
12572
|
+
sessionId: this.options.sessionId,
|
|
12573
|
+
messageId
|
|
12574
|
+
});
|
|
12575
|
+
this.#pruneMessageId = messageId;
|
|
12576
|
+
}
|
|
12577
|
+
get pruneMessageId() {
|
|
12578
|
+
return this.#pruneMessageId;
|
|
12579
|
+
}
|
|
12580
|
+
pruneMessages(messages) {
|
|
12581
|
+
if (!messages || messages.length === 0)
|
|
12582
|
+
return 0;
|
|
12583
|
+
if (!this.pruneMessageId)
|
|
12584
|
+
return 0;
|
|
12585
|
+
const totalBefore = messages.length;
|
|
12586
|
+
const index = messages.findIndex((m) => m.info && m.info.id === this.pruneMessageId);
|
|
12587
|
+
if (index !== -1) {
|
|
12588
|
+
const assistantMessage = messages[index];
|
|
12589
|
+
if (assistantMessage) {
|
|
12590
|
+
if (assistantMessage.role === "assistant" || assistantMessage.info?.role === "assistant") {
|
|
12591
|
+
if (typeof assistantMessage.content === "string") {
|
|
12592
|
+
assistantMessage.content = "";
|
|
12593
|
+
}
|
|
12594
|
+
if (Array.isArray(assistantMessage.parts)) {
|
|
12595
|
+
assistantMessage.parts = assistantMessage.parts.filter((p) => p.type !== "text" && p.type !== "reasoning");
|
|
12596
|
+
}
|
|
12597
|
+
if (Array.isArray(assistantMessage.content)) {
|
|
12598
|
+
assistantMessage.content = assistantMessage.content.filter((p) => p.type !== "text" && p.type !== "reasoning");
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
12602
|
+
messages.splice(0, index);
|
|
12603
|
+
this.log(`Deep context prune completed`, {
|
|
12604
|
+
sessionId: this.options.sessionId,
|
|
12605
|
+
removed: index,
|
|
12606
|
+
totalBefore,
|
|
12607
|
+
remaining: messages.length
|
|
12608
|
+
});
|
|
12609
|
+
return index;
|
|
12610
|
+
}
|
|
12611
|
+
return 0;
|
|
12385
12612
|
}
|
|
12386
|
-
|
|
12387
|
-
|
|
12613
|
+
async initDropper() {
|
|
12614
|
+
await this.dropper.create();
|
|
12388
12615
|
}
|
|
12389
|
-
|
|
12390
|
-
this.
|
|
12391
|
-
this.sessionPruneMap.delete(sessionId);
|
|
12616
|
+
async getCurrentFile() {
|
|
12617
|
+
return this.dropper.getCurrentFile();
|
|
12392
12618
|
}
|
|
12393
|
-
|
|
12394
|
-
this.
|
|
12619
|
+
async getPrompt() {
|
|
12620
|
+
const file2 = await this.getCurrentFile();
|
|
12621
|
+
return `<context_dropper_session id="${this.dropperName}">
|
|
12622
|
+
` + `You are currently processing a file injected by Context-Dropper. ` + `**DO NOT use any tools to read this file again.** The complete file content is already provided below.
|
|
12623
|
+
|
|
12624
|
+
` + `<instructions>
|
|
12625
|
+
` + `${this.options.instructions}
|
|
12626
|
+
` + `</instructions>
|
|
12627
|
+
|
|
12628
|
+
` + `<file path="${file2.path}">
|
|
12629
|
+
` + `${file2.content}
|
|
12630
|
+
` + `</file>
|
|
12631
|
+
|
|
12632
|
+
` + `**IMPORTANT:** When you are completely finished fulfilling the instructions for this specific file, ` + `you MUST call the \`context-dropper_next\` tool to get the next file. Do not stop until all files are processed.
|
|
12633
|
+
` + `</context_dropper_session>`;
|
|
12395
12634
|
}
|
|
12396
|
-
|
|
12397
|
-
return this.
|
|
12635
|
+
async tagProcessed() {
|
|
12636
|
+
return this.dropper.tagProcessed();
|
|
12637
|
+
}
|
|
12638
|
+
async isDone() {
|
|
12639
|
+
return this.dropper.isDone();
|
|
12640
|
+
}
|
|
12641
|
+
async nextFile() {
|
|
12642
|
+
return this.dropper.nextFile();
|
|
12643
|
+
}
|
|
12644
|
+
get dropperName() {
|
|
12645
|
+
return this.dropper.dropperName;
|
|
12398
12646
|
}
|
|
12399
12647
|
}
|
|
12400
12648
|
|
|
12401
|
-
|
|
12402
|
-
|
|
12649
|
+
class SessionManager {
|
|
12650
|
+
dropperService;
|
|
12651
|
+
sessions = new Map;
|
|
12652
|
+
log;
|
|
12653
|
+
cwd;
|
|
12654
|
+
constructor(cwd, log, dropperService) {
|
|
12655
|
+
this.dropperService = dropperService;
|
|
12656
|
+
this.cwd = cwd;
|
|
12657
|
+
this.log = log;
|
|
12658
|
+
}
|
|
12659
|
+
async createSession(sessionId, filesetName, instructions) {
|
|
12660
|
+
const session = new Session({ sessionId, filesetName, instructions, cwd: this.cwd }, this.log, this.dropperService);
|
|
12661
|
+
this.sessions.set(sessionId, session);
|
|
12662
|
+
await session.initDropper();
|
|
12663
|
+
return session;
|
|
12664
|
+
}
|
|
12665
|
+
getSession(sessionId) {
|
|
12666
|
+
return this.sessions.get(sessionId);
|
|
12667
|
+
}
|
|
12668
|
+
deleteSession(sessionId) {
|
|
12669
|
+
this.log(`Deleting session ${sessionId}`);
|
|
12670
|
+
this.sessions.delete(sessionId);
|
|
12671
|
+
}
|
|
12672
|
+
}
|
|
12403
12673
|
|
|
12404
12674
|
// ../src/dropper/service.ts
|
|
12405
12675
|
import {
|
|
@@ -12411,7 +12681,7 @@ import {
|
|
|
12411
12681
|
writeFile,
|
|
12412
12682
|
access
|
|
12413
12683
|
} from "node:fs/promises";
|
|
12414
|
-
import
|
|
12684
|
+
import path2 from "node:path";
|
|
12415
12685
|
|
|
12416
12686
|
// ../src/file-utils/errors.ts
|
|
12417
12687
|
class AppError extends Error {
|
|
@@ -12481,16 +12751,16 @@ var defaultDropperServiceDeps = {
|
|
|
12481
12751
|
}
|
|
12482
12752
|
};
|
|
12483
12753
|
function getFilesetsDirectory(dataDir) {
|
|
12484
|
-
return
|
|
12754
|
+
return path2.join(dataDir, "filesets");
|
|
12485
12755
|
}
|
|
12486
12756
|
function getDroppersDirectory(dataDir) {
|
|
12487
|
-
return
|
|
12757
|
+
return path2.join(dataDir, "droppers");
|
|
12488
12758
|
}
|
|
12489
12759
|
function getFilesetFilePath(dataDir, filesetName) {
|
|
12490
|
-
return
|
|
12760
|
+
return path2.join(getFilesetsDirectory(dataDir), `${filesetName}.txt`);
|
|
12491
12761
|
}
|
|
12492
12762
|
function getDropperFilePath(dataDir, dropperName) {
|
|
12493
|
-
return
|
|
12763
|
+
return path2.join(getDroppersDirectory(dataDir), `${dropperName}.json`);
|
|
12494
12764
|
}
|
|
12495
12765
|
function parseFilesetContent(content) {
|
|
12496
12766
|
return content.split(/\r?\n/g).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -12751,408 +13021,109 @@ ${untaggedPaths.join(`
|
|
|
12751
13021
|
}
|
|
12752
13022
|
}
|
|
12753
13023
|
|
|
12754
|
-
//
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
await access2(filePath);
|
|
12775
|
-
return true;
|
|
12776
|
-
} catch {
|
|
12777
|
-
return false;
|
|
12778
|
-
}
|
|
12779
|
-
},
|
|
12780
|
-
writeTextFileFn: async (filePath, content) => {
|
|
12781
|
-
await writeFile2(filePath, content, "utf-8");
|
|
12782
|
-
},
|
|
12783
|
-
readTextFileFn: async (filePath) => {
|
|
12784
|
-
return await readFile2(filePath, "utf-8");
|
|
12785
|
-
},
|
|
12786
|
-
listFilesFn: async (directoryPath) => {
|
|
12787
|
-
try {
|
|
12788
|
-
const names = await readdir2(directoryPath);
|
|
12789
|
-
return names.map((name) => path2.join(directoryPath, name));
|
|
12790
|
-
} catch (error45) {
|
|
12791
|
-
if (isNotFoundError2(error45)) {
|
|
12792
|
-
return [];
|
|
12793
|
-
}
|
|
12794
|
-
throw error45;
|
|
12795
|
-
}
|
|
12796
|
-
},
|
|
12797
|
-
deleteFileFn: async (filePath) => {
|
|
12798
|
-
await rm2(filePath);
|
|
12799
|
-
},
|
|
12800
|
-
statFileFn: async (filePath) => {
|
|
12801
|
-
const fileStat = await stat2(filePath);
|
|
12802
|
-
return {
|
|
12803
|
-
createdAt: fileStat.ctime.toISOString(),
|
|
12804
|
-
updatedAt: fileStat.mtime.toISOString()
|
|
12805
|
-
};
|
|
12806
|
-
}
|
|
12807
|
-
};
|
|
12808
|
-
function getFilesetsDirectory2(dataDir) {
|
|
12809
|
-
return path2.join(dataDir, "filesets");
|
|
12810
|
-
}
|
|
12811
|
-
function getDroppersDirectory2(dataDir) {
|
|
12812
|
-
return path2.join(dataDir, "droppers");
|
|
12813
|
-
}
|
|
12814
|
-
function getFilesetFilePath2(dataDir, filesetName) {
|
|
12815
|
-
return path2.join(getFilesetsDirectory2(dataDir), `${filesetName}.txt`);
|
|
12816
|
-
}
|
|
12817
|
-
function parseFilesetContent2(content) {
|
|
12818
|
-
return content.split(/\r?\n/g).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
12819
|
-
}
|
|
12820
|
-
function parseDropperReference(rawJson, dropperPath) {
|
|
12821
|
-
let parsed;
|
|
12822
|
-
try {
|
|
12823
|
-
parsed = JSON.parse(rawJson);
|
|
12824
|
-
} catch {
|
|
12825
|
-
throw new AppError(`Invalid dropper metadata: ${dropperPath}`);
|
|
12826
|
-
}
|
|
12827
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.fileset !== "string") {
|
|
12828
|
-
throw new AppError(`Invalid dropper metadata: ${dropperPath}`);
|
|
12829
|
-
}
|
|
12830
|
-
return {
|
|
12831
|
-
fileset: parsed.fileset
|
|
12832
|
-
};
|
|
12833
|
-
}
|
|
12834
|
-
|
|
12835
|
-
class DefaultFilesetService {
|
|
12836
|
-
deps;
|
|
12837
|
-
constructor(deps = defaultFilesetServiceDeps) {
|
|
12838
|
-
this.deps = deps;
|
|
12839
|
-
}
|
|
12840
|
-
async importFromList(input) {
|
|
12841
|
-
const filesetsDirectory = getFilesetsDirectory2(input.dataDir);
|
|
12842
|
-
const filesetFilePath = getFilesetFilePath2(input.dataDir, input.name);
|
|
12843
|
-
await this.deps.ensureDirFn(filesetsDirectory);
|
|
12844
|
-
if (await this.deps.fileExistsFn(filesetFilePath)) {
|
|
12845
|
-
throw new AppError(`Fileset already exists: ${input.name}`);
|
|
12846
|
-
}
|
|
12847
|
-
const content = input.normalizedFilePaths.length === 0 ? "" : `${input.normalizedFilePaths.join(`
|
|
12848
|
-
`)}
|
|
12849
|
-
`;
|
|
12850
|
-
await this.deps.writeTextFileFn(filesetFilePath, content);
|
|
12851
|
-
}
|
|
12852
|
-
async list(input) {
|
|
12853
|
-
const filesetPaths = (await this.deps.listFilesFn(getFilesetsDirectory2(input.dataDir))).filter((filePath) => filePath.endsWith(".txt")).sort((a, b) => path2.basename(a).localeCompare(path2.basename(b)));
|
|
12854
|
-
const records = [];
|
|
12855
|
-
for (const filesetPath of filesetPaths) {
|
|
12856
|
-
const name = path2.basename(filesetPath, ".txt");
|
|
12857
|
-
records.push(await this.show({
|
|
12858
|
-
dataDir: input.dataDir,
|
|
12859
|
-
name
|
|
12860
|
-
}));
|
|
12861
|
-
}
|
|
12862
|
-
return records;
|
|
12863
|
-
}
|
|
12864
|
-
async show(input) {
|
|
12865
|
-
const filesetFilePath = getFilesetFilePath2(input.dataDir, input.name);
|
|
12866
|
-
if (!await this.deps.fileExistsFn(filesetFilePath)) {
|
|
12867
|
-
throw new AppError(`Fileset not found: ${input.name}`);
|
|
12868
|
-
}
|
|
12869
|
-
const [content, fileStat] = await Promise.all([
|
|
12870
|
-
this.deps.readTextFileFn(filesetFilePath),
|
|
12871
|
-
this.deps.statFileFn(filesetFilePath)
|
|
12872
|
-
]);
|
|
12873
|
-
return {
|
|
12874
|
-
name: input.name,
|
|
12875
|
-
files: parseFilesetContent2(content),
|
|
12876
|
-
createdAt: fileStat.createdAt,
|
|
12877
|
-
updatedAt: fileStat.updatedAt
|
|
12878
|
-
};
|
|
12879
|
-
}
|
|
12880
|
-
async remove(input) {
|
|
12881
|
-
const filesetFilePath = getFilesetFilePath2(input.dataDir, input.name);
|
|
12882
|
-
if (!await this.deps.fileExistsFn(filesetFilePath)) {
|
|
12883
|
-
throw new AppError(`Fileset not found: ${input.name}`);
|
|
12884
|
-
}
|
|
12885
|
-
const dependentDroppers = [];
|
|
12886
|
-
const dropperPaths = (await this.deps.listFilesFn(getDroppersDirectory2(input.dataDir))).filter((dropperPath) => dropperPath.endsWith(".json"));
|
|
12887
|
-
for (const dropperPath of dropperPaths) {
|
|
12888
|
-
const dropperContent = await this.deps.readTextFileFn(dropperPath);
|
|
12889
|
-
const reference = parseDropperReference(dropperContent, dropperPath);
|
|
12890
|
-
if (reference.fileset === input.name) {
|
|
12891
|
-
dependentDroppers.push(path2.basename(dropperPath, ".json"));
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
if (dependentDroppers.length > 0) {
|
|
12895
|
-
throw new AppError(`Cannot remove fileset ${input.name}: referenced by droppers: ${dependentDroppers.join(", ")}`);
|
|
12896
|
-
}
|
|
12897
|
-
await this.deps.deleteFileFn(filesetFilePath);
|
|
12898
|
-
}
|
|
12899
|
-
}
|
|
12900
|
-
|
|
12901
|
-
// src/toolkit.ts
|
|
12902
|
-
class Toolkit {
|
|
12903
|
-
dropperService;
|
|
12904
|
-
filesetService;
|
|
12905
|
-
dataDir;
|
|
12906
|
-
constructor(cwd, dropperService, filesetService) {
|
|
12907
|
-
this.dropperService = dropperService ?? new DefaultDropperService;
|
|
12908
|
-
this.filesetService = filesetService ?? new DefaultFilesetService;
|
|
12909
|
-
this.dataDir = path3.resolve(cwd, ".context-dropper");
|
|
12910
|
-
}
|
|
12911
|
-
async createDropper(filesetName, dropperName) {
|
|
12912
|
-
await this.dropperService.create({
|
|
12913
|
-
dataDir: this.dataDir,
|
|
12914
|
-
filesetName,
|
|
12915
|
-
dropperName
|
|
12916
|
-
});
|
|
12917
|
-
}
|
|
12918
|
-
async removeDropper(dropperName) {
|
|
12919
|
-
try {
|
|
12920
|
-
await this.dropperService.remove({
|
|
12921
|
-
dataDir: this.dataDir,
|
|
12922
|
-
dropperName
|
|
12923
|
-
});
|
|
12924
|
-
} catch (e) {
|
|
12925
|
-
if (e.message && e.message.includes("not found")) {
|
|
12926
|
-
return;
|
|
13024
|
+
// src/index.ts
|
|
13025
|
+
class Program {
|
|
13026
|
+
sessionManager;
|
|
13027
|
+
log;
|
|
13028
|
+
messageHandler;
|
|
13029
|
+
constructor(sessionManager, log) {
|
|
13030
|
+
this.sessionManager = sessionManager;
|
|
13031
|
+
this.log = log;
|
|
13032
|
+
this.messageHandler = new MessageHandler;
|
|
13033
|
+
this.messageHandler.use(":context-dropper <string:filesetName> <string:instructions>", async ({ filesetName, instructions }, { sessionId, messageId, input }) => {
|
|
13034
|
+
this.log(`Processing :context-dropper command`, { sessionId });
|
|
13035
|
+
const session = await this.sessionManager.createSession(sessionId, String(filesetName), String(instructions));
|
|
13036
|
+
try {
|
|
13037
|
+
const prompt = await session.getPrompt();
|
|
13038
|
+
if (messageId)
|
|
13039
|
+
session.pruneMessageId = messageId;
|
|
13040
|
+
return prompt;
|
|
13041
|
+
} catch (error45) {
|
|
13042
|
+
this.log(`Error handling :context-dropper command`, { error: error45.message }, "error");
|
|
13043
|
+
return `Error handling :context-dropper: ${error45.message}`;
|
|
12927
13044
|
}
|
|
12928
|
-
throw e;
|
|
12929
|
-
}
|
|
12930
|
-
}
|
|
12931
|
-
async tagProcessed(dropperName) {
|
|
12932
|
-
await this.dropperService.tag({
|
|
12933
|
-
dataDir: this.dataDir,
|
|
12934
|
-
dropperName,
|
|
12935
|
-
tags: ["processed"]
|
|
12936
13045
|
});
|
|
12937
13046
|
}
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
return
|
|
12947
|
-
|
|
12948
|
-
}
|
|
12949
|
-
async nextFile(dropperName) {
|
|
12950
|
-
await this.dropperService.next({
|
|
12951
|
-
dataDir: this.dataDir,
|
|
12952
|
-
dropperName
|
|
12953
|
-
});
|
|
12954
|
-
}
|
|
12955
|
-
async getFilePrompt(dropperName, instructions, isNext = false) {
|
|
12956
|
-
const dump = await this.dropperService.dump({
|
|
12957
|
-
dataDir: this.dataDir,
|
|
12958
|
-
dropperName
|
|
12959
|
-
});
|
|
12960
|
-
const index = dump.pointer.currentIndex;
|
|
12961
|
-
const filePath = index !== null && index >= 0 && index < dump.entries.length ? dump.entries[index]?.path ?? "Unknown File" : "Unknown File";
|
|
12962
|
-
let fileContent = "";
|
|
12963
|
-
try {
|
|
12964
|
-
fileContent = await this.dropperService.show({
|
|
12965
|
-
dataDir: this.dataDir,
|
|
12966
|
-
dropperName
|
|
12967
|
-
});
|
|
12968
|
-
} catch (e) {
|
|
12969
|
-
fileContent = `Error reading file: ${e.message}`;
|
|
12970
|
-
}
|
|
12971
|
-
const header = isNext ? `[Context-Dropper: Advanced to next file]` : `Context-dropper task initialized for session '${dropperName}'.`;
|
|
12972
|
-
return `${header}
|
|
12973
|
-
|
|
12974
|
-
` + `Instructions for this file:
|
|
12975
|
-
${instructions}
|
|
12976
|
-
|
|
12977
|
-
` + `File: ${filePath}
|
|
12978
|
-
|
|
12979
|
-
` + `File Content:
|
|
12980
|
-
${fileContent}
|
|
12981
|
-
|
|
12982
|
-
` + `When you are done with this file, DO NOT just say "DONE". You MUST call the 'context-dropper.next' tool to automatically fetch the next file.`;
|
|
12983
|
-
}
|
|
12984
|
-
}
|
|
12985
|
-
|
|
12986
|
-
// 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);
|
|
13047
|
+
getActiveSession(messages) {
|
|
13048
|
+
if (!messages || messages.length === 0)
|
|
13049
|
+
return;
|
|
13050
|
+
const firstMessage = messages[0];
|
|
13051
|
+
if (!firstMessage || !firstMessage.info)
|
|
13052
|
+
return;
|
|
13053
|
+
const sessionId = firstMessage.info.sessionID;
|
|
13054
|
+
if (!sessionId)
|
|
13055
|
+
return;
|
|
13056
|
+
return this.sessionManager.getSession(sessionId);
|
|
13015
13057
|
}
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
var ContextDropperPlugin = async (ctx) => {
|
|
13020
|
-
const version2 = getPackageVersion();
|
|
13021
|
-
log(`Plugin initializing! Version: ${version2}`);
|
|
13022
|
-
setTimeout(() => {
|
|
13023
|
-
ctx.client.tui.showToast({
|
|
13024
|
-
body: {
|
|
13025
|
-
title: `Context Dropper v${version2}`,
|
|
13026
|
-
message: "Plugin is active! Type '/drop <filesetName> <instructions>' to start.",
|
|
13027
|
-
variant: "success",
|
|
13028
|
-
duration: 5000
|
|
13029
|
-
}
|
|
13030
|
-
}).catch((e) => log("Failed to show toast", e));
|
|
13031
|
-
log("Initialization toast sent");
|
|
13032
|
-
}, 1000);
|
|
13033
|
-
return {
|
|
13034
|
-
tool: {
|
|
13035
|
-
"context-dropper": tool({
|
|
13058
|
+
get tools() {
|
|
13059
|
+
return {
|
|
13060
|
+
"context-dropper_init": tool({
|
|
13036
13061
|
description: "Initializes the context-dropper task.",
|
|
13037
13062
|
args: {
|
|
13038
13063
|
filesetName: tool.schema.string().describe("The name of the fileset to process"),
|
|
13039
13064
|
instructions: tool.schema.string().describe("Instructions on what to do with the files")
|
|
13040
13065
|
},
|
|
13041
13066
|
execute: async (args, context) => {
|
|
13042
|
-
const
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
instructions: args.instructions
|
|
13046
|
-
});
|
|
13067
|
+
const { filesetName, instructions } = args;
|
|
13068
|
+
const sessionId = context.sessionID;
|
|
13069
|
+
const session = await this.sessionManager.createSession(sessionId, filesetName, instructions);
|
|
13047
13070
|
try {
|
|
13048
|
-
|
|
13049
|
-
await toolkit.removeDropper(dropperName);
|
|
13050
|
-
log(`Creating dropper ${dropperName} from fileset ${args.filesetName}`);
|
|
13051
|
-
await toolkit.createDropper(args.filesetName, dropperName);
|
|
13052
|
-
return await toolkit.getFilePrompt(dropperName, args.instructions, false);
|
|
13071
|
+
return await session.getPrompt();
|
|
13053
13072
|
} catch (error45) {
|
|
13054
|
-
log(`Error in
|
|
13055
|
-
return `Error
|
|
13073
|
+
this.log(`Error in context-dropper_init`, { error: error45.message }, "error");
|
|
13074
|
+
return `Error in context-dropper_init: ${error45.message}`;
|
|
13056
13075
|
}
|
|
13057
13076
|
}
|
|
13058
13077
|
}),
|
|
13059
|
-
"context-
|
|
13078
|
+
"context-dropper_next": tool({
|
|
13060
13079
|
description: "Call this tool when you have finished processing the current file to save state, prune context, and fetch the next file.",
|
|
13061
13080
|
args: {},
|
|
13062
13081
|
execute: async (args, context) => {
|
|
13063
13082
|
const sessionId = context.sessionID;
|
|
13064
|
-
const
|
|
13065
|
-
if (!
|
|
13083
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
13084
|
+
if (!session) {
|
|
13066
13085
|
return "No active context-dropper session found. Please initialize one first.";
|
|
13067
13086
|
}
|
|
13068
13087
|
try {
|
|
13069
|
-
|
|
13070
|
-
await
|
|
13071
|
-
log(`Checking if ${state.dropperName} is done`);
|
|
13072
|
-
const isDone = await toolkit.isDone(state.dropperName);
|
|
13088
|
+
await session.tagProcessed();
|
|
13089
|
+
const isDone = await session.isDone();
|
|
13073
13090
|
if (isDone) {
|
|
13074
|
-
log(`Session
|
|
13075
|
-
|
|
13091
|
+
this.log(`Session completed`, {
|
|
13092
|
+
dropperName: session.dropperName
|
|
13093
|
+
});
|
|
13094
|
+
this.sessionManager.deleteSession(sessionId);
|
|
13076
13095
|
return `[Context-Dropper: All files have been processed. Task complete.]`;
|
|
13077
13096
|
}
|
|
13078
|
-
|
|
13079
|
-
await
|
|
13080
|
-
|
|
13081
|
-
sessionManager.setPruneMessageId(sessionId, context.messageID);
|
|
13097
|
+
await session.nextFile();
|
|
13098
|
+
const prompt = await session.getPrompt();
|
|
13099
|
+
session.pruneMessageId = context.messageID;
|
|
13082
13100
|
return prompt;
|
|
13083
13101
|
} catch (error45) {
|
|
13084
|
-
log(`Error
|
|
13085
|
-
return `[
|
|
13102
|
+
this.log(`Error in context-dropper_next`, { error: error45.message }, "error");
|
|
13103
|
+
return `[context-dropper_next error: ${error45.message}]`;
|
|
13086
13104
|
}
|
|
13087
13105
|
}
|
|
13088
13106
|
})
|
|
13089
|
-
}
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
const match = originalText.match(/^\/drop\s+([^\s]+)\s+(.+)$/is);
|
|
13099
|
-
if (match) {
|
|
13100
|
-
const filesetName = match[1] || "";
|
|
13101
|
-
const instructions = match[2] || "";
|
|
13102
|
-
const dropperName = `session-${sessionId}`;
|
|
13103
|
-
sessionManager.setSession(sessionId, {
|
|
13104
|
-
dropperName,
|
|
13105
|
-
instructions
|
|
13106
|
-
});
|
|
13107
|
-
try {
|
|
13108
|
-
log(`Removing existing dropper ${dropperName}`);
|
|
13109
|
-
await toolkit.removeDropper(dropperName);
|
|
13110
|
-
log(`Creating dropper ${dropperName} from fileset ${filesetName}`);
|
|
13111
|
-
await toolkit.createDropper(filesetName, dropperName);
|
|
13112
|
-
const prompt = await toolkit.getFilePrompt(dropperName, instructions, false);
|
|
13113
|
-
part.text = prompt;
|
|
13114
|
-
if (output.message?.id) {
|
|
13115
|
-
sessionManager.setPruneMessageId(sessionId, output.message.id);
|
|
13116
|
-
}
|
|
13117
|
-
} catch (error45) {
|
|
13118
|
-
log(`Error starting context-dropper via /drop: ${error45.message}`);
|
|
13119
|
-
part.text = `Error starting context-dropper: ${error45.message}`;
|
|
13120
|
-
}
|
|
13121
|
-
} else {
|
|
13122
|
-
part.text = "Invalid command format. Please use: `/drop <filesetName> <instructions>`";
|
|
13123
|
-
}
|
|
13124
|
-
continue;
|
|
13125
|
-
}
|
|
13126
|
-
if (text.includes("stop context-dropper") || text.includes("stop dropping")) {
|
|
13127
|
-
sessionManager.deleteSession(sessionId);
|
|
13128
|
-
part.text += `
|
|
13129
|
-
|
|
13130
|
-
[Context-Dropper: Process stopped manually by user. State cleared.]`;
|
|
13131
|
-
continue;
|
|
13132
|
-
}
|
|
13133
|
-
}
|
|
13134
|
-
}
|
|
13135
|
-
},
|
|
13136
|
-
"experimental.chat.messages.transform": async (input, output) => {
|
|
13137
|
-
if (!output.messages || output.messages.length === 0)
|
|
13138
|
-
return;
|
|
13139
|
-
const firstMessage = output.messages[0];
|
|
13140
|
-
if (!firstMessage || !firstMessage.info)
|
|
13141
|
-
return;
|
|
13142
|
-
const sessionId = firstMessage.info.sessionID;
|
|
13143
|
-
if (!sessionId)
|
|
13144
|
-
return;
|
|
13145
|
-
const pruneStartId = sessionManager.getPruneMessageId(sessionId);
|
|
13146
|
-
if (pruneStartId) {
|
|
13147
|
-
const index = output.messages.findIndex((m) => m.info && m.info.id === pruneStartId);
|
|
13148
|
-
if (index !== -1) {
|
|
13149
|
-
output.messages.splice(0, index);
|
|
13150
|
-
}
|
|
13107
|
+
};
|
|
13108
|
+
}
|
|
13109
|
+
get plugin() {
|
|
13110
|
+
return {
|
|
13111
|
+
tool: this.tools,
|
|
13112
|
+
"chat.message": this.messageHandler.handle,
|
|
13113
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
13114
|
+
const activeSession = this.getActiveSession(output.messages);
|
|
13115
|
+
activeSession?.pruneMessages(output.messages);
|
|
13151
13116
|
}
|
|
13152
|
-
}
|
|
13153
|
-
}
|
|
13117
|
+
};
|
|
13118
|
+
}
|
|
13119
|
+
}
|
|
13120
|
+
var src_default = async (ctx) => {
|
|
13121
|
+
const log = createLogger("context-dropper", ctx.client);
|
|
13122
|
+
log(`Plugin initializing! Version: ${getPackageVersion()}`);
|
|
13123
|
+
const dropperService = new DefaultDropperService;
|
|
13124
|
+
const sessionManager = new SessionManager(ctx.worktree, log, dropperService);
|
|
13125
|
+
return new Program(sessionManager, log).plugin;
|
|
13154
13126
|
};
|
|
13155
|
-
var src_default = ContextDropperPlugin;
|
|
13156
13127
|
export {
|
|
13157
13128
|
src_default as default
|
|
13158
13129
|
};
|
package/package.json
CHANGED