#Undo message
1 messages · Page 1 of 1 (latest)
Not exactly. I don’t see an OpenClaw equivalent of OpenCode’s “undo last message” in the current docs/source.
Closest options:
/resetor/new→ start a fresh session for this chat/thread/compact→ keep the session, but summarize older context to shrink it
So if you want to fully wipe the current thread context, send:
/reset
Docs:
If you mean “remove only the last turn, but keep everything before it”, that does not look supported right now.
Yes I really means “remove only the last turn, but keep everything before it”, as I tried to manually edit json in session folder, and it seems like openclaw parse it properly. Maybe I need to make this as a skill?
Yeah, if manual edit “worked”, that makes sense. The transcript is real JSONL on disk, and OpenClaw rebuilds context from it.
But I would not make this a normal skill.
Better options, in order:
- Core feature / GitHub issue
- Plugin command like
/undo - Raw transcript hack as a private experiment only
Why plugin command, not skill:
- skills go through the model, which is the wrong layer for precise session mutation
- plugin commands bypass the LLM
- plugin command context gets
sessionKey,sessionId, andsessionFile, which is exactly what you need
Important caveat: this is not just “delete the last line”.
The transcript is a tree with id + parentId, so “undo last turn” should usually remove:
- the last user message
- the assistant reply
- any tool calls / tool results descended from that turn
Also, sessions.json is mutable, but the gateway is still the authority and may rewrite state while live, so doing transcript surgery behind its back is brittle.
So my recommendation is:
- don’t ship this as a skill
- if you want to prototype it, make a plugin command
- if you want it to be solid, it probably deserves a first-class
/undofeature
Docs:
- Session internals: https://docs.openclaw.ai/reference/session-management-compaction
- Plugin authoring: https://docs.openclaw.ai/plugins/building-plugins
If you want, I can sketch the minimal /undo plugin design.
Can you build the plugin for me?
I can’t ship files from Discord, but I can get you very close. I’d make it a plugin command, not a skill.
This starter adds /undo and does a conservative transcript rewrite with an automatic backup.
package.json
{
"name": "@local/openclaw-session-undo",
"version": "0.0.1",
"type": "module",
"openclaw": {
"extensions": ["./index.ts"],
"compat": {
"pluginApi": "YOUR_OPENCLAW_VERSION",
"minGatewayVersion": "YOUR_OPENCLAW_VERSION"
},
"build": {
"openclawVersion": "YOUR_OPENCLAW_VERSION",
"pluginSdkVersion": "YOUR_OPENCLAW_VERSION"
}
}
}
openclaw.plugin.json
{
"id": "session-undo",
"name": "Session Undo",
"description": "Adds /undo to remove the last user turn from the active session transcript.",
"configSchema": {
"type": "object",
"additionalProperties": false
}
}
index.ts
import fs from "node:fs/promises";
import path from "node:path";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
type TranscriptLine = {
type?: string;
id?: string;
parentId?: string;
message?: {
role?: string;
};
};
function backupSuffix(): string {
return new Date().toISOString().replace(/[:.]/g, "-");
}
function tryParseJson(line: string): TranscriptLine | null {
try {
return JSON.parse(line) as TranscriptLine;
} catch {
return null;
}
}
export default definePluginEntry({
id: "session-undo",
name: "Session Undo",
description: "Adds /undo to remove the last user turn from the active session transcript.",
register(api) {
api.registerCommand({
name: "undo",
description: "Remove the last user turn from the current session",
async handler(ctx) {
if (!ctx.sessionFile) {
return { text: "No active session file found for this conversation." };
}
const sessionFile = ctx.sessionFile;
const raw = await fs.readFile(sessionFile, "utf8");
const lines = raw.split(/\r?\n/).filter((line) => line.length > 0);
if (lines.length <= 1) {
return { text: "Nothing to undo." };
}
const parsed = lines.map((rawLine, index) => ({
index,
raw: rawLine,
json: tryParseJson(rawLine),
}));
const header = parsed[0]?.json;
if (!header || header.type !== "session") {
return { text: "Session header missing or invalid, refusing to edit transcript." };
}
let lastUserIndex = -1;
for (let i = parsed.length - 1; i >= 1; i -= 1) {
const entry = parsed[i]?.json;
if (entry?.type === "message" && entry?.message?.role === "user") {
lastUserIndex = i;
break;
}
}
if (lastUserIndex < 0) {
return { text: "No user turn found to undo." };
}
const target = parsed[lastUserIndex]!;
const removeIndexes = new Set<number>([lastUserIndex]);
const targetId = typeof target.json?.id === "string" ? target.json.id : "";
if (targetId) {
const removeIds = new Set<string>([targetId]);
let changed = true;
while (changed) {
changed = false;
for (let i = 1; i < parsed.length; i += 1) {
if (removeIndexes.has(i)) continue;
const entry = parsed[i]?.json;
if (!entry) continue;
const parentId = typeof entry.parentId === "string" ? entry.parentId : "";
const id = typeof entry.id === "string" ? entry.id : "";
if (parentId && removeIds.has(parentId)) {
removeIndexes.add(i);
if (id) removeIds.add(id);
changed = true;
}
}
}
} else {
for (let i = lastUserIndex + 1; i < parsed.length; i += 1) {
removeIndexes.add(i);
}
}
const keptLines = parsed
.filter((entry) => !removeIndexes.has(entry.index))
.map((entry) => entry.raw);
if (keptLines.length < 1) {
return { text: "Refusing to write an empty transcript." };
}
const backupPath = `${sessionFile}.undo.${backupSuffix()}`;
await fs.copyFile(sessionFile, backupPath);
await fs.writeFile(sessionFile, `${keptLines.join("\n")}\n`, "utf8");
const removedCount = parsed.length - keptLines.length;
return {
text:
`Undid the last turn. Removed ${removedCount} transcript ` +
`${removedCount === 1 ? "entry" : "entries"}.\n` +
`Backup: ${path.basename(backupPath)}`
};
},
});
},
});
Install
If you have a global install:
openclaw plugins install -l ./session-undo
openclaw plugins enable session-undo
openclaw gateway restart
If you run from source, use pnpm openclaw ... instead.