mirror of
https://github.com/dtzp555-max/memory-continuity.git
synced 2026-07-22 05:25:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b1b99ea21 | ||
|
|
24221ea749 | ||
|
|
2fc42661cf |
+50
-52
@@ -1,75 +1,73 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.3.0 — 2026-03-16
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
First **fully stable** release. All previous versions had critical bugs causing silent failure on gateway deployments.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Workspace resolution** (`_ctx.workspaceDir`) — Hooks now read workspace path from the hook context parameter instead of `api.runtime.workspaceDir` (which was always `undefined` in gateway mode). This was the root cause of the plugin silently doing nothing on all gateway/Telegram/Discord deployments.
|
||||||
|
- **Channel metadata stripping** — User messages from Telegram/Discord are cleaned of `Conversation info (untrusted metadata)` prefixes before saving, preventing garbage in the state file.
|
||||||
|
- **Recovery death spiral** — Short conversations (< 2 real user messages) no longer overwrite existing meaningful state. Previously: tell secret → `/new` → model ignores injected context → "I don't remember" → `agent_end` overwrites secret with failure → permanent data loss.
|
||||||
|
- **State staleness** — Removed "skip if existing state is meaningful" guard that prevented state from ever updating after the first write.
|
||||||
|
|
||||||
|
### Debugging timeline
|
||||||
|
1. Hooks not firing → `api.runtime.workspaceDir` was `undefined` → fix: use `_ctx.workspaceDir`
|
||||||
|
2. State file full of Telegram metadata → fix: strip `Conversation info` / `Sender` prefixes
|
||||||
|
3. State never updating → "Existing state is meaningful, skipping" → fix: always update
|
||||||
|
4. Good state overwritten by bad → fix: require ≥ 2 real user messages to overwrite
|
||||||
|
|
||||||
|
### Tested on
|
||||||
|
- macOS: GPT-5.4 (openai-codex) ✅
|
||||||
|
- Oracle Cloud Linux: MiniMax M2.5 ✅
|
||||||
|
- Multiple OpenClaw agents via Telegram ✅
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
- State extraction uses last user/assistant message only (not full summary)
|
||||||
|
- Models that ignore `prependSystemContext` won't surface recovered state (state file is preserved, not overwritten). Adding recovery instructions to BOOTSTRAP.md helps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2.2.0 — 2026-03-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `package.json` with npm-style metadata and `openclaw.type: "plugin"` provenance
|
||||||
|
- `plugins.allow` auto-configuration in `post-install.sh`
|
||||||
|
- GitHub release with comparison table vs vector-DB plugins
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- "plugins.allow is empty" gateway warning
|
||||||
|
- "loaded without install/load-path provenance" gateway warning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.1.0 — 2026-03-16
|
## v2.1.0 — 2026-03-16
|
||||||
|
|
||||||
### Summary
|
|
||||||
Clean install experience: eliminates gateway provenance warnings for fresh installations.
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `package.json` — proper npm-style metadata with `openclaw.type: "plugin"` provenance field
|
- `openclaw.plugin.json` manifest with `configSchema`
|
||||||
- `plugins.allow` auto-configuration in `post-install.sh` — registers plugin in OpenClaw trust list
|
- `scripts/post-install.sh` one-command installer
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `post-install.sh` now copies `package.json` to extensions directory
|
- README updated with "Why this plugin?" section
|
||||||
- `openclaw.plugin.json` now includes `source` field pointing to GitHub repo
|
|
||||||
- README updated with `plugins.allow` documentation and install step
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- "plugins.allow is empty; discovered non-bundled plugins may auto-load" warning
|
|
||||||
- "loaded without install/load-path provenance; treat as untracked local code" warning
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v2.0.0 — 2026-03-15
|
## v2.0.0 — 2026-03-15
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
Major architecture upgrade: from prompt-based skill to **lifecycle plugin**.
|
Architecture upgrade: skill (prompt-based) → **plugin** (lifecycle hooks).
|
||||||
State injection and extraction now happen automatically via OpenClaw hooks,
|
|
||||||
making recovery model-agnostic and reliable across all providers.
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `index.js` — plugin entry point with 5 lifecycle hooks
|
- `index.js` with 5 lifecycle hooks: `before_agent_start`, `before_compaction`, `before_reset`, `agent_end`, `session_end`
|
||||||
- `openclaw.plugin.json` — plugin manifest with configSchema
|
- Automated state injection via `prependSystemContext`
|
||||||
- Automated installer (`scripts/post-install.sh`) that handles copy + config + restart
|
- Auto-extraction of working state from conversation
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Architecture: skill (prompt-based) → plugin (hook-based)
|
|
||||||
- `before_agent_start` hook injects `CURRENT_STATE.md` into system context automatically
|
|
||||||
- `agent_end` hook auto-extracts working state from conversation
|
|
||||||
- `before_compaction` hook preserves state through context compression
|
|
||||||
- `before_reset` hook archives state before `/new`
|
|
||||||
- `session_end` hook ensures state file exists
|
|
||||||
- README fully rewritten for plugin architecture
|
|
||||||
- post-install.sh rewritten for plugin installation flow
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Dependency on model cooperation for state read/write
|
- Dependency on model cooperation for state read/write
|
||||||
- Reliance on `skillsSnapshot` cache clearing
|
|
||||||
|
|
||||||
### Validated
|
|
||||||
- Tested with MiniMax M2.5 and GPT-5.4 on OpenClaw 2026.3.12
|
|
||||||
- `/new` recovery: agent correctly surfaces recovered state
|
|
||||||
- Multi-turn state: conversation context (secrets, scheduled events) persists across resets
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.3.0-probe — 2026-03-13
|
## v1.0.0 — 2026-03-14
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
Transition from skill-only to dual-form package (skill + lifecycle plugin probe).
|
Initial release as a skill (SKILL.md only). Required model to voluntarily read/write `CURRENT_STATE.md`. Did not work reliably with weaker models.
|
||||||
|
|
||||||
### Added
|
|
||||||
- `plugin/lifecycle-prototype.ts`
|
|
||||||
- `references/phase2-hook-validation.md`
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Repository direction clarified: primary path is lifecycle plugin
|
|
||||||
- ContextEngine documented as future option, not v1 default
|
|
||||||
|
|
||||||
### Validated
|
|
||||||
- Startup continuity injection on resident subagents
|
|
||||||
- Hook wiring confirmed without `read` tool dependency
|
|
||||||
|
|
||||||
### Known limitation
|
|
||||||
- Skill-based approach unreliable: models can ignore recovery instructions
|
|
||||||
- This limitation is resolved in v2.0.0 by moving to hooks
|
|
||||||
|
|||||||
@@ -238,20 +238,48 @@ const plugin = {
|
|||||||
const statePath = resolveStatePath(ws);
|
const statePath = resolveStatePath(ws);
|
||||||
if (!statePath) return;
|
if (!statePath) return;
|
||||||
|
|
||||||
// If there's already a meaningful state file, don't overwrite with
|
const messages = event?.messages;
|
||||||
// auto-extracted content (agent or user wrote it explicitly)
|
if (!messages || messages.length === 0) {
|
||||||
const existing = readFile(statePath);
|
log.info?.("[memory-continuity] No messages in session, skipping");
|
||||||
if (existing && buildSnapshot(existing)) {
|
|
||||||
log.info?.("[memory-continuity] Existing state is meaningful, skipping auto-extract");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract from conversation messages
|
// Count real user messages (exclude system, metadata-only, short commands)
|
||||||
const newState = extractStateFromMessages(event?.messages);
|
const realUserMsgs = messages.filter(m => {
|
||||||
if (!newState) return;
|
if (m?.role !== "user") return false;
|
||||||
|
const text = typeof m?.content === "string" ? m.content
|
||||||
|
: Array.isArray(m?.content) ? m.content.filter(b => b?.type === "text").map(b => b.text).join("\n")
|
||||||
|
: "";
|
||||||
|
const cleaned = text
|
||||||
|
.replace(/^Conversation info \(untrusted metadata\):[\s\S]*?\n\n/m, "")
|
||||||
|
.replace(/^Sender \(untrusted metadata\):[\s\S]*?\n\n/m, "")
|
||||||
|
.trim();
|
||||||
|
// Skip very short messages like "/new", "/status", single-word queries
|
||||||
|
return cleaned.length > 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't overwrite meaningful state with trivial conversations
|
||||||
|
// (e.g., "/new" then "what was my secret?" — only 1 real message)
|
||||||
|
const existing = readFile(statePath);
|
||||||
|
if (existing && buildSnapshot(existing) && realUserMsgs.length < 2) {
|
||||||
|
log.info?.("[memory-continuity] Conversation too short to overwrite existing state (" + realUserMsgs.length + " real msgs)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState = extractStateFromMessages(messages);
|
||||||
|
if (!newState) {
|
||||||
|
log.info?.("[memory-continuity] No extractable state from conversation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archive previous state if it exists
|
||||||
|
if (existing) {
|
||||||
|
const archivePath = statePath.replace(/CURRENT_STATE\.md$/, `STATE_ARCHIVE_${Date.now()}.md`);
|
||||||
|
writeFile(archivePath, existing);
|
||||||
|
}
|
||||||
|
|
||||||
writeFile(statePath, newState);
|
writeFile(statePath, newState);
|
||||||
log.info?.("[memory-continuity] Auto-extracted state from conversation");
|
log.info?.("[memory-continuity] Updated state from conversation (" + realUserMsgs.length + " real msgs)");
|
||||||
}, { priority: 90 }); // low priority, run after other hooks
|
}, { priority: 90 }); // low priority, run after other hooks
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"id": "memory-continuity",
|
"id": "memory-continuity",
|
||||||
"name": "Memory Continuity",
|
"name": "Memory Continuity",
|
||||||
"description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.",
|
"description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"source": "https://github.com/dtzp555-max/memory-continuity",
|
"source": "https://github.com/dtzp555-max/memory-continuity",
|
||||||
"configSchema": {
|
"configSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "memory-continuity",
|
"name": "memory-continuity",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"description": "Zero-dependency memory continuity for OpenClaw — plain markdown, lifecycle hooks, no vector DB.",
|
"description": "Zero-dependency memory continuity for OpenClaw — plain markdown, lifecycle hooks, no vector DB.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Reference in New Issue
Block a user