mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4832b382b6 | |||
| 17258ce60f | |||
| 6fad91953c | |||
| 7b33b51c1e |
@@ -176,6 +176,19 @@ The plugin exposes these tools to your OpenCode agent:
|
|||||||
- ✅ Pressure-aware compression
|
- ✅ Pressure-aware compression
|
||||||
- ✅ Zero configuration, works immediately
|
- ✅ Zero configuration, works immediately
|
||||||
|
|
||||||
|
## Does working-memory system increase token usage? It depends.
|
||||||
|
|
||||||
|
It depends on your workflow.
|
||||||
|
|
||||||
|
- 🧹 **Clean Slate user** (for example, using DCP and frequently restarting sessions)
|
||||||
|
- ⚠️ Yes, it might add slight overhead.
|
||||||
|
- Because you keep starting fresh, automated memory persistence does not get enough time to pay off.
|
||||||
|
|
||||||
|
- 🚀 **Long Haul user** (staying in one session until token limits/compaction hit)
|
||||||
|
- ✅ This plugin is a token saver.
|
||||||
|
- Without it, compaction can cause the agent to lose the goal, forget active files, or make wrong assumptions, which creates correction loops.
|
||||||
|
- By preserving high-value context (Goals, Progress, Active Files), the agent inherits its previous state quickly. The small memory-prompt cost avoids the larger cost of the agent getting lost.
|
||||||
|
|
||||||
## Configuration (Optional)
|
## Configuration (Optional)
|
||||||
|
|
||||||
The plugin works great with zero configuration. To customize behavior, modify the constants at the top of `index.ts`. See the [Configuration Guide](docs/configuration.md) for all tunable options.
|
The plugin works great with zero configuration. To customize behavior, modify the constants at the top of `index.ts`. See the [Configuration Guide](docs/configuration.md) for all tunable options.
|
||||||
|
|||||||
@@ -249,8 +249,7 @@ async function loadCoreMemory(
|
|||||||
try {
|
try {
|
||||||
const content = await readFile(path, "utf-8");
|
const content = await readFile(path, "utf-8");
|
||||||
return JSON.parse(content) as CoreMemory;
|
return JSON.parse(content) as CoreMemory;
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("Failed to load core memory:", error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,8 +355,7 @@ async function loadWorkingMemory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data as WorkingMemory;
|
return data as WorkingMemory;
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("Failed to load working memory:", error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1319,28 +1317,34 @@ async function loadModelPressureInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate total tokens by querying OpenCode's session database.
|
* Calculate total tokens by querying session messages via OpenCode SDK.
|
||||||
|
* Cross-platform compatible (no sqlite3 CLI dependency).
|
||||||
*/
|
*/
|
||||||
async function calculateTotalTokensFromDB(sessionID: string): Promise<number> {
|
async function calculateTotalTokensFromDB(
|
||||||
|
client: ReturnType<typeof import("@opencode-ai/sdk").createOpencodeClient>,
|
||||||
|
sessionID: string
|
||||||
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const { execSync } = await import("child_process");
|
const result = await client.session.messages({
|
||||||
const dbPath = join(process.env.HOME || "~", ".local/share/opencode/opencode.db");
|
path: { id: sessionID }
|
||||||
|
});
|
||||||
|
|
||||||
// Get tokens.total from most recent assistant message
|
if (!result.data) return 0;
|
||||||
const query = `
|
|
||||||
SELECT json_extract(data, '$.tokens.total') as total
|
|
||||||
FROM message
|
|
||||||
WHERE session_id = '${sessionID}'
|
|
||||||
AND json_extract(data, '$.role') = 'assistant'
|
|
||||||
AND json_extract(data, '$.tokens.total') IS NOT NULL
|
|
||||||
ORDER BY time_created DESC
|
|
||||||
LIMIT 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = execSync(`sqlite3 "${dbPath}" "${query}"`, { encoding: "utf-8" }).trim();
|
// Find the most recent assistant message with non-zero tokens.
|
||||||
return parseInt(result) || 0;
|
// API returns messages oldest-first, so we scan backwards.
|
||||||
|
for (let i = result.data.length - 1; i >= 0; i--) {
|
||||||
|
const { info } = result.data[i];
|
||||||
|
if (info.role !== "assistant") continue;
|
||||||
|
|
||||||
|
// Total = input + output + reasoning + cache.read
|
||||||
|
const total = info.tokens.input + info.tokens.output + info.tokens.reasoning + info.tokens.cache.read;
|
||||||
|
if (total > 0) return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[working-memory] Failed to query tokens from DB:", error);
|
// Silent — API call failed
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1523,7 +1527,7 @@ export default async function WorkingMemoryPlugin(
|
|||||||
|
|
||||||
// Calculate current token usage from DB and update pressure info.
|
// Calculate current token usage from DB and update pressure info.
|
||||||
if (model) {
|
if (model) {
|
||||||
const totalTokens = await calculateTotalTokensFromDB(sessionID);
|
const totalTokens = await calculateTotalTokensFromDB(client, sessionID);
|
||||||
|
|
||||||
// Calculate pressure with current model
|
// Calculate pressure with current model
|
||||||
const updatedPressure = calculateModelPressure(
|
const updatedPressure = calculateModelPressure(
|
||||||
@@ -1725,26 +1729,14 @@ export default async function WorkingMemoryPlugin(
|
|||||||
|
|
||||||
// Inject pending OpenCode todos into compaction context.
|
// Inject pending OpenCode todos into compaction context.
|
||||||
try {
|
try {
|
||||||
const { execSync } = await import("child_process");
|
const result = await client.session.todo({
|
||||||
const dbPath = join(process.env.HOME || "~", ".local/share/opencode/opencode.db");
|
path: { id: sessionID }
|
||||||
|
});
|
||||||
|
|
||||||
const query = `
|
if (result.data) {
|
||||||
SELECT content, status, priority
|
const todos = result.data
|
||||||
FROM todo
|
.filter(todo => todo.status !== "completed")
|
||||||
WHERE session_id = '${sessionID}'
|
.map(todo => `- [${todo.status}] ${todo.content} (${todo.priority})`);
|
||||||
AND status != 'completed'
|
|
||||||
ORDER BY position ASC;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = execSync(`sqlite3 "${dbPath}" "${query.replace(/\n/g, ' ')}"`, {
|
|
||||||
encoding: "utf-8"
|
|
||||||
}).trim();
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const todos = result.split('\n').map(line => {
|
|
||||||
const [content, status, priority] = line.split('|');
|
|
||||||
return `- [${status}] ${content} (${priority})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (todos.length > 0) {
|
if (todos.length > 0) {
|
||||||
output.context.push(
|
output.context.push(
|
||||||
@@ -1753,7 +1745,7 @@ export default async function WorkingMemoryPlugin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[working-memory] Failed to inject todos from DB:", error);
|
// Silent — API call failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform about preserved working memory
|
// Inform about preserved working memory
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opencode-working-memory",
|
"name": "opencode-working-memory",
|
||||||
"version": "1.1.0",
|
"version": "1.1.2",
|
||||||
"description": "Advanced four-tier memory architecture for OpenCode with intelligent pressure monitoring and auto-storage governance",
|
"description": "Advanced four-tier memory architecture for OpenCode with intelligent pressure monitoring and auto-storage governance",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user