mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4832b382b6 | |||
| 17258ce60f | |||
| 6fad91953c | |||
| 7b33b51c1e | |||
| 2edb643667 | |||
| d7302e531a |
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1122,7 +1120,7 @@ Recent session context (auto-managed, sorted by relevance):
|
|||||||
|
|
||||||
${sections.join("\n\n")}
|
${sections.join("\n\n")}
|
||||||
|
|
||||||
(${totalItems} items shown, updated: ${new Date(memory.updatedAt).toLocaleTimeString()})
|
(${totalItems} items shown)
|
||||||
</working_memory>
|
</working_memory>
|
||||||
`.trim();
|
`.trim();
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1463,18 +1467,9 @@ ${context.value || "[No project context set - add relevant file paths, conventio
|
|||||||
</context>
|
</context>
|
||||||
|
|
||||||
IMPORTANT: These blocks persist across conversation resets and compaction.
|
IMPORTANT: These blocks persist across conversation resets and compaction.
|
||||||
Update them regularly using core_memory_update tool when:
|
Update them regularly using core_memory_update tool. When blocks approach their character limits, compress or rephrase content.
|
||||||
- Goals change or new objectives are identified
|
|
||||||
- Significant progress is made or tasks are completed
|
|
||||||
- Important project context is discovered (file structures, patterns, conventions)
|
|
||||||
|
|
||||||
When memory blocks approach their character limits, compress or rephrase content.
|
To mark decisions for automatic capture into working memory, write inline: [Decision: chose X over Y because Z]
|
||||||
|
|
||||||
**Usage Discipline** (see Core Memory Usage Guidelines above for details):
|
|
||||||
- goal: ONE specific task, not project-wide goals
|
|
||||||
- progress: Checklist format, NO line numbers/commit hashes/API signatures
|
|
||||||
- context: ONLY files you're currently working on, NO type definitions/function signatures
|
|
||||||
- NEVER store: API docs, library types, function signatures (read source instead)
|
|
||||||
</core_memory>
|
</core_memory>
|
||||||
`.trim();
|
`.trim();
|
||||||
}
|
}
|
||||||
@@ -1488,75 +1483,35 @@ export default async function WorkingMemoryPlugin(
|
|||||||
): Promise<ReturnType<Plugin>> {
|
): Promise<ReturnType<Plugin>> {
|
||||||
const { directory, client } = input;
|
const { directory, client } = input;
|
||||||
|
|
||||||
|
// Cache for sub-agent detection — avoids repeated API calls per session.
|
||||||
|
// Maps sessionID → parentID (string) or null (root session).
|
||||||
|
const sessionParentCache = new Map<string, string | null>();
|
||||||
|
|
||||||
|
async function isSubAgent(sessionID: string): Promise<boolean> {
|
||||||
|
if (sessionParentCache.has(sessionID)) {
|
||||||
|
return sessionParentCache.get(sessionID) !== null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await client.session.get({ path: { id: sessionID } });
|
||||||
|
const parentID = result.data?.parentID ?? null;
|
||||||
|
sessionParentCache.set(sessionID, parentID);
|
||||||
|
return parentID !== null;
|
||||||
|
} catch {
|
||||||
|
// If we can't determine, assume it's NOT a sub-agent (safe default).
|
||||||
|
sessionParentCache.set(sessionID, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Inject core memory usage guidelines, pressure warnings, core memory,
|
// Inject pressure warnings, core memory, and working memory into the system prompt each turn.
|
||||||
// and working memory into the system prompt each turn.
|
// Core memory usage guidelines are in the core_memory_update tool description instead.
|
||||||
"experimental.chat.system.transform": async (hookInput, output) => {
|
"experimental.chat.system.transform": async (hookInput, output) => {
|
||||||
const { sessionID, model } = hookInput;
|
const { sessionID, model } = hookInput;
|
||||||
if (!sessionID) return;
|
if (!sessionID) return;
|
||||||
|
|
||||||
// Inject core memory usage guidelines.
|
// Sub-agents are short-lived — skip entire memory system.
|
||||||
// Inserted before <core_memory> so the agent reads guidance first.
|
if (await isSubAgent(sessionID)) return;
|
||||||
const coreMemoryGuidelines = `
|
|
||||||
# Core Memory Usage Guidelines
|
|
||||||
|
|
||||||
The Working Memory Plugin provides persistent core_memory blocks. **USE THEM CORRECTLY**:
|
|
||||||
|
|
||||||
## goal block (1000 chars)
|
|
||||||
**Purpose**: ONE specific task you're working on RIGHT NOW
|
|
||||||
|
|
||||||
✅ **GOOD Examples**:
|
|
||||||
- "Fix pruning bug where items with relevanceScore <0.01 are incorrectly excluded"
|
|
||||||
- "Add new tool: working_memory_search to query pool items by keyword"
|
|
||||||
- "Investigate why pressure warnings not showing in system prompt"
|
|
||||||
|
|
||||||
❌ **BAD Examples**:
|
|
||||||
- "Complete Phase 1-4 development and testing" (too broad, likely already done)
|
|
||||||
- "Build a working memory system for OpenCode" (project-level goal, not task-level)
|
|
||||||
|
|
||||||
## progress block (2000 chars)
|
|
||||||
**Purpose**: Checklist of done/in-progress/blocked items + key decisions
|
|
||||||
|
|
||||||
✅ **GOOD Examples**:
|
|
||||||
- "✅ Found bug in applyDecay() line 856\\n⏳ Testing fix with gamma=0.85\\n❓ Need to verify edge case: score=0"
|
|
||||||
- "✅ Phase 1-3 complete\\n⏳ Phase 4 intervention testing\\n⚠️ BLOCKED: Need promptAsync docs"
|
|
||||||
|
|
||||||
❌ **BAD Examples**:
|
|
||||||
- "Function sendPressureInterventionMessage() @ working-memory.ts:L1286-1354" (line numbers useless after edits)
|
|
||||||
- "Commit 2f42f1b implemented promptAsync integration" (commit hash irrelevant)
|
|
||||||
- "API: client.session.promptAsync({ path: {id}, body: {...} })" (API signature, not progress)
|
|
||||||
|
|
||||||
## context block (1500 chars)
|
|
||||||
**Purpose**: Files you're CURRENTLY editing + key patterns/conventions
|
|
||||||
|
|
||||||
✅ **GOOD Examples**:
|
|
||||||
- "Editing: .opencode/plugins/working-memory.ts (main plugin, 1706 lines)\\nRelated: WORKING_MEMORY.md, TEST_PHASE4.md"
|
|
||||||
- "Key paths: .opencode/memory-core/ (persistent blocks), memory-working/ (session data)"
|
|
||||||
- "Pattern: All async file ops use mkdir({recursive:true}) before writeFile"
|
|
||||||
|
|
||||||
❌ **BAD Examples**:
|
|
||||||
- "OpenCode SDK types: TextPartInput = { type: 'text', text: string, synthetic?: boolean }" (type definition)
|
|
||||||
- "Function signature: async function loadCoreMemory(directory: string, sessionID: string): Promise<CoreMemory | null>" (function signature)
|
|
||||||
- "Method client.session.promptAsync() returns 204 No Content" (API behavior, read docs instead)
|
|
||||||
|
|
||||||
## ⚠️ NEVER Store in Core Memory
|
|
||||||
- API documentation (read source/docs when needed)
|
|
||||||
- Type definitions from libraries (import them)
|
|
||||||
- Function signatures (read source code)
|
|
||||||
- Implementation details (belong in code comments)
|
|
||||||
- Completed goals (clear them immediately)
|
|
||||||
|
|
||||||
## ✅ Update Core Memory Immediately When
|
|
||||||
- **Starting new task**: Clear old goal, set new specific goal
|
|
||||||
- **Making progress**: Update progress checklist (keep concise)
|
|
||||||
- **Switching files**: Update context with current working files
|
|
||||||
- **Task completed**: Clear goal/progress, set next task
|
|
||||||
- **Approaching char limit**: Compress or remove outdated info
|
|
||||||
|
|
||||||
**Remember**: Core Memory is your **working scratchpad**, not a reference manual.
|
|
||||||
`.trim();
|
|
||||||
|
|
||||||
output.system.push(coreMemoryGuidelines);
|
|
||||||
|
|
||||||
// Check for memory pressure and inject warning into system prompt.
|
// Check for memory pressure and inject warning into system prompt.
|
||||||
// Skip if model just changed (avoids false alarms with different limits).
|
// Skip if model just changed (avoids false alarms with different limits).
|
||||||
@@ -1572,7 +1527,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
|
|
||||||
// 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(
|
||||||
@@ -1630,6 +1585,9 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
const { sessionID, callID, tool: toolName, args } = hookInput;
|
const { sessionID, callID, tool: toolName, args } = hookInput;
|
||||||
const { output: toolOutput } = hookOutput;
|
const { output: toolOutput } = hookOutput;
|
||||||
|
|
||||||
|
// Sub-agents don't need working memory tracking.
|
||||||
|
if (await isSubAgent(sessionID)) return;
|
||||||
|
|
||||||
await cacheToolOutput(directory, {
|
await cacheToolOutput(directory, {
|
||||||
callID,
|
callID,
|
||||||
sessionID,
|
sessionID,
|
||||||
@@ -1654,6 +1612,9 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
"experimental.chat.messages.transform": async (hookInput, output) => {
|
"experimental.chat.messages.transform": async (hookInput, output) => {
|
||||||
const sessionID = output.messages[0]?.info?.sessionID || "";
|
const sessionID = output.messages[0]?.info?.sessionID || "";
|
||||||
|
|
||||||
|
// Sub-agents don't need smart pruning.
|
||||||
|
if (sessionID && await isSubAgent(sessionID)) return;
|
||||||
|
|
||||||
// Load current pressure info to get pressure-aware pruning config
|
// Load current pressure info to get pressure-aware pruning config
|
||||||
const currentPressure = await loadModelPressureInfo(directory, sessionID);
|
const currentPressure = await loadModelPressureInfo(directory, sessionID);
|
||||||
const pressureLevel = currentPressure?.current?.pressure || 0;
|
const pressureLevel = currentPressure?.current?.pressure || 0;
|
||||||
@@ -1690,6 +1651,30 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Auto-capture [Decision: ...] markers from agent responses.
|
||||||
|
"experimental.text.complete": async (hookInput, output) => {
|
||||||
|
const { sessionID } = hookInput;
|
||||||
|
if (!sessionID) return;
|
||||||
|
|
||||||
|
// Sub-agents are short-lived — skip decision tracking.
|
||||||
|
if (await isSubAgent(sessionID)) return;
|
||||||
|
|
||||||
|
// Extract all [Decision: ...] markers from the completed text.
|
||||||
|
const matches = output.text.matchAll(/\[Decision:\s*([^\]]+)\]/gi);
|
||||||
|
for (const match of matches) {
|
||||||
|
const description = match[1].trim();
|
||||||
|
if (!description) continue;
|
||||||
|
|
||||||
|
await addToWorkingMemory(directory, sessionID, {
|
||||||
|
type: "decision",
|
||||||
|
content: description,
|
||||||
|
source: "auto:text",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
mentions: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Clean up all session artifacts on session deletion.
|
// Clean up all session artifacts on session deletion.
|
||||||
event: async ({ event }) => {
|
event: async ({ event }) => {
|
||||||
if (event.type === "session.deleted") {
|
if (event.type === "session.deleted") {
|
||||||
@@ -1704,6 +1689,9 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
"experimental.session.compacting": async (hookInput, output) => {
|
"experimental.session.compacting": async (hookInput, output) => {
|
||||||
const { sessionID } = hookInput;
|
const { sessionID } = hookInput;
|
||||||
|
|
||||||
|
// Sub-agents don't need compaction support.
|
||||||
|
if (await isSubAgent(sessionID)) return;
|
||||||
|
|
||||||
// Preserve only the most relevant working memory items
|
// Preserve only the most relevant working memory items
|
||||||
const preservedItems = await preserveRelevantItems(directory, sessionID, 0.5);
|
const preservedItems = await preserveRelevantItems(directory, sessionID, 0.5);
|
||||||
|
|
||||||
@@ -1741,26 +1729,14 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
|
|
||||||
// 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(
|
||||||
@@ -1769,7 +1745,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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
|
||||||
@@ -1795,7 +1771,48 @@ Operations:
|
|||||||
- append: Add content to the end of the block (automatically adds newline)
|
- append: Add content to the end of the block (automatically adds newline)
|
||||||
|
|
||||||
These blocks are ALWAYS visible to you in every message, even after compaction.
|
These blocks are ALWAYS visible to you in every message, even after compaction.
|
||||||
Update them regularly to maintain continuity across long conversations.`,
|
Update them regularly to maintain continuity across long conversations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage Guidelines
|
||||||
|
|
||||||
|
### goal block (1000 chars)
|
||||||
|
**Purpose**: ONE specific task you're working on RIGHT NOW
|
||||||
|
|
||||||
|
✅ GOOD: "Fix pruning bug where items with relevanceScore <0.01 are incorrectly excluded"
|
||||||
|
✅ GOOD: "Add new tool: working_memory_search to query pool items by keyword"
|
||||||
|
❌ BAD: "Complete Phase 1-4 development and testing" (too broad, likely already done)
|
||||||
|
❌ BAD: "Build a working memory system for OpenCode" (project-level goal, not task-level)
|
||||||
|
|
||||||
|
### progress block (2000 chars)
|
||||||
|
**Purpose**: Checklist of done/in-progress/blocked items + key decisions
|
||||||
|
|
||||||
|
✅ GOOD: "✅ Found bug in applyDecay()\\n⏳ Testing fix with gamma=0.85\\n❓ Need to verify edge case"
|
||||||
|
✅ GOOD: "✅ Phase 1-3 complete\\n⏳ Phase 4 intervention testing\\n⚠️ BLOCKED: Need promptAsync docs"
|
||||||
|
❌ BAD: "Function foo() @ file.ts:L1286-1354" (line numbers useless after edits)
|
||||||
|
❌ BAD: "Commit 2f42f1b implemented X" (commit hash irrelevant)
|
||||||
|
❌ BAD: "API: client.session.promptAsync({ ... })" (API signatures belong in source)
|
||||||
|
|
||||||
|
### context block (1500 chars)
|
||||||
|
**Purpose**: Files you're CURRENTLY editing + key patterns/conventions
|
||||||
|
|
||||||
|
✅ GOOD: "Editing: src/plugin.ts\\nKey paths: .opencode/memory-core/ (persistent blocks)"
|
||||||
|
✅ GOOD: "Pattern: All async file ops use mkdir({recursive:true}) before writeFile"
|
||||||
|
❌ BAD: Type definitions, function signatures, API docs (read source/docs instead)
|
||||||
|
|
||||||
|
### NEVER store in core memory
|
||||||
|
- API documentation (read source/docs when needed)
|
||||||
|
- Type definitions from libraries (import them)
|
||||||
|
- Function signatures (read source code)
|
||||||
|
- Completed goals (clear them immediately)
|
||||||
|
|
||||||
|
### Update core memory immediately when
|
||||||
|
- Starting new task: clear old goal, set new specific goal
|
||||||
|
- Making progress: update progress checklist (keep concise)
|
||||||
|
- Switching files: update context with current working files
|
||||||
|
- Task completed: clear goal/progress, set next task
|
||||||
|
- Approaching char limit: compress or remove outdated info`,
|
||||||
args: {
|
args: {
|
||||||
block: tool.schema.enum(["goal", "progress", "context"]).describe(
|
block: tool.schema.enum(["goal", "progress", "context"]).describe(
|
||||||
"Which memory block to update (goal/progress/context)"
|
"Which memory block to update (goal/progress/context)"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opencode-working-memory",
|
"name": "opencode-working-memory",
|
||||||
"version": "1.0.1",
|
"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