pi-resume 1.4.0 → 1.5.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 1.5.0
4
+ - `/rn` / `/rp` position is shown in the footer status (`session 3/80`); the
5
+ transient notice was being replaced by pi's own "Resumed session".
6
+ - `pi --rN` waits for the provider refresh at most 15 s before resuming.
7
+ - Picker rows leave room for the duplicate suffix ` (N)` so it is never clipped.
8
+ - New interactive e2e suite (`npm run test:tui`): a real pi TUI in tmux with an
9
+ isolated agent dir — startup flag, `/rn` `/rp` walk stability, `/r1`, `/rs`
10
+ picker (single-line rows at 80 cols, arrow navigation, Enter, duplicates),
11
+ `/rs set`, `/rds`. Runs in CI.
12
+
13
+ ## 1.4.0
14
+ - Refactor only: `extensions/index.ts` split into `src/ext/*` (one module per
15
+ concern), shared `loadSessions()`, all user-facing strings in `messages.ts`,
16
+ pure `applySetting()` / `resolveStartupRank()` with unit tests.
17
+
18
+ ## 1.3.2
19
+ - Fix: `pi --rN` / `--rn` never actually switched (startup context has no
20
+ `switchSession`). Now dispatches `/rN` as a command after the provider
21
+ refresh finishes (switching mid-refresh crashed pi with an AbortError).
22
+
23
+ ## 1.3.1
24
+ - Fix: `/rn` `/rp` walk by creation time instead of mtime — resuming bumps
25
+ mtime and made the walk ping-pong between two sessions.
26
+
27
+ ## 1.3.0
28
+ - Legacy subagent fork sessions migrated into `<parent>/forks/`.
29
+ - Sessions open in another live pi are skipped by navigation and marked in
30
+ `/rs`; pid-reuse guard for the registry.
31
+
32
+ ## 1.2.x
33
+ - `/r3`–`/r5` removed (`/rn` `/rp` cover it); docs fixes.
34
+
35
+ ## 1.1.0
36
+ - `/rn` `/rp` step navigation; `pi --r N` / `--r1` / `--rn` startup flags.
37
+ - Fix: picker rows wrapped on narrow terminals and broke cursor navigation.
38
+ - Fix: duplicate picker rows resolved to the wrong session.
39
+ - Session renames appended past the head window are picked up (tail read).
40
+ - Metadata cache keyed by (file, mtime, size); CI.
package/README.md CHANGED
@@ -72,8 +72,9 @@ in time instead of recalculating ranks.
72
72
  `/rn` / `/rp` walk by **creation time** (the timestamp in the session filename),
73
73
  not by mtime: resuming a session makes extensions append to it, which bumps its
74
74
  mtime and would otherwise reshuffle the list into a ping-pong between two
75
- sessions. `/r1` and `/rs` still rank by last activity (mtime). A `(pos/total)` indicator is shown on
76
- each switch. At the ends of the list a notice is shown and nothing is switched.
75
+ sessions. `/r1` and `/rs` still rank by last activity (mtime). The footer status
76
+ shows your position (`session 3/80`) after each step. At the ends of the list a
77
+ notice is shown and nothing is switched.
77
78
 
78
79
  ### `/rs` — Smart Resume
79
80
 
@@ -153,3 +154,11 @@ npm test
153
154
  ## License
154
155
 
155
156
  MIT
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ npm test # unit tests (pure modules)
162
+ npm run test:tui # interactive e2e: real pi TUI in tmux, isolated agent dir (needs tmux)
163
+ docker build -f tests/e2e/Dockerfile -t e2e . && docker run --rm e2e # install/CLI e2e
164
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-resume",
3
- "version": "1.4.0",
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",
3
+ "version": "1.5.0",
4
+ "description": "Fast session resume for pi coding agent \u2014 /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",
7
7
  "keywords": [
@@ -19,6 +19,7 @@
19
19
  "extensions",
20
20
  "src",
21
21
  "README.md",
22
+ "CHANGELOG.md",
22
23
  "LICENSE"
23
24
  ],
24
25
  "pi": {
@@ -42,6 +43,7 @@
42
43
  "typecheck": "tsc --noEmit",
43
44
  "test": "node --experimental-vm-modules --test tests/*.test.ts",
44
45
  "check": "npm run typecheck && npm pack --dry-run",
45
- "prepublishOnly": "npm run check"
46
+ "prepublishOnly": "npm run check",
47
+ "test:tui": "node --experimental-vm-modules --test tests/tui/tui.test.ts"
46
48
  }
47
49
  }
@@ -54,12 +54,18 @@ export async function switchTo(
54
54
  ctx: ExtensionCommandContext,
55
55
  target: StatResult,
56
56
  announce: (label: string) => string = MSG.resumed,
57
+ status?: string,
57
58
  ): Promise<{ cancelled: boolean }> {
58
59
  const meta = await readSessionMeta(target.file, target);
59
60
  const label = truncate(sessionLabel(meta), LABEL_MAX);
60
61
  return ctx.switchSession(target.file, {
61
62
  withSession: async (newCtx) => {
63
+ // pi's own "Resumed session" notice lands right after ours and replaces
64
+ // it, so the durable part (position) goes to the footer status instead.
62
65
  newCtx.ui.notify(announce(label), "info");
66
+ newCtx.ui.setStatus(STATUS_KEY, status);
63
67
  },
64
68
  });
65
69
  }
70
+
71
+ export const STATUS_KEY = "pi-resume";
package/src/ext/resume.ts CHANGED
@@ -42,7 +42,7 @@ export async function resumeStep(dir: 1 | -1, ctx: ExtensionCommandContext): Pro
42
42
  ctx.ui.notify(dir === 1 ? MSG.atOldest : MSG.atNewest, "info");
43
43
  return;
44
44
  }
45
- await switchTo(ctx, target, (label) => MSG.resumedAt(pos, total, label));
45
+ await switchTo(ctx, target, (label) => MSG.resumedAt(pos, total, label), `session ${pos}/${total}`);
46
46
  }
47
47
 
48
48
  export function registerResumeCommands(pi: ExtensionAPI): void {
@@ -12,6 +12,9 @@ import { currentSessionFile, sessionDirFor } from "./context.ts";
12
12
  import { MAX_RANK, ordinal } from "./resume.ts";
13
13
  import { MSG } from "./messages.ts";
14
14
 
15
+ /** Upper bound on waiting for the provider refresh before resuming anyway. */
16
+ const REFRESH_WAIT_MAX_MS = 15_000;
17
+
15
18
  export type FlagReader = (name: string) => unknown;
16
19
 
17
20
  /**
@@ -62,9 +65,10 @@ async function resumeAtStartup(pi: ExtensionAPI, ctx: ExtensionContext): Promise
62
65
  // dispatch our own slash command to run with a command context. Switching
63
66
  // while a provider refresh is in flight aborts it and crashes pi (uncaught
64
67
  // AbortError from the provider), so let the refresh finish first.
65
- try {
66
- await ctx.modelRegistry.refresh();
67
- } catch {}
68
+ await Promise.race([
69
+ ctx.modelRegistry.refresh().catch(() => {}),
70
+ new Promise((r) => setTimeout(r, REFRESH_WAIT_MAX_MS)),
71
+ ]);
68
72
  pi.sendUserMessage(`/r${wanted.rank}`, { expandPromptTemplates: true });
69
73
  }
70
74
 
package/src/format.ts CHANGED
@@ -34,11 +34,12 @@ export function sessionLabel(e: SessionEntry): string {
34
34
  * Reserves space for the age/size columns and the picker's cursor/indent
35
35
  * so rows never wrap (wrapped rows break selection navigation in the TUI).
36
36
  */
37
- // Columns the TUI select list draws around each row (cursor arrow, indent,
38
- // uniquify suffix like " (12)"). Keeping rows shorter than
39
- // terminal width minus this margin prevents line wrapping, which would
40
- // break cursor navigation in the picker.
41
- const PICKER_ROW_MARGIN = 6;
37
+ // Columns reserved at the end of each picker row: the select component's
38
+ // cursor/indent (~4) plus the uniquify suffix " (NN)" (5) that
39
+ // buildPickerItems() may append to duplicate rows. Rows longer than the
40
+ // terminal width either wrap (breaking cursor navigation) or get clipped by
41
+ // the component (hiding the suffix), so the label is truncated to fit.
42
+ const PICKER_ROW_MARGIN = 10;
42
43
 
43
44
  /** Label marker for a session currently open in another pi process. */
44
45
  export const OPEN_ELSEWHERE_MARK = "⦿ ";