pi-edit-session-in-place 0.1.14 → 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,17 @@ 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
+
7
18
  ## [0.1.14] - 2026-05-29
8
19
 
9
20
  ### 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.78.0`
10
- - `@earendil-works/pi-tui` `0.78.0`
9
+ - `@earendil-works/pi-coding-agent` `0.78.1`
10
+ - `@earendil-works/pi-tui` `0.78.1`
11
11
  - Node.js `>=22 <25`
12
12
 
13
- Local development and verification in this repo target pi `0.78.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,
@@ -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})`,
@@ -600,15 +756,18 @@ export default function editSessionInPlace(pi: ExtensionAPI) {
600
756
  },
601
757
  });
602
758
 
603
- pi.on("session_start", async () => {
759
+ pi.on("session_start", (_event, ctx) => {
604
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
+ }
605
768
  });
606
769
 
607
770
  pi.on("session_shutdown", async () => {
608
771
  clearSavedDraft();
609
772
  });
610
-
611
- pi.on("session_start", (_event, ctx) => {
612
- ctx.ui.setEditorComponent((tui, theme, keybindings) => new EditSessionInPlaceEditor(tui, theme, keybindings));
613
- });
614
773
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-edit-session-in-place",
3
- "version": "0.1.14",
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",
@@ -45,8 +45,8 @@
45
45
  "node": ">=22 <25"
46
46
  },
47
47
  "devDependencies": {
48
- "@earendil-works/pi-coding-agent": "^0.78.0",
49
- "@earendil-works/pi-tui": "^0.78.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
  },