pi-resume 1.3.0 → 1.3.1
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/README.md +6 -1
- package/extensions/index.ts +4 -2
- package/package.json +1 -1
- package/src/nav.ts +32 -2
package/README.md
CHANGED
|
@@ -63,7 +63,12 @@ Walk the mtime-sorted session list relative to the **current** session:
|
|
|
63
63
|
- `/rp` — previous session (one step **newer**)
|
|
64
64
|
|
|
65
65
|
Useful after `/r1` lands on the wrong session: keep pressing `/rn` to walk back
|
|
66
|
-
in time instead of recalculating ranks.
|
|
66
|
+
in time instead of recalculating ranks.
|
|
67
|
+
|
|
68
|
+
`/rn` / `/rp` walk by **creation time** (the timestamp in the session filename),
|
|
69
|
+
not by mtime: resuming a session makes extensions append to it, which bumps its
|
|
70
|
+
mtime and would otherwise reshuffle the list into a ping-pong between two
|
|
71
|
+
sessions. `/r1` and `/rs` still rank by last activity (mtime). A `(pos/total)` indicator is shown on
|
|
67
72
|
each switch. At the ends of the list a notice is shown and nothing is switched.
|
|
68
73
|
|
|
69
74
|
### `/rs` — Smart Resume
|
package/extensions/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
import { formatEntry, truncate, sessionLabel, formatSize } from "../src/format.ts";
|
|
38
38
|
import { loadConfig, saveConfig, clampPage, clampDays } from "../src/config.ts";
|
|
39
39
|
import { getSessionDir } from "../src/session-dir.ts";
|
|
40
|
-
import { rankTarget, navTarget, parseRankFlag, type Hidden } from "../src/nav.ts";
|
|
40
|
+
import { rankTarget, navTarget, sortByCreated, parseRankFlag, type Hidden } from "../src/nav.ts";
|
|
41
41
|
import { buildPickerItems, resolveChoice } from "../src/picker.ts";
|
|
42
42
|
import { markActive, unmarkActive, activeElsewhere } from "../src/active.ts";
|
|
43
43
|
import { findLegacyForks, migrateLegacyForks } from "../src/migrate.ts";
|
|
@@ -214,7 +214,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
214
|
|
|
215
215
|
const currentFile = ctx.sessionManager.getSessionFile() ?? undefined;
|
|
216
216
|
const hidden = await hiddenPredicate();
|
|
217
|
-
|
|
217
|
+
// Walk by creation time: resuming bumps mtime, which would reshuffle
|
|
218
|
+
// an mtime-ordered walk into a ping-pong between two sessions.
|
|
219
|
+
const { target, pos, total } = await navTarget(sortByCreated(files), currentFile, dir, hidden);
|
|
218
220
|
|
|
219
221
|
if (!target) {
|
|
220
222
|
ctx.ui.notify(edge, "info");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-resume",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Fast session resume for pi coding agent — /r1,/r2 ranked resume, /rn and /rp step navigation, pi --r1/--rn startup flags, /rs paginated picker, /rds subagent session cleanup; skips sessions open in other pi instances, tidies legacy subagent forks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/nav.ts
CHANGED
|
@@ -16,6 +16,36 @@ export type Hidden<T extends FileRef> = (f: T) => Promise<boolean>;
|
|
|
16
16
|
|
|
17
17
|
const never = async () => false;
|
|
18
18
|
|
|
19
|
+
// pi session filenames: `<ISO timestamp with '-' instead of ':' and '.'>_<uuid>.jsonl`
|
|
20
|
+
// e.g. 2026-08-10T14-31-05-921Z_019fec15-....jsonl
|
|
21
|
+
const FILENAME_TS_RE = /(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z_/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Session creation time from the filename (immutable), or undefined if the
|
|
25
|
+
* name doesn't follow pi's pattern.
|
|
26
|
+
*/
|
|
27
|
+
export function createdAtFromName(file: string): number | undefined {
|
|
28
|
+
const base = file.slice(file.lastIndexOf("/") + 1);
|
|
29
|
+
const m = FILENAME_TS_RE.exec(base);
|
|
30
|
+
if (!m) return undefined;
|
|
31
|
+
const t = Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`);
|
|
32
|
+
return isNaN(t) ? undefined : t;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Stable order for step navigation: newest-created first.
|
|
37
|
+
*
|
|
38
|
+
* mtime is NOT stable for walking: resuming a session makes extensions append
|
|
39
|
+
* entries (e.g. state on session_start), which bumps its mtime to "now" and
|
|
40
|
+
* reshuffles the list, so /rn, /rn, /rn ping-pongs between two sessions.
|
|
41
|
+
* Creation time never changes, so a walk over it is predictable. Files
|
|
42
|
+
* without a parseable timestamp fall back to mtime.
|
|
43
|
+
*/
|
|
44
|
+
export function sortByCreated<T extends FileRef & { mtime: Date }>(files: T[]): T[] {
|
|
45
|
+
const key = (f: T) => createdAtFromName(f.file) ?? f.mtime.getTime();
|
|
46
|
+
return [...files].sort((a, b) => key(b) - key(a));
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
/**
|
|
20
50
|
* Rank-th most recent visible session, current excluded (rank 1 = latest).
|
|
21
51
|
* Returns the target or undefined, plus how many visible candidates were seen.
|
|
@@ -36,8 +66,8 @@ export async function rankTarget<T extends FileRef>(
|
|
|
36
66
|
}
|
|
37
67
|
|
|
38
68
|
/**
|
|
39
|
-
* Step relative to the current session in the
|
|
40
|
-
* hidden files. dir = 1 → older, dir = -1 → newer. An unsaved current session
|
|
69
|
+
* Step relative to the current session in the given list (callers pass
|
|
70
|
+
* sortByCreated() output for a stable walk), skipping hidden files. dir = 1 → older, dir = -1 → newer. An unsaved current session
|
|
41
71
|
* (not in the list) is treated as the newest. `pos`/`total` are raw list
|
|
42
72
|
* indices (1-based) for the on-screen indicator.
|
|
43
73
|
*/
|