@davideasden/pi-undo 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davideasden/pi-undo",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Persistent workspace undo and redo for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/controller.ts CHANGED
@@ -452,7 +452,9 @@ export class UndoControllerImpl implements UndoController {
452
452
  }
453
453
 
454
454
  async beforeTree(event: SessionBeforeTreeEvent): Promise<SessionBeforeTreeResult | undefined> {
455
- if (!this.dependencies.isAgentIdle()) {
455
+ // Pi 0.86+ 在树导航期间也会让 isIdle() 暂时返回 false;只有 pi-undo
456
+ // 已经记录了尚未 settle 的 run 时,才需要中止并取消导航。
457
+ if (this.staged !== undefined) {
456
458
  await this.dependencies.abortAgent();
457
459
  return { cancel: true };
458
460
  }
package/src/pi-runtime.ts CHANGED
@@ -285,7 +285,13 @@ function rebuildControllerState(
285
285
  identity: SessionFileIdentity,
286
286
  ): ControllerInitialState {
287
287
  const state = sessionStateFor(manager);
288
- const checkpoints = state.getCheckpoints(identity);
288
+ const branch = physicalBranch(manager, manager.getLeafId());
289
+ const checkpoints = checkpointFrontierAfterDetachedRun(
290
+ manager,
291
+ identity,
292
+ branch,
293
+ state.getCheckpoints(identity),
294
+ );
289
295
  const cursor = state.getCursor(identity);
290
296
  let undoStack = [...checkpoints];
291
297
  if (cursor !== null) {
@@ -305,12 +311,34 @@ function rebuildControllerState(
305
311
  if (sourceCursor === undefined) throw new Error("cursor redo safety manifest 缺失");
306
312
  return { checkpoint, targetManifestId: sourceCursor.rollbackManifestId };
307
313
  });
308
- const branch = physicalBranch(manager, manager.getLeafId());
309
314
  const lastBarrier = findLastIndex(branch, (entry) => entry.type === "custom" && entry.customType === "pi-undo:barrier");
310
315
  const lastCheckpoint = findLastIndex(branch, (entry) => entry.type === "custom" && entry.customType === "pi-undo:checkpoint");
311
316
  return { undoStack, redoStack, historyPaused: lastBarrier > lastCheckpoint };
312
317
  }
313
318
 
319
+ /**
320
+ * redo 或树导航后的新 run 会挂在 cursor 后面,使前一个 checkpoint 脱离当前物理 branch。
321
+ * start entry 保存了 run 前的逻辑叶,利用它恢复可信的 checkpoint frontier,再接上当前 branch。
322
+ */
323
+ function checkpointFrontierAfterDetachedRun(
324
+ manager: ReadonlySessionManager,
325
+ identity: SessionFileIdentity,
326
+ branch: readonly Record<string, unknown>[],
327
+ current: readonly CheckpointRecord[],
328
+ ): CheckpointRecord[] {
329
+ const start = [...branch].reverse().find((entry) => entry.type === "custom" && entry.customType === "pi-undo:start");
330
+ if (start === undefined || !isRecord(start.data)) return [...current];
331
+ const sourceLogicalLeaf = start.data.sourceLogicalLeaf;
332
+ if (typeof sourceLogicalLeaf !== "string") return [...current];
333
+ const sourceCheckpoint = findCheckpointByEndLeaf(manager, identity, sourceLogicalLeaf);
334
+ if (sourceCheckpoint === undefined) return [...current];
335
+ const inherited = checkpointFrontierById(manager, identity, sourceCheckpoint.checkpointId);
336
+ return [
337
+ ...inherited,
338
+ ...current.filter((checkpoint) => !inherited.some((candidate) => candidate.checkpointId === checkpoint.checkpointId)),
339
+ ].filter((checkpoint, index, all) => all.findIndex((candidate) => candidate.checkpointId === checkpoint.checkpointId) === index);
340
+ }
341
+
314
342
  function checkpointFrontierById(
315
343
  manager: ReadonlySessionManager,
316
344
  identity: SessionFileIdentity,