pi-edit-session-in-place 0.1.13 → 0.1.15

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 CHANGED
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.1.15] - 2026-06-04
8
+
9
+ ### Changed
10
+ - updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-tui` `0.78.1` and regenerated the npm lockfile
11
+ - refreshed README compatibility notes and the fleet-tested pi marker for `0.78.1` without pinning pi `0.78.1` as a hard runtime requirement
12
+ - changed the main-editor integration to wrap any previously configured custom editor instead of replacing it
13
+
14
+ ### Compatibility
15
+ - reviewed the pi `0.78.1` changelog and current extension, TUI, custom editor, mode, session, and package guidance; the extension now guards custom TUI behavior with `ctx.mode === "tui"` and keeps existing custom editors composed
16
+ - confirmed the new `ctx.getSystemPromptOptions()` command API, provider additions, package-install hardening, and security fixes do not require additional runtime changes for this package
17
+
18
+ ## [0.1.14] - 2026-05-29
19
+
20
+ ### Changed
21
+ - updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-tui` `0.78.0` and regenerated the npm lockfile
22
+ - aligned package tooling metadata with the pi fleet policy: `packageManager` now records the npm 11 major line and `engines.node` is `>=22 <25`
23
+ - refreshed README compatibility notes and the fleet-tested pi marker for `0.78.0`
24
+
25
+ ### Compatibility
26
+ - reviewed the pi `0.78.0` changelog and current extension, TUI, session, and package guidance; the extension continues to use supported command, shortcut, custom editor, and session tree APIs
27
+ - confirmed the new startup session naming, file-link rendering, provider, and argument parsing changes do not require runtime changes for this package
28
+
7
29
  ## [0.1.13] - 2026-05-28
8
30
 
9
31
  ### Changed
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # pi edit-session-in-place
2
2
 
3
- A [pi](https://github.com/badlogic/pi-mono) extension that lets you rewind to an earlier user message in the current branch, then either **edit it in place** or **delete it and continue from there**.
3
+ A [pi](https://github.com/earendil-works/pi-mono) extension that lets you rewind to an earlier user message in the current branch, then either **edit it in place** or **delete it and continue from there**.
4
4
 
5
5
  ## Compatibility
6
6
 
7
7
  Tested with:
8
8
 
9
- - `@earendil-works/pi-coding-agent` `0.77.0`
10
- - `@earendil-works/pi-tui` `0.77.0`
11
- - Node.js `>=22.19.0`
9
+ - `@earendil-works/pi-coding-agent` `0.78.1`
10
+ - `@earendil-works/pi-tui` `0.78.1`
11
+ - Node.js `>=22 <25`
12
12
 
13
- Local development and verification in this repo target pi `0.77.0`. `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` stay in `devDependencies` for local typechecking and tests, while pi core packages are declared as optional wildcard peers and the extension relies on pi's bundled runtime packages at execution time. That keeps installs forward-open for future pi releases: npm peer ranges should not block users from trying a newer pi, though runtime behavior is only verified against the tested baseline until a follow-up package release confirms it.
13
+ Local development and verification in this repo target pi `0.78.1` as the suggested minimum tested baseline. `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` stay in `devDependencies` for local typechecking and tests, while pi core packages are declared as optional wildcard peers and the extension relies on pi's bundled runtime packages at execution time. That keeps installs forward-open for future pi releases: npm peer ranges should not block users from trying a newer pi, though runtime behavior is only verified against the tested baseline until a follow-up package release confirms it.
14
14
 
15
15
  ## What it does
16
16
 
@@ -76,11 +76,12 @@ If you clear the message and submit an empty value, the selected message is effe
76
76
 
77
77
  ## Behavior notes
78
78
 
79
- - Works in interactive mode; non-interactive modes do not show the picker/editor UI
79
+ - Works in interactive TUI mode; non-interactive and RPC modes do not show the picker/editor UI
80
80
  - Later messages on the abandoned branch are not deleted from the session file; they remain reachable through `/tree`
81
81
  - If the selected message contains images, the extension warns that re-editing or deleting it will drop the images and keep only text behavior
82
82
  - The extension only offers text-bearing user messages for editing; image-only or whitespace-only user messages are skipped
83
83
  - Queued messages must be cleared before using the command
84
+ - If another extension has already customized the main editor, this extension wraps it instead of replacing it
84
85
 
85
86
  ## Development
86
87
 
@@ -15,6 +15,7 @@ import {
15
15
  DynamicBorder,
16
16
  keyHint,
17
17
  rawKeyHint,
18
+ type AppKeybinding,
18
19
  type ExtensionAPI,
19
20
  type ExtensionCommandContext,
20
21
  type KeybindingsManager,
@@ -29,6 +30,8 @@ import {
29
30
  Spacer,
30
31
  Text,
31
32
  matchesKey,
33
+ type AutocompleteProvider,
34
+ type EditorComponent,
32
35
  type EditorTheme,
33
36
  type Focusable,
34
37
  type TUI,
@@ -288,7 +291,7 @@ class EditableMessageSelector extends Container {
288
291
  this.onCancel = onCancel;
289
292
  this.selectedIndex = Math.max(0, messages.length - 1);
290
293
 
291
- this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
294
+ this.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
292
295
  this.addChild(new Spacer(1));
293
296
  this.addChild(new Text(theme.fg("accent", title), 1, 0));
294
297
  this.addChild(new Spacer(1));
@@ -317,7 +320,7 @@ class EditableMessageSelector extends Container {
317
320
  ),
318
321
  );
319
322
  this.addChild(new Spacer(1));
320
- this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
323
+ this.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
321
324
  }
322
325
 
323
326
  private setSelectedIndex(index: number) {
@@ -391,7 +394,7 @@ class ReeditMessageEditor extends Container implements Focusable {
391
394
  this.keybindings = keybindings;
392
395
  this.onCancel = onCancel;
393
396
 
394
- this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
397
+ this.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
395
398
  this.addChild(new Spacer(1));
396
399
  this.addChild(new Text(theme.fg("accent", title), 1, 0));
397
400
  this.addChild(new Spacer(1));
@@ -413,7 +416,7 @@ class ReeditMessageEditor extends Container implements Focusable {
413
416
  ].join(" ");
414
417
  this.addChild(new Text(hint, 1, 0));
415
418
  this.addChild(new Spacer(1));
416
- this.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
419
+ this.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
417
420
  }
418
421
 
419
422
  handleInput(data: string): void {
@@ -503,7 +506,10 @@ const clearSavedDraft = () => {
503
506
  };
504
507
 
505
508
  const handleEditTurn = async (ctx: ExtensionCommandContext) => {
506
- if (!ctx.hasUI) {
509
+ if (ctx.mode !== "tui") {
510
+ if (ctx.hasUI) {
511
+ ctx.ui.notify("/edit-turn requires interactive TUI mode.", "warning");
512
+ }
507
513
  clearSavedDraft();
508
514
  return;
509
515
  }
@@ -566,12 +572,16 @@ const handleEditTurn = async (ctx: ExtensionCommandContext) => {
566
572
  );
567
573
  };
568
574
 
575
+ const triggerEditTurnCommand = (editor: Pick<EditorComponent, "getText" | "setText" | "handleInput">) => {
576
+ draftBeforeHotkey = editor.getText();
577
+ editor.setText(COMMAND_TEXT);
578
+ editor.handleInput("\r");
579
+ };
580
+
569
581
  class EditSessionInPlaceEditor extends CustomEditor {
570
582
  handleInput(data: string): void {
571
583
  if (matchesKey(data, HOTKEY)) {
572
- draftBeforeHotkey = this.getText();
573
- this.setText(COMMAND_TEXT);
574
- super.handleInput("\r");
584
+ triggerEditTurnCommand(this);
575
585
  return;
576
586
  }
577
587
 
@@ -579,6 +589,152 @@ class EditSessionInPlaceEditor extends CustomEditor {
579
589
  }
580
590
  }
581
591
 
592
+ type AutocompleteAwareEditor = EditorComponent & {
593
+ isShowingAutocomplete?: () => boolean;
594
+ };
595
+
596
+ class EditSessionInPlaceEditorWrapper implements EditorComponent, Focusable {
597
+ public actionHandlers: Map<AppKeybinding, () => void> = new Map();
598
+ public onEscape?: () => void;
599
+ public onCtrlD?: () => void;
600
+ public onPasteImage?: () => void;
601
+ public onExtensionShortcut?: (data: string) => boolean | undefined;
602
+
603
+ constructor(
604
+ private readonly base: EditorComponent,
605
+ private readonly keybindings: KeybindingsManager,
606
+ ) {}
607
+
608
+ get focused(): boolean {
609
+ return "focused" in this.base && typeof this.base.focused === "boolean" ? this.base.focused : false;
610
+ }
611
+
612
+ set focused(value: boolean) {
613
+ if ("focused" in this.base) {
614
+ (this.base as EditorComponent & Focusable).focused = value;
615
+ }
616
+ }
617
+
618
+ get wantsKeyRelease(): boolean | undefined {
619
+ return this.base.wantsKeyRelease;
620
+ }
621
+
622
+ set wantsKeyRelease(value: boolean | undefined) {
623
+ this.base.wantsKeyRelease = value;
624
+ }
625
+
626
+ get onSubmit(): ((text: string) => void) | undefined {
627
+ return this.base.onSubmit;
628
+ }
629
+
630
+ set onSubmit(value: ((text: string) => void) | undefined) {
631
+ this.base.onSubmit = value;
632
+ }
633
+
634
+ get onChange(): ((text: string) => void) | undefined {
635
+ return this.base.onChange;
636
+ }
637
+
638
+ set onChange(value: ((text: string) => void) | undefined) {
639
+ this.base.onChange = value;
640
+ }
641
+
642
+ get borderColor(): ((str: string) => string) | undefined {
643
+ return this.base.borderColor;
644
+ }
645
+
646
+ set borderColor(value: ((str: string) => string) | undefined) {
647
+ this.base.borderColor = value;
648
+ }
649
+
650
+ render(width: number): string[] {
651
+ return this.base.render(width);
652
+ }
653
+
654
+ invalidate(): void {
655
+ this.base.invalidate();
656
+ }
657
+
658
+ getText(): string {
659
+ return this.base.getText();
660
+ }
661
+
662
+ setText(text: string): void {
663
+ this.base.setText(text);
664
+ }
665
+
666
+ handleInput(data: string): void {
667
+ if (matchesKey(data, HOTKEY)) {
668
+ triggerEditTurnCommand(this.base);
669
+ return;
670
+ }
671
+
672
+ if (this.onExtensionShortcut?.(data)) {
673
+ return;
674
+ }
675
+
676
+ if (this.keybindings.matches(data, "app.clipboard.pasteImage")) {
677
+ this.onPasteImage?.();
678
+ return;
679
+ }
680
+
681
+ if (this.keybindings.matches(data, "app.interrupt")) {
682
+ const isShowingAutocomplete = (this.base as AutocompleteAwareEditor).isShowingAutocomplete?.() ?? false;
683
+ if (!isShowingAutocomplete) {
684
+ const handler = this.onEscape ?? this.actionHandlers.get("app.interrupt");
685
+ if (handler) {
686
+ handler();
687
+ return;
688
+ }
689
+ }
690
+
691
+ this.base.handleInput(data);
692
+ return;
693
+ }
694
+
695
+ if (this.keybindings.matches(data, "app.exit") && this.base.getText().length === 0) {
696
+ const handler = this.onCtrlD ?? this.actionHandlers.get("app.exit");
697
+ if (handler) {
698
+ handler();
699
+ }
700
+ return;
701
+ }
702
+
703
+ for (const [action, handler] of this.actionHandlers) {
704
+ if (action !== "app.interrupt" && action !== "app.exit" && this.keybindings.matches(data, action)) {
705
+ handler();
706
+ return;
707
+ }
708
+ }
709
+
710
+ this.base.handleInput(data);
711
+ }
712
+
713
+ addToHistory(text: string): void {
714
+ this.base.addToHistory?.(text);
715
+ }
716
+
717
+ insertTextAtCursor(text: string): void {
718
+ this.base.insertTextAtCursor?.(text);
719
+ }
720
+
721
+ getExpandedText(): string {
722
+ return this.base.getExpandedText?.() ?? this.base.getText();
723
+ }
724
+
725
+ setAutocompleteProvider(provider: AutocompleteProvider): void {
726
+ this.base.setAutocompleteProvider?.(provider);
727
+ }
728
+
729
+ setPaddingX(padding: number): void {
730
+ this.base.setPaddingX?.(padding);
731
+ }
732
+
733
+ setAutocompleteMaxVisible(maxVisible: number): void {
734
+ this.base.setAutocompleteMaxVisible?.(maxVisible);
735
+ }
736
+ }
737
+
582
738
  export default function editSessionInPlace(pi: ExtensionAPI) {
583
739
  pi.registerCommand(COMMAND_NAME, {
584
740
  description: `Select and re-edit a previous user message on the current branch (${HOTKEY_LABEL})`,
@@ -587,9 +743,8 @@ export default function editSessionInPlace(pi: ExtensionAPI) {
587
743
  },
588
744
  });
589
745
 
590
- // pi 0.65.2 shortcut handlers receive ExtensionContext, which cannot run slash commands
591
- // or navigate the session tree. Keep the custom editor hotkey path for execution, and
592
- // register the shortcut here so it appears in /hotkeys and other shortcut diagnostics.
746
+ // Shortcut handlers receive ExtensionContext, so the custom editor hotkey path performs
747
+ // the command execution while this registration keeps the hotkey discoverable.
593
748
  pi.registerShortcut(HOTKEY, {
594
749
  description: `Edit a previous user message (${HOTKEY_LABEL})`,
595
750
  handler: (ctx) => {
@@ -601,15 +756,18 @@ export default function editSessionInPlace(pi: ExtensionAPI) {
601
756
  },
602
757
  });
603
758
 
604
- pi.on("session_start", async () => {
759
+ pi.on("session_start", (_event, ctx) => {
605
760
  clearSavedDraft();
761
+ if (ctx.mode === "tui") {
762
+ const previousEditor = ctx.ui.getEditorComponent();
763
+ ctx.ui.setEditorComponent((tui, theme, keybindings) => {
764
+ const baseEditor = previousEditor?.(tui, theme, keybindings);
765
+ return baseEditor ? new EditSessionInPlaceEditorWrapper(baseEditor, keybindings) : new EditSessionInPlaceEditor(tui, theme, keybindings);
766
+ });
767
+ }
606
768
  });
607
769
 
608
770
  pi.on("session_shutdown", async () => {
609
771
  clearSavedDraft();
610
772
  });
611
-
612
- pi.on("session_start", (_event, ctx) => {
613
- ctx.ui.setEditorComponent((tui, theme, keybindings) => new EditSessionInPlaceEditor(tui, theme, keybindings));
614
- });
615
773
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-edit-session-in-place",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "pi extension that lets you re-edit or delete an earlier user message in the current session branch",
5
5
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
6
6
  "license": "MIT",
@@ -42,11 +42,11 @@
42
42
  "@earendil-works/pi-tui": "*"
43
43
  },
44
44
  "engines": {
45
- "node": ">=22.19.0"
45
+ "node": ">=22 <25"
46
46
  },
47
47
  "devDependencies": {
48
- "@earendil-works/pi-coding-agent": "^0.77.0",
49
- "@earendil-works/pi-tui": "^0.77.0",
48
+ "@earendil-works/pi-coding-agent": "^0.78.1",
49
+ "@earendil-works/pi-tui": "^0.78.1",
50
50
  "@types/node": "^25.9.1",
51
51
  "typescript": "^6.0.3"
52
52
  },
@@ -58,7 +58,7 @@
58
58
  "./extensions"
59
59
  ]
60
60
  },
61
- "packageManager": "npm@11.16.0",
61
+ "packageManager": "npm@11.0.0",
62
62
  "peerDependenciesMeta": {
63
63
  "@earendil-works/pi-coding-agent": {
64
64
  "optional": true