@oh-my-pi/pi-natives 17.4.2 → 18.0.1

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.
@@ -0,0 +1,28 @@
1
+ interface AdaptedDesktopCapabilities {
2
+ readonly [key: string]: unknown;
3
+ readonly ax: boolean;
4
+ readonly backgroundWindowInput: boolean;
5
+ readonly deliveryModes: readonly string[];
6
+ readonly axPermission: string;
7
+ }
8
+
9
+ interface AdaptedDesktopSession {
10
+ readonly capabilities: AdaptedDesktopCapabilities;
11
+ listWindows(): Promise<Array<Record<string, unknown>>>;
12
+ capture(target: string, caps?: unknown): Promise<Record<string, unknown>>;
13
+ click(
14
+ target: string,
15
+ x: number,
16
+ y: number,
17
+ options?: { button?: string; count?: number; modifiers?: string[]; deliveryMode?: string },
18
+ ): Promise<void>;
19
+ typeText(target: string, text: string, options?: { deliveryMode?: string }): Promise<void>;
20
+ keyChord(target: string, keys: string[], options?: { deliveryMode?: string }): Promise<void>;
21
+ close(): Promise<void>;
22
+ }
23
+
24
+ interface AdaptedDesktopSessionConstructor {
25
+ new (options: Record<string, unknown>): AdaptedDesktopSession;
26
+ }
27
+
28
+ export function adaptDesktopSession(NativeDesktopSession: unknown): AdaptedDesktopSessionConstructor;
@@ -0,0 +1,341 @@
1
+ const LEGACY_ERROR_CODES = {
2
+ DESKTOP_INVALID_OPTIONS: "InvalidTarget",
3
+ DESKTOP_INVALID_ACTION: "InvalidTarget",
4
+ DESKTOP_BACKEND_UNAVAILABLE: null,
5
+ DESKTOP_PERMISSION_DENIED: "PermissionDenied",
6
+ DESKTOP_CAPTURE_FAILED: "CaptureFailed",
7
+ DESKTOP_INPUT_FAILED: "InputFailed",
8
+ DESKTOP_DEADLINE_EXCEEDED: "Timeout",
9
+ DESKTOP_LAYOUT_CHANGED: "InvalidCoordinateFrame",
10
+ DESKTOP_COORDINATE_OUT_OF_BOUNDS: "InvalidCoordinateFrame",
11
+ DESKTOP_SESSION_CLOSED: "Closed",
12
+ DESKTOP_WORKER_FAILED: "Internal",
13
+ };
14
+
15
+ const ADAPTED_SESSION_CLASSES = new WeakMap();
16
+
17
+ function desktopError(code, message) {
18
+ return new Error(`${code}: ${message}`);
19
+ }
20
+
21
+ function normalizeError(error, fallbackCode) {
22
+ if (!(error instanceof Error)) return desktopError(fallbackCode, String(error));
23
+ if (/^[A-Z][A-Za-z]+: /.test(error.message)) return error;
24
+
25
+ const match = /^(DESKTOP_[A-Z_]+):\s*(.*)$/.exec(error.message);
26
+ if (match === null) return desktopError(fallbackCode, error.message);
27
+ const code = LEGACY_ERROR_CODES[match[1]] ?? fallbackCode;
28
+ return desktopError(code, match[2]);
29
+ }
30
+
31
+ function normalizeCapabilities(capabilities) {
32
+ return {
33
+ ...capabilities,
34
+ ax: false,
35
+ backgroundWindowInput: false,
36
+ deliveryModes: ["foreground"],
37
+ axPermission: "unavailable",
38
+ };
39
+ }
40
+
41
+ function legacyPoint(point) {
42
+ return { x: Math.round(point.x), y: Math.round(point.y) };
43
+ }
44
+
45
+ function captureCapsKey(caps) {
46
+ return `${caps?.maxWidth ?? ""}:${caps?.maxHeight ?? ""}`;
47
+ }
48
+
49
+ function legacyButton(button) {
50
+ return button === "middle" ? "wheel" : button;
51
+ }
52
+ function sourceDimensions(capture) {
53
+ if (capture.sourceWidth !== undefined && capture.sourceHeight !== undefined) {
54
+ return { sourceWidth: capture.sourceWidth, sourceHeight: capture.sourceHeight };
55
+ }
56
+ const displays = Array.isArray(capture.displays) ? capture.displays : [];
57
+ if (displays.length === 0) {
58
+ return { sourceWidth: capture.width, sourceHeight: capture.height };
59
+ }
60
+ const minX = Math.min(...displays.map(display => display.x));
61
+ const minY = Math.min(...displays.map(display => display.y));
62
+ const maxX = Math.max(...displays.map(display => display.x + display.width));
63
+ const maxY = Math.max(...displays.map(display => display.y + display.height));
64
+ const nativeScale = Math.max(1, ...displays.map(display => display.scale ?? 1));
65
+ return {
66
+ sourceWidth: Math.max(1, Math.round((maxX - minX) * nativeScale)),
67
+ sourceHeight: Math.max(1, Math.round((maxY - minY) * nativeScale)),
68
+ };
69
+ }
70
+
71
+ function frameSignature(capture) {
72
+ return JSON.stringify({
73
+ target: capture.target,
74
+ width: capture.width,
75
+ height: capture.height,
76
+ displays: capture.displays?.map(display => ({
77
+ id: display.id,
78
+ x: display.x,
79
+ y: display.y,
80
+ width: display.width,
81
+ height: display.height,
82
+ scale: display.scale,
83
+ pixelX: display.pixelX,
84
+ pixelY: display.pixelY,
85
+ pixelWidth: display.pixelWidth,
86
+ pixelHeight: display.pixelHeight,
87
+ })),
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Adapt the pre-parity desktop addon ABI used by pull-request CI artifacts to
93
+ * the current session contract. Released addons exposed capture/execute/close;
94
+ * current addons already expose the complete API and pass through unchanged.
95
+ */
96
+ export function adaptDesktopSession(NativeDesktopSession) {
97
+ if (typeof NativeDesktopSession?.prototype?.click === "function") return NativeDesktopSession;
98
+ const cached = ADAPTED_SESSION_CLASSES.get(NativeDesktopSession);
99
+ if (cached) return cached;
100
+
101
+ class DesktopSession {
102
+ #native;
103
+ #nativeDesktopSession;
104
+ #options;
105
+ #sessions;
106
+ #closed = false;
107
+ #capturedTargets = new Map();
108
+
109
+ constructor(options) {
110
+ try {
111
+ this.#nativeDesktopSession = NativeDesktopSession;
112
+ this.#options = options;
113
+ this.#native = new NativeDesktopSession(options);
114
+ this.#sessions = new Map([[captureCapsKey(), this.#native]]);
115
+ } catch (error) {
116
+ throw normalizeError(error, "Internal");
117
+ }
118
+ }
119
+
120
+ get capabilities() {
121
+ return normalizeCapabilities(this.#native.capabilities);
122
+ }
123
+
124
+ #ensureOpen() {
125
+ if (this.#closed) throw desktopError("Closed", "desktop session is closed");
126
+ }
127
+
128
+ #nativeForCapturedTarget(target) {
129
+ const capture = this.#capturedTargets.get(target);
130
+ if (capture) return capture.native;
131
+ throw desktopError(
132
+ "InvalidCoordinateFrame",
133
+ `no capture of '${target}' yet — take a screenshot of this target first`,
134
+ );
135
+ }
136
+
137
+ #sessionForCapture(caps) {
138
+ const key = captureCapsKey(caps);
139
+ const existing = this.#sessions.get(key);
140
+ if (existing) return existing;
141
+ try {
142
+ const native = new this.#nativeDesktopSession({
143
+ ...this.#options,
144
+ maxWidth: caps?.maxWidth,
145
+ maxHeight: caps?.maxHeight,
146
+ });
147
+ this.#sessions.set(key, native);
148
+ return native;
149
+ } catch (error) {
150
+ throw normalizeError(error, "CaptureFailed");
151
+ }
152
+ }
153
+
154
+ #ensureForeground(target, options) {
155
+ if (options?.deliveryMode !== "foreground" && (target !== "desktop" || options?.deliveryMode !== undefined)) {
156
+ throw desktopError(
157
+ "BackgroundUnavailable",
158
+ "the installed native addon supports foreground input only",
159
+ );
160
+ }
161
+ }
162
+
163
+ async #execute(actions, target, native = this.#native, fallbackCode = "InputFailed") {
164
+ try {
165
+ const capture = await native.execute(Array.isArray(actions) ? actions : [actions], target);
166
+ const previous = this.#capturedTargets.get(target);
167
+ if (capture && previous?.native === native && frameSignature(capture) !== previous.signature) {
168
+ this.#capturedTargets.delete(target);
169
+ }
170
+ } catch (error) {
171
+ throw normalizeError(error, fallbackCode);
172
+ }
173
+ }
174
+
175
+ async listDisplays() {
176
+ this.#ensureOpen();
177
+ throw desktopError("CaptureFailed", "the installed native addon does not expose display enumeration");
178
+ }
179
+
180
+ async listWindows() {
181
+ this.#ensureOpen();
182
+ if (typeof this.#native.listWindows !== "function") {
183
+ throw desktopError("CaptureFailed", "the installed native addon does not expose window enumeration");
184
+ }
185
+ try {
186
+ return await this.#native.listWindows();
187
+ } catch (error) {
188
+ throw normalizeError(error, "CaptureFailed");
189
+ }
190
+ }
191
+
192
+ async capture(target, caps) {
193
+ this.#ensureOpen();
194
+ if (target !== "desktop" && !/^\d+$/.test(target)) {
195
+ throw desktopError("InvalidTarget", `invalid window target '${target}'`);
196
+ }
197
+ if (target !== "desktop" && typeof this.#native.listWindows !== "function") {
198
+ throw desktopError("CaptureFailed", "the installed native addon does not support window capture");
199
+ }
200
+ try {
201
+ const native = this.#sessionForCapture(caps);
202
+ const capture = await native.capture(target);
203
+ const adapted = { ...capture, ...sourceDimensions(capture), target };
204
+ this.#capturedTargets.set(target, { native, signature: frameSignature(adapted) });
205
+ return adapted;
206
+ } catch (error) {
207
+ throw normalizeError(error, "CaptureFailed");
208
+ }
209
+ }
210
+
211
+ async click(target, x, y, options) {
212
+ this.#ensureOpen();
213
+ this.#ensureForeground(target, options);
214
+ const native = this.#nativeForCapturedTarget(target);
215
+ const count = Math.max(1, options?.count ?? 1);
216
+ const button = legacyButton(options?.button ?? "left");
217
+ const point = { x: Math.round(x), y: Math.round(y), keys: options?.modifiers ?? [] };
218
+ const actions =
219
+ count === 2 && button === "left"
220
+ ? [{ type: "double_click", ...point }]
221
+ : Array.from({ length: count }, () => ({ type: "click", ...point, button }));
222
+ await this.#execute(actions, target, native);
223
+ }
224
+
225
+ async moveMouse(target, x, y, options) {
226
+ this.#ensureOpen();
227
+ this.#ensureForeground(target, options);
228
+ await this.#execute(
229
+ { type: "move", x: Math.round(x), y: Math.round(y), keys: options?.modifiers ?? [] },
230
+ target,
231
+ this.#nativeForCapturedTarget(target),
232
+ );
233
+ }
234
+
235
+ async drag(target, path, options) {
236
+ this.#ensureOpen();
237
+ this.#ensureForeground(target, options);
238
+ await this.#execute(
239
+ { type: "drag", path: path.map(legacyPoint), keys: options?.modifiers ?? [] },
240
+ target,
241
+ this.#nativeForCapturedTarget(target),
242
+ );
243
+ }
244
+
245
+ async scroll(target, x, y, dx, dy, options) {
246
+ this.#ensureOpen();
247
+ this.#ensureForeground(target, options);
248
+ await this.#execute(
249
+ {
250
+ type: "scroll",
251
+ x: Math.round(x),
252
+ y: Math.round(y),
253
+ scroll_x: Math.round(dx),
254
+ scroll_y: Math.round(dy),
255
+ keys: options?.modifiers ?? [],
256
+ },
257
+ target,
258
+ this.#nativeForCapturedTarget(target),
259
+ );
260
+ }
261
+
262
+ async typeText(target, text, options) {
263
+ this.#ensureOpen();
264
+ this.#ensureForeground(target, options);
265
+ await this.#execute({ type: "type", text }, target, this.#capturedTargets.get(target)?.native ?? this.#native);
266
+ }
267
+
268
+ async keyChord(target, keys, options) {
269
+ this.#ensureOpen();
270
+ this.#ensureForeground(target, options);
271
+ await this.#execute({ type: "keypress", keys }, target, this.#capturedTargets.get(target)?.native ?? this.#native);
272
+ }
273
+
274
+ async raiseWindow() {
275
+ this.#ensureOpen();
276
+ throw desktopError("BackgroundUnavailable", "the installed native addon does not support window control");
277
+ }
278
+
279
+ async axSnapshot() {
280
+ this.#ensureOpen();
281
+ throw desktopError("AxUnsupported", "accessibility is unavailable in the installed native addon");
282
+ }
283
+
284
+ async axQuery() {
285
+ return this.axSnapshot();
286
+ }
287
+
288
+ async axElementAt() {
289
+ return this.axSnapshot();
290
+ }
291
+
292
+ async axFocused() {
293
+ return this.axSnapshot();
294
+ }
295
+
296
+ async axNode() {
297
+ return this.axSnapshot();
298
+ }
299
+
300
+ async axAttributes() {
301
+ return this.axSnapshot();
302
+ }
303
+
304
+ async axChildren() {
305
+ return this.axSnapshot();
306
+ }
307
+
308
+ async axParent() {
309
+ return this.axSnapshot();
310
+ }
311
+
312
+ async axPerform() {
313
+ return this.axSnapshot();
314
+ }
315
+
316
+ async axSetValue() {
317
+ return this.axSnapshot();
318
+ }
319
+
320
+ async axFocus() {
321
+ return this.axSnapshot();
322
+ }
323
+
324
+ async axClick() {
325
+ return this.axSnapshot();
326
+ }
327
+
328
+ async close() {
329
+ if (this.#closed) return;
330
+ this.#closed = true;
331
+ try {
332
+ await Promise.all([...this.#sessions.values()].map(native => native.close()));
333
+ } catch (error) {
334
+ throw normalizeError(error, "Internal");
335
+ }
336
+ }
337
+ }
338
+
339
+ ADAPTED_SESSION_CLASSES.set(NativeDesktopSession, DesktopSession);
340
+ return DesktopSession;
341
+ }
package/native/desktop.js CHANGED
@@ -1,10 +1,13 @@
1
+ import { adaptDesktopSession } from "./desktop-adapter.js";
1
2
  import { loadNative } from "./loader-state.js";
2
3
 
4
+ let DesktopSession;
5
+
3
6
  /**
4
7
  * Construct a desktop session without loading the native addon until the
5
8
  * computer worker receives its initialization message.
6
9
  */
7
10
  export function createDesktopSession(options) {
8
- const { DesktopSession } = loadNative();
11
+ DesktopSession ??= adaptDesktopSession(loadNative().DesktopSession);
9
12
  return new DesktopSession(options);
10
13
  }
package/native/index.d.ts CHANGED
@@ -77,6 +77,24 @@ export declare class FileLock {
77
77
  release(): void
78
78
  }
79
79
 
80
+ /**
81
+ * Stateful incremental syntax highlighter for streamed code.
82
+ *
83
+ * Carries syntect parser state across [`HighlightStream::push`] calls so
84
+ * chunked highlighting of a growing buffer is byte-identical to highlighting
85
+ * the concatenated text in one call. Feed newline-terminated complete lines;
86
+ * only the final push may omit the trailing newline. An unresolved language
87
+ * echoes input unchanged.
88
+ */
89
+ export declare class HighlightStream {
90
+ /** Create a stream for `lang`; an unknown language yields a passthrough. */
91
+ constructor(lang: string | undefined | null, colors: HighlightColors)
92
+ /** Whether the language resolved to a grammar; `false` means passthrough. */
93
+ get supported(): boolean
94
+ /** Highlight the next chunk and advance parser state. */
95
+ push(chunk: string): string
96
+ }
97
+
80
98
  /** WebRTC peer that accepts 16 kHz mono PCM and renders remote Opus audio. */
81
99
  export declare class LiveWebRtcPeer {
82
100
  /**
@@ -234,6 +252,48 @@ export declare class Shell {
234
252
  liveBackgroundJobCount(): Promise<number>
235
253
  }
236
254
 
255
+ /**
256
+ * Dedicated writer thread for one terminal fd.
257
+ *
258
+ * Constructed by the TUI's `ProcessTerminal` around stdout. The fd is
259
+ * `dup(2)`'d at construction and closed on drop, so later manipulation of the
260
+ * original descriptor does not affect the pump.
261
+ */
262
+ export declare class TtyWriter {
263
+ /**
264
+ * Start a pump thread for `fd` (typically 1). Fails on non-Unix hosts and
265
+ * when the descriptor cannot be duplicated.
266
+ */
267
+ constructor(fd: number)
268
+ /**
269
+ * Enqueue terminal output; never blocks. Returns the total bytes now
270
+ * pending (including this chunk).
271
+ *
272
+ * Reads the JS string as UTF-16 through the thread's scratch arena and
273
+ * transcodes it with `xutf` straight into the shared back buffer, so a
274
+ * warm writer costs no per-call heap allocation.
275
+ */
276
+ write(data: string): number
277
+ /** Bytes accepted but not yet written to the terminal. */
278
+ pending(): number
279
+ /** True once a write failed (dead PTY); queued output has been dropped. */
280
+ get dead(): boolean
281
+ /**
282
+ * Block the calling thread until the queue drains, the writer dies, or
283
+ * `timeout_ms` elapses. Returns true when fully drained. Exit paths only.
284
+ */
285
+ flushSync(timeoutMs: number): boolean
286
+ /**
287
+ * Flush (bounded by `flush_timeout_ms`), stop the pump thread, and join it.
288
+ *
289
+ * A pump stuck in a blocked `write(2)` (stalled-but-alive PTY consumer)
290
+ * cannot be joined without freezing the caller: when the bounded flush
291
+ * times out the thread is detached instead and its dup'd fd is leaked —
292
+ * closing it under a blocked write would race kernel fd reuse.
293
+ */
294
+ stop(flushTimeoutMs: number): void
295
+ }
296
+
237
297
  /**
238
298
  * Install the bounded Tokio runtime napi-rs adopts for async exports and the
239
299
  * bounded Rayon global pool used by native parallel iterators.
@@ -279,7 +339,7 @@ export declare function __ompInstallTokioRuntime(): void
279
339
  * `packages/natives/native/index.js` (which derives the name from
280
340
  * `package.json#version`).
281
341
  */
282
- export declare function __piNativesV17_4_2(): void
342
+ export declare function __piNativesV18_0_1(): void
283
343
 
284
344
  /**
285
345
  * Apply ast-grep rewrite rules to matching files; honors `dryRun` and returns
@@ -1399,6 +1459,31 @@ export declare enum MacOSAppearance {
1399
1459
  Light = 'light'
1400
1460
  }
1401
1461
 
1462
+ /**
1463
+ * Return the autocorrection macOS chooses for one completed-word range.
1464
+ *
1465
+ * Returns `null` when no confident correction exists or the service is
1466
+ * unavailable.
1467
+ * On macOS, the lookup runs on the dedicated spelling thread.
1468
+ */
1469
+ export declare function macOSAutocorrectWord(text: string, start: number, length: number): Promise<string | null>
1470
+
1471
+ /**
1472
+ * Find every misspelled word using the active macOS dictionaries.
1473
+ *
1474
+ * Returns an empty list when Apple's spelling service is unavailable.
1475
+ * On macOS, the check runs on the dedicated spelling thread.
1476
+ */
1477
+ export declare function macOSCheckSpelling(text: string): Promise<Array<SpellingRange>>
1478
+
1479
+ /**
1480
+ * Return macOS dictionary completions for one partial-word range.
1481
+ *
1482
+ * Returns an empty list when Apple's spelling service is unavailable.
1483
+ * On macOS, the lookup runs on the dedicated spelling thread.
1484
+ */
1485
+ export declare function macOSCompleteWord(text: string, start: number, length: number): Promise<Array<string>>
1486
+
1402
1487
  /**
1403
1488
  * Options for starting a macOS power assertion.
1404
1489
  *
@@ -1423,6 +1508,17 @@ export interface MacOSPowerAssertionOptions {
1423
1508
  display?: boolean
1424
1509
  }
1425
1510
 
1511
+ /** Whether the host can use Apple's native spelling service. */
1512
+ export declare function macOSSpellCheckerAvailable(): boolean
1513
+
1514
+ /**
1515
+ * Return macOS replacement guesses for one misspelled-word range.
1516
+ *
1517
+ * Returns an empty list when Apple's spelling service is unavailable.
1518
+ * On macOS, the lookup runs on the dedicated spelling thread.
1519
+ */
1520
+ export declare function macOSSpellingGuesses(text: string, start: number, length: number): Promise<Array<string>>
1521
+
1426
1522
  /** A single match in the content. */
1427
1523
  export interface Match {
1428
1524
  /** 1-indexed line number. */
@@ -1952,6 +2048,14 @@ export interface SnapcompactRenderOptions {
1952
2048
  */
1953
2049
  export declare function snapcompactSupportedChars(font: string, chars: string): string
1954
2050
 
2051
+ /** A misspelled span measured in JavaScript/UTF-16 code units. */
2052
+ export interface SpellingRange {
2053
+ /** Inclusive UTF-16 start offset. */
2054
+ start: number
2055
+ /** UTF-16 length of the misspelled span. */
2056
+ length: number
2057
+ }
2058
+
1955
2059
  /**
1956
2060
  * Unified-diff hunks with jsdiff
1957
2061
  * `structuredPatch(_, _, oldText, newText, _, _, { context }).hunks`
package/native/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { loadNative } from "./loader-state.js";
2
+ import { adaptDesktopSession } from "./desktop-adapter.js";
2
3
 
3
4
  /**
4
5
  * Native addon entrypoint.
@@ -18,18 +19,20 @@ const nativeBindings = loadNative();
18
19
  // classes
19
20
  export const AudioCapture = nativeBindings.AudioCapture;
20
21
  export const AudioPlayback = nativeBindings.AudioPlayback;
21
- export const DesktopSession = nativeBindings.DesktopSession;
22
+ export const DesktopSession = adaptDesktopSession(nativeBindings.DesktopSession);
22
23
  export const FileLock = nativeBindings.FileLock;
24
+ export const HighlightStream = nativeBindings.HighlightStream;
23
25
  export const LiveWebRtcPeer = nativeBindings.LiveWebRtcPeer;
24
26
  export const MacAppearanceObserver = nativeBindings.MacAppearanceObserver;
25
27
  export const MacOSPowerAssertion = nativeBindings.MacOSPowerAssertion;
26
28
  export const Process = nativeBindings.Process;
27
29
  export const PtySession = nativeBindings.PtySession;
28
30
  export const Shell = nativeBindings.Shell;
31
+ export const TtyWriter = nativeBindings.TtyWriter;
29
32
 
30
33
  // functions
31
34
  export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime;
32
- export const __piNativesV17_4_2 = nativeBindings.__piNativesV17_4_2;
35
+ export const __piNativesV18_0_1 = nativeBindings.__piNativesV18_0_1;
33
36
  export const astEdit = nativeBindings.astEdit;
34
37
  export const astGrep = nativeBindings.astGrep;
35
38
  export const astMatch = nativeBindings.astMatch;
@@ -63,6 +66,11 @@ export const isoResolve = nativeBindings.isoResolve;
63
66
  export const isoStart = nativeBindings.isoStart;
64
67
  export const isoStop = nativeBindings.isoStop;
65
68
  export const listWorkspace = nativeBindings.listWorkspace;
69
+ export const macOSAutocorrectWord = nativeBindings.macOSAutocorrectWord;
70
+ export const macOSCheckSpelling = nativeBindings.macOSCheckSpelling;
71
+ export const macOSCompleteWord = nativeBindings.macOSCompleteWord;
72
+ export const macOSSpellCheckerAvailable = nativeBindings.macOSSpellCheckerAvailable;
73
+ export const macOSSpellingGuesses = nativeBindings.macOSSpellingGuesses;
66
74
  export const matchesKey = nativeBindings.matchesKey;
67
75
  export const matchesKittySequence = nativeBindings.matchesKittySequence;
68
76
  export const matchesLegacySequence = nativeBindings.matchesLegacySequence;
@@ -648,6 +648,27 @@ function maybeStageNodeModulesAddon(ctx, errors) {
648
648
  return stagedPath;
649
649
  }
650
650
 
651
+
652
+ /**
653
+ * Before version sentinels were exported, published native addons still shared
654
+ * this stable core ABI. Let those on-disk addons bridge a package-version bump
655
+ * when they expose the signature; keep every versioned addon and a current
656
+ * on-disk file paired with resident old exports on the strict path below.
657
+ */
658
+ function isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel) {
659
+ if (diskHasExpectedSentinel) return false;
660
+ if (Object.keys(bindings).some(key => /^__piNativesV[A-Za-z0-9_]+$/.test(key))) return false;
661
+ return (
662
+ typeof bindings.countTokens === "function" &&
663
+ typeof bindings.executeShell === "function" &&
664
+ typeof bindings.visibleWidth === "function" &&
665
+ typeof bindings.DesktopSession === "function" &&
666
+ typeof bindings.DesktopSession.prototype?.capture === "function" &&
667
+ typeof bindings.DesktopSession.prototype?.execute === "function" &&
668
+ typeof bindings.DesktopSession.prototype?.close === "function"
669
+ );
670
+ }
671
+
651
672
  export function validateLoadedBindings(ctx, bindings, candidate) {
652
673
  // In workspace dev (running out of `packages/natives/native/` rather than a
653
674
  // `node_modules` install or a compiled bundle) the local `.node` only gains
@@ -680,6 +701,7 @@ export function validateLoadedBindings(ctx, bindings, candidate) {
680
701
  // The successful require above normally guarantees readability. If the
681
702
  // file disappears concurrently, retain the safe reinstall diagnosis.
682
703
  }
704
+ if (isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel)) return;
683
705
  if (residentSentinel && diskHasExpectedSentinel) {
684
706
  const residentVersion = residentSentinel.slice("__piNativesV".length).replace(/_/g, ".");
685
707
  throw new Error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-natives",
3
- "version": "17.4.2",
3
+ "version": "18.0.1",
4
4
  "description": "Native Rust bindings for PDF conversion, audio, WebRTC, grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
5
5
  "type": "module",
6
6
  "homepage": "https://omp.sh",
@@ -68,6 +68,8 @@
68
68
  "native/clipboard.d.ts",
69
69
  "native/desktop.js",
70
70
  "native/desktop.d.ts",
71
+ "native/desktop-adapter.js",
72
+ "native/desktop-adapter.d.ts",
71
73
  "native/loader-state.js",
72
74
  "native/loader-state.d.ts",
73
75
  "native/embedded-addon.js",
@@ -90,10 +92,10 @@
90
92
  }
91
93
  },
92
94
  "optionalDependencies": {
93
- "@oh-my-pi/pi-natives-linux-x64": "17.4.2",
94
- "@oh-my-pi/pi-natives-linux-arm64": "17.4.2",
95
- "@oh-my-pi/pi-natives-darwin-x64": "17.4.2",
96
- "@oh-my-pi/pi-natives-darwin-arm64": "17.4.2",
97
- "@oh-my-pi/pi-natives-win32-x64": "17.4.2"
95
+ "@oh-my-pi/pi-natives-linux-x64": "18.0.1",
96
+ "@oh-my-pi/pi-natives-linux-arm64": "18.0.1",
97
+ "@oh-my-pi/pi-natives-darwin-x64": "18.0.1",
98
+ "@oh-my-pi/pi-natives-darwin-arm64": "18.0.1",
99
+ "@oh-my-pi/pi-natives-win32-x64": "18.0.1"
98
100
  }
99
101
  }