pi-double-esc 1.0.14 → 1.0.16

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/double-esc.ts +37 -4
  3. package/package.json +8 -3
package/README.md CHANGED
@@ -101,3 +101,16 @@ npm run lint:dead # check for unused exports
101
101
  ## License
102
102
 
103
103
  MIT
104
+
105
+ ## Pi 1.0 compatibility (1.0.16)
106
+
107
+ Tested against Pi **1.0.0** with exact SDK development pins and wildcard host peers.
108
+ Editor composition, autocomplete Escape, timer cleanup and shutdown restoration remain intact.
109
+
110
+ Run `bun run test:host` for the offline real-host regression. Set `PI1_HOST_PACKAGE` to an installed Pi package directory and `PI1_HOST_ENTRY=bundle` to test its bundled CLI runtime.
111
+
112
+ ## Pi 0.99 compatibility (1.0.15)
113
+
114
+ Preserves single-Escape autocomplete dismissal, restores editor methods/factories and clears debounce timers on shutdown. Non-TUI sessions are untouched; cached render output is not mutated.
115
+
116
+ Tested with Pi 0.99.0. Host-provided Pi packages are wildcard peers, not bundled dependencies; development uses exact 0.99.0 versions.
package/double-esc.ts CHANGED
@@ -46,6 +46,7 @@ function decorateDoubleEscapeEditor(
46
46
  tui: TUI,
47
47
  appTheme: Theme,
48
48
  isIdle: () => boolean,
49
+ onCleanup: (cleanup: () => void) => void,
49
50
  ): EditorComponent {
50
51
  let escapeState: DoubleEscapeState = createInitialState();
51
52
  let debounceTimer: ReturnType<typeof setTimeout> | undefined;
@@ -55,8 +56,18 @@ function decorateDoubleEscapeEditor(
55
56
  debounceTimer = undefined;
56
57
  };
57
58
 
58
- const handleInput = editor.handleInput.bind(editor);
59
- editor.handleInput = (data: string): void => {
59
+ let active = true;
60
+ const originalHandleInput = editor.handleInput;
61
+ const originalRender = editor.render;
62
+ const handleInput = originalHandleInput.bind(editor);
63
+ const wrappedInput = (data: string): void => {
64
+ const autocomplete = editor as EditorComponent & { isShowingAutocomplete?: () => boolean };
65
+ if (!active || autocomplete.isShowingAutocomplete?.()) {
66
+ clearDebounce();
67
+ escapeState = createInitialState();
68
+ handleInput(data);
69
+ return;
70
+ }
60
71
  if (!matchesKey(data, "escape")) {
61
72
  if (escapeState.hintActive) {
62
73
  escapeState = handleOtherKey(escapeState).state;
@@ -86,7 +97,7 @@ function decorateDoubleEscapeEditor(
86
97
 
87
98
  const render = editor.render.bind(editor);
88
99
  editor.render = (width: number): string[] => {
89
- const lines = render(width);
100
+ const lines = [...render(width)];
90
101
  if (!escapeState.hintActive || lines.length === 0) return lines;
91
102
 
92
103
  const label = " esc again to abort ";
@@ -104,6 +115,15 @@ function decorateDoubleEscapeEditor(
104
115
  return lines;
105
116
  };
106
117
 
118
+ editor.handleInput = wrappedInput;
119
+ const wrappedRender = editor.render;
120
+ onCleanup(() => {
121
+ active = false;
122
+ clearDebounce();
123
+ escapeState = createInitialState();
124
+ if (editor.handleInput === wrappedInput) editor.handleInput = originalHandleInput;
125
+ if (editor.render === wrappedRender) editor.render = originalRender;
126
+ });
107
127
  return editor;
108
128
  }
109
129
 
@@ -117,7 +137,11 @@ type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorCom
117
137
  type DecoratedEditorFactory = EditorFactory & { [DECORATED_FACTORY]?: true };
118
138
 
119
139
  export default function (pi: ExtensionAPI) {
140
+ const cleanups = new Set<() => void>();
141
+ let installed: DecoratedEditorFactory | undefined;
142
+ let previous: EditorFactory | undefined;
120
143
  pi.on("session_start", (_event, ctx) => {
144
+ if (ctx.mode !== "tui") return;
121
145
  const previousEditorFactory = ctx.ui.getEditorComponent() as DecoratedEditorFactory | undefined;
122
146
  if (previousEditorFactory?.[DECORATED_FACTORY]) return;
123
147
 
@@ -125,10 +149,19 @@ export default function (pi: ExtensionAPI) {
125
149
  const editor =
126
150
  previousEditorFactory?.(tui, editorTheme, keybindings) ??
127
151
  new CustomEditor(tui, editorTheme, keybindings);
128
- return decorateDoubleEscapeEditor(editor, tui, ctx.ui.theme, () => ctx.isIdle());
152
+ return decorateDoubleEscapeEditor(editor, tui, ctx.ui.theme, () => ctx.isIdle(), (cleanup) => cleanups.add(cleanup));
129
153
  };
130
154
  editorFactory[DECORATED_FACTORY] = true;
131
155
 
156
+ previous = previousEditorFactory;
157
+ installed = editorFactory;
132
158
  ctx.ui.setEditorComponent(editorFactory);
133
159
  });
160
+ pi.on("session_shutdown", (_event, ctx) => {
161
+ for (const cleanup of cleanups) cleanup();
162
+ cleanups.clear();
163
+ if (installed && ctx.ui.getEditorComponent() === installed) ctx.ui.setEditorComponent(previous);
164
+ installed = undefined;
165
+ previous = undefined;
166
+ });
134
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-double-esc",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Prevent accidental Escape from aborting the LLM — requires double-press to interrupt while streaming",
5
5
  "type": "module",
6
6
  "author": "Tom X Nguyen",
@@ -30,6 +30,7 @@
30
30
  "README.md"
31
31
  ],
32
32
  "scripts": {
33
+ "test:host": "node scripts/pi1-host.mjs",
33
34
  "test": "vitest run",
34
35
  "test:watch": "vitest",
35
36
  "test:coverage": "vitest run --coverage",
@@ -37,8 +38,8 @@
37
38
  "lint:dead": "knip --no-gitignore"
38
39
  },
39
40
  "devDependencies": {
40
- "@earendil-works/pi-coding-agent": "0.87.0",
41
- "@earendil-works/pi-tui": "0.87.0",
41
+ "@earendil-works/pi-coding-agent": "1.0.0",
42
+ "@earendil-works/pi-tui": "1.0.0",
42
43
  "@types/node": "25.9.1",
43
44
  "@vitest/coverage-v8": "4.1.7",
44
45
  "knip": "6.14.1",
@@ -52,5 +53,9 @@
52
53
  },
53
54
  "overrides": {
54
55
  "brace-expansion": "5.0.6"
56
+ },
57
+ "peerDependencies": {
58
+ "@earendil-works/pi-coding-agent": "*",
59
+ "@earendil-works/pi-tui": "*"
55
60
  }
56
61
  }