pi-double-esc 1.0.16 → 1.0.18

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 CHANGED
@@ -104,7 +104,7 @@ MIT
104
104
 
105
105
  ## Pi 1.0 compatibility (1.0.16)
106
106
 
107
- Tested against Pi **1.0.0** with exact SDK development pins and wildcard host peers.
107
+ Tested against Pi **1.2.0** with exact SDK development pins and wildcard host peers.
108
108
  Editor composition, autocomplete Escape, timer cleanup and shutdown restoration remain intact.
109
109
 
110
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.
package/double-esc.ts CHANGED
@@ -134,24 +134,53 @@ function decorateDoubleEscapeEditor(
134
134
  const DECORATED_FACTORY = Symbol("pi-double-esc.decoratedFactory");
135
135
 
136
136
  type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>>;
137
- type DecoratedEditorFactory = EditorFactory & { [DECORATED_FACTORY]?: true };
137
+ // Shared with cooperating editor decorators: inactive factories are skipped
138
+ // when an outer owner later unwinds, regardless of shutdown order.
139
+ const EDITOR_FACTORY_PATCH = Symbol.for("pi.editor-factory.patch.v1");
140
+ type DecoratedEditorFactory = EditorFactory & {
141
+ [DECORATED_FACTORY]?: true;
142
+ [EDITOR_FACTORY_PATCH]?: { active: boolean; original?: EditorFactory };
143
+ };
144
+ function unwrapEditorFactory(factory: EditorFactory | undefined): EditorFactory | undefined {
145
+ while (factory) {
146
+ const state = (factory as DecoratedEditorFactory)[EDITOR_FACTORY_PATCH];
147
+ if (!state || state.active) return factory;
148
+ factory = state.original;
149
+ }
150
+ return undefined;
151
+ }
152
+
153
+ function hasDoubleEscape(factory: EditorFactory | undefined): boolean {
154
+ while (factory) {
155
+ const wrapped = factory as DecoratedEditorFactory;
156
+ const state = wrapped[EDITOR_FACTORY_PATCH];
157
+ if (state?.active !== false && wrapped[DECORATED_FACTORY]) return true;
158
+ factory = state?.original;
159
+ }
160
+ return false;
161
+ }
138
162
 
139
163
  export default function (pi: ExtensionAPI) {
140
164
  const cleanups = new Set<() => void>();
141
165
  let installed: DecoratedEditorFactory | undefined;
142
166
  let previous: EditorFactory | undefined;
167
+ const factoryStates: { active: boolean; original?: EditorFactory }[] = [];
143
168
  pi.on("session_start", (_event, ctx) => {
144
169
  if (ctx.mode !== "tui") return;
145
- const previousEditorFactory = ctx.ui.getEditorComponent() as DecoratedEditorFactory | undefined;
146
- if (previousEditorFactory?.[DECORATED_FACTORY]) return;
170
+ const previousEditorFactory = unwrapEditorFactory(ctx.ui.getEditorComponent()) as DecoratedEditorFactory | undefined;
171
+ if (hasDoubleEscape(previousEditorFactory)) return;
147
172
 
173
+ const state = { active: true, original: previousEditorFactory };
148
174
  const editorFactory: DecoratedEditorFactory = (tui, editorTheme, keybindings) => {
149
175
  const editor =
150
- previousEditorFactory?.(tui, editorTheme, keybindings) ??
176
+ unwrapEditorFactory(previousEditorFactory)?.(tui, editorTheme, keybindings) ??
151
177
  new CustomEditor(tui, editorTheme, keybindings);
178
+ if (!state.active) return editor;
152
179
  return decorateDoubleEscapeEditor(editor, tui, ctx.ui.theme, () => ctx.isIdle(), (cleanup) => cleanups.add(cleanup));
153
180
  };
154
181
  editorFactory[DECORATED_FACTORY] = true;
182
+ editorFactory[EDITOR_FACTORY_PATCH] = state;
183
+ factoryStates.push(state);
155
184
 
156
185
  previous = previousEditorFactory;
157
186
  installed = editorFactory;
@@ -160,7 +189,9 @@ export default function (pi: ExtensionAPI) {
160
189
  pi.on("session_shutdown", (_event, ctx) => {
161
190
  for (const cleanup of cleanups) cleanup();
162
191
  cleanups.clear();
163
- if (installed && ctx.ui.getEditorComponent() === installed) ctx.ui.setEditorComponent(previous);
192
+ for (const state of factoryStates) state.active = false;
193
+ factoryStates.length = 0;
194
+ if (installed && ctx.ui.getEditorComponent() === installed) ctx.ui.setEditorComponent(unwrapEditorFactory(previous));
164
195
  installed = undefined;
165
196
  previous = undefined;
166
197
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-double-esc",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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",
@@ -38,8 +38,8 @@
38
38
  "lint:dead": "knip --no-gitignore"
39
39
  },
40
40
  "devDependencies": {
41
- "@earendil-works/pi-coding-agent": "1.0.0",
42
- "@earendil-works/pi-tui": "1.0.0",
41
+ "@earendil-works/pi-coding-agent": "1.2.0",
42
+ "@earendil-works/pi-tui": "1.2.0",
43
43
  "@types/node": "25.9.1",
44
44
  "@vitest/coverage-v8": "4.1.7",
45
45
  "knip": "6.14.1",
@@ -52,7 +52,7 @@
52
52
  ]
53
53
  },
54
54
  "overrides": {
55
- "brace-expansion": "5.0.6"
55
+ "brace-expansion": "5.0.12"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@earendil-works/pi-coding-agent": "*",