mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 01:20:38 +00:00
The backend rejects a save after the call that made the changes has returned, and the workspace then undoes them. A plugin, or the MCP agent, went on working with shapes that were no longer there and never learned why. The rollback event now names the files that lost commits and the pages and page shapes their changes touched, and plugins get it as the new `contentrollback` event (`ContentRollback` type) through a subscription to the store stream, so no rollback is dropped by debouncing. The MCP plugin keeps the rollbacks it is told about and sends them with its next task response, failed or not. The server collects them per tool call and puts a warning first in that tool's response, naming what was affected; the instructions tell the agent to read the pages again and redo what is missing.
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
import type { ContentRollbackEvent } from "../../common/src";
|
|
|
|
/**
|
|
* Log of the rollbacks of rejected changes that the MCP server has not been told about yet.
|
|
*
|
|
* A rollback happens after the task that made the rejected changes has returned (the backend
|
|
* rejects a save seconds later), so the log hands its events to the next task response.
|
|
*/
|
|
export class RollbackLog {
|
|
private events: ContentRollbackEvent[] = [];
|
|
|
|
/**
|
|
* Starts recording the rollbacks Penpot reports.
|
|
*
|
|
* Penpot versions without the `contentrollback` event reject the subscription; the log then
|
|
* stays empty.
|
|
*/
|
|
listen(): void {
|
|
try {
|
|
// @ts-ignore - contentrollback is not yet in the released plugin types
|
|
penpot.on("contentrollback", (event: ContentRollbackEvent) => this.record(event));
|
|
} catch (error) {
|
|
console.warn("Penpot does not report rollbacks:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a rollback to the log.
|
|
*
|
|
* @param event - the rollback as reported by Penpot
|
|
*/
|
|
record(event: ContentRollbackEvent): void {
|
|
this.events.push({
|
|
fileIds: [...event.fileIds],
|
|
pageIds: [...event.pageIds],
|
|
shapeIds: [...event.shapeIds],
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the rollbacks not yet acknowledged, oldest first.
|
|
*/
|
|
pending(): ContentRollbackEvent[] {
|
|
return [...this.events];
|
|
}
|
|
|
|
/**
|
|
* Removes the oldest `count` rollbacks, once a response carrying them has been sent.
|
|
*
|
|
* @param count - the number of rollbacks the sent response carried
|
|
*/
|
|
acknowledge(count: number): void {
|
|
this.events = this.events.slice(count);
|
|
}
|
|
}
|