@oh-my-pi/pi-natives 18.3.1 → 18.3.3
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/native/desktop-adapter.d.ts +4 -4
- package/native/desktop-adapter.js +2 -2
- package/native/index.d.ts +87 -26
- package/native/index.js +2 -2
- package/native/loader-state.d.ts +2 -6
- package/native/loader-state.js +44 -66
- package/native/version-sentinel.d.ts +16 -4
- package/native/version-sentinel.js +82 -24
- package/package.json +7 -7
|
@@ -2,7 +2,7 @@ interface AdaptedDesktopCapabilities {
|
|
|
2
2
|
readonly [key: string]: unknown;
|
|
3
3
|
readonly ax: boolean;
|
|
4
4
|
readonly backgroundWindowInput: boolean;
|
|
5
|
-
readonly
|
|
5
|
+
readonly takeover: boolean;
|
|
6
6
|
readonly axPermission: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -14,10 +14,10 @@ interface AdaptedDesktopSession {
|
|
|
14
14
|
target: string,
|
|
15
15
|
x: number,
|
|
16
16
|
y: number,
|
|
17
|
-
options?: { button?: string; count?: number; modifiers?: string[];
|
|
17
|
+
options?: { button?: string; count?: number; modifiers?: string[]; takeover?: boolean },
|
|
18
18
|
): Promise<void>;
|
|
19
|
-
typeText(target: string, text: string, options?: {
|
|
20
|
-
keyChord(target: string, keys: string[], options?: {
|
|
19
|
+
typeText(target: string, text: string, options?: { takeover?: boolean }): Promise<void>;
|
|
20
|
+
keyChord(target: string, keys: string[], options?: { takeover?: boolean }): Promise<void>;
|
|
21
21
|
close(): Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ function normalizeCapabilities(capabilities) {
|
|
|
33
33
|
...capabilities,
|
|
34
34
|
ax: false,
|
|
35
35
|
backgroundWindowInput: false,
|
|
36
|
-
|
|
36
|
+
takeover: true,
|
|
37
37
|
axPermission: "unavailable",
|
|
38
38
|
};
|
|
39
39
|
}
|
|
@@ -211,7 +211,7 @@ export function adaptDesktopSession(NativeDesktopSession) {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
#ensureForeground(target, options) {
|
|
214
|
-
if (
|
|
214
|
+
if (target !== "desktop" && options?.takeover !== true) {
|
|
215
215
|
throw desktopError(
|
|
216
216
|
"BackgroundUnavailable",
|
|
217
217
|
"the installed native addon supports foreground input only",
|
package/native/index.d.ts
CHANGED
|
@@ -366,6 +366,53 @@ export declare class Shell {
|
|
|
366
366
|
liveBackgroundJobCount(): Promise<number>
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/** One word-completion engine running on its own thread. */
|
|
370
|
+
export declare class TextPredictor {
|
|
371
|
+
/**
|
|
372
|
+
* Spawn the engine thread and start opening the engine; load errors
|
|
373
|
+
* surface from [`TextPredictor::ready`] and every later call.
|
|
374
|
+
*
|
|
375
|
+
* # Errors
|
|
376
|
+
* Returns an error when the engine thread cannot be spawned.
|
|
377
|
+
*/
|
|
378
|
+
constructor(options: TextPredictorOptions)
|
|
379
|
+
/**
|
|
380
|
+
* Resolve once the engine has loaded.
|
|
381
|
+
*
|
|
382
|
+
* # Errors
|
|
383
|
+
* Rejects with the engine's load error (missing weights, corrupt state).
|
|
384
|
+
*/
|
|
385
|
+
ready(): Promise<void>
|
|
386
|
+
/**
|
|
387
|
+
* Ghost text for `prefix` typed after `before`, or `null`.
|
|
388
|
+
*
|
|
389
|
+
* # Errors
|
|
390
|
+
* Rejects when the engine failed to load.
|
|
391
|
+
*/
|
|
392
|
+
complete(before: string, prefix: string): Promise<PredictedWord | null>
|
|
393
|
+
/**
|
|
394
|
+
* Learn from submitted prompts, in submission order.
|
|
395
|
+
*
|
|
396
|
+
* # Errors
|
|
397
|
+
* Rejects when the engine failed to load.
|
|
398
|
+
*/
|
|
399
|
+
observe(prompts: Array<string>): Promise<void>
|
|
400
|
+
/**
|
|
401
|
+
* Learn from a suggestion the user accepted (`true`) or typed past.
|
|
402
|
+
*
|
|
403
|
+
* # Errors
|
|
404
|
+
* Rejects when the engine failed to load.
|
|
405
|
+
*/
|
|
406
|
+
feedback(before: string, prefix: string, suggestion: string, accepted: boolean): Promise<void>
|
|
407
|
+
/**
|
|
408
|
+
* Flush learned state to the state directory.
|
|
409
|
+
*
|
|
410
|
+
* # Errors
|
|
411
|
+
* Rejects when the engine failed to load or the state cannot be written.
|
|
412
|
+
*/
|
|
413
|
+
persist(): Promise<void>
|
|
414
|
+
}
|
|
415
|
+
|
|
369
416
|
/**
|
|
370
417
|
* Dedicated writer thread for one terminal fd.
|
|
371
418
|
*
|
|
@@ -633,24 +680,13 @@ export declare class VcsRepo {
|
|
|
633
680
|
export declare function __ompInstallTokioRuntime(): void
|
|
634
681
|
|
|
635
682
|
/**
|
|
636
|
-
*
|
|
637
|
-
* that the `.node` file on disk is from the same package release as the
|
|
638
|
-
* `index.js` ESM wrapper invoking it.
|
|
639
|
-
*
|
|
640
|
-
* The `js_name` is bumped by `scripts/release.ts` to match the new
|
|
641
|
-
* `Cargo.toml` / `package.json` version on every release. The JS loader
|
|
642
|
-
* computes the expected name from `package.json#version` and refuses to use
|
|
643
|
-
* a `.node` that doesn't expose it, turning the silent
|
|
644
|
-
* `<sym> is not a function` crash from a locked-file update (the canonical
|
|
645
|
-
* Windows `bun install -g` failure mode) into a clear load-time error.
|
|
683
|
+
* Release version stamped into this `.node` after linking.
|
|
646
684
|
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
* `packages/natives/native/index.js` (which derives the name from
|
|
651
|
-
* `package.json#version`).
|
|
685
|
+
* `None` for an unstamped build. The JS loader compares it against
|
|
686
|
+
* `package.json#version` so a `.node` from another release fails at load time
|
|
687
|
+
* with an actionable error instead of a later `<sym> is not a function` crash.
|
|
652
688
|
*/
|
|
653
|
-
export declare function
|
|
689
|
+
export declare function __piNativesBuildVersion(): string | null
|
|
654
690
|
|
|
655
691
|
/**
|
|
656
692
|
* Reports whether the on-device model can generate, as an `availability`
|
|
@@ -1066,7 +1102,11 @@ export interface DesktopCapabilities {
|
|
|
1066
1102
|
input: boolean
|
|
1067
1103
|
ax: boolean
|
|
1068
1104
|
backgroundWindowInput: boolean
|
|
1069
|
-
|
|
1105
|
+
/**
|
|
1106
|
+
* Whether window input accepts `takeover: true` (briefly activate the
|
|
1107
|
+
* target and post real input).
|
|
1108
|
+
*/
|
|
1109
|
+
takeover: boolean
|
|
1070
1110
|
capturePermission: string
|
|
1071
1111
|
inputPermission: string
|
|
1072
1112
|
axPermission: string
|
|
@@ -1726,6 +1766,11 @@ export interface GrepOptions {
|
|
|
1726
1766
|
path: string
|
|
1727
1767
|
/** Glob filter for filenames (e.g., "*.ts"). */
|
|
1728
1768
|
glob?: string
|
|
1769
|
+
/**
|
|
1770
|
+
* Match simple glob patterns at any depth (default: true; `*.ts` ->
|
|
1771
|
+
* `**\/*.ts`). Set false when `glob` is already relative to `path`.
|
|
1772
|
+
*/
|
|
1773
|
+
recursive?: boolean
|
|
1729
1774
|
/** Filter by file type (e.g., "js", "py", "rust"). */
|
|
1730
1775
|
type?: string
|
|
1731
1776
|
/** Case-insensitive search. */
|
|
@@ -2107,14 +2152,6 @@ export declare function macOSAutocorrectWord(text: string, start: number, length
|
|
|
2107
2152
|
*/
|
|
2108
2153
|
export declare function macOSCheckSpelling(text: string): Promise<Array<SpellingRange>>
|
|
2109
2154
|
|
|
2110
|
-
/**
|
|
2111
|
-
* Return macOS dictionary completions for one partial-word range.
|
|
2112
|
-
*
|
|
2113
|
-
* Returns an empty list when Apple's spelling service is unavailable.
|
|
2114
|
-
* On macOS, the lookup runs on the dedicated spelling thread.
|
|
2115
|
-
*/
|
|
2116
|
-
export declare function macOSCompleteWord(text: string, start: number, length: number): Promise<Array<string>>
|
|
2117
|
-
|
|
2118
2155
|
/** Whether the host can use Apple's native spelling service. */
|
|
2119
2156
|
export declare function macOSSpellCheckerAvailable(): boolean
|
|
2120
2157
|
|
|
@@ -2388,7 +2425,11 @@ export interface PointerOptions {
|
|
|
2388
2425
|
button?: string
|
|
2389
2426
|
count?: number
|
|
2390
2427
|
modifiers?: Array<string>
|
|
2391
|
-
|
|
2428
|
+
/**
|
|
2429
|
+
* Briefly activate the target window and post real input instead of the
|
|
2430
|
+
* default background delivery.
|
|
2431
|
+
*/
|
|
2432
|
+
takeover?: boolean
|
|
2392
2433
|
}
|
|
2393
2434
|
|
|
2394
2435
|
/**
|
|
@@ -2415,6 +2456,14 @@ export interface PowerAssertionOptions {
|
|
|
2415
2456
|
display?: boolean
|
|
2416
2457
|
}
|
|
2417
2458
|
|
|
2459
|
+
/** Ghost text for the word being typed. */
|
|
2460
|
+
export interface PredictedWord {
|
|
2461
|
+
/** Characters to paint after the typed prefix. */
|
|
2462
|
+
suffix: string
|
|
2463
|
+
/** Engine-calibrated probability that `suffix` is exactly right. */
|
|
2464
|
+
confidence: number
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2418
2467
|
/** Current state of a process reference. */
|
|
2419
2468
|
export declare enum ProcessStatus {
|
|
2420
2469
|
/** The referenced process is still running. */
|
|
@@ -3137,6 +3186,18 @@ export interface SummarySegment {
|
|
|
3137
3186
|
*/
|
|
3138
3187
|
export declare function supportsLanguage(lang: string): boolean
|
|
3139
3188
|
|
|
3189
|
+
/** Options for [`TextPredictor::new`]. */
|
|
3190
|
+
export interface TextPredictorOptions {
|
|
3191
|
+
/** Engine: `ngram`, `smollm`, or `apple`. */
|
|
3192
|
+
method: string
|
|
3193
|
+
/** Private directory for persisted learned state. */
|
|
3194
|
+
stateDir: string
|
|
3195
|
+
/** Directory holding downloaded model weights (`smollm` only). */
|
|
3196
|
+
modelDir?: string
|
|
3197
|
+
/** Show threshold override; omit for the engine's tuned default. */
|
|
3198
|
+
showThreshold?: number
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3140
3201
|
/**
|
|
3141
3202
|
* Truncate text to a visible width, preserving ANSI codes.
|
|
3142
3203
|
*
|
package/native/index.js
CHANGED
|
@@ -38,6 +38,7 @@ export const PowerAssertion = nativeBindings.PowerAssertion;
|
|
|
38
38
|
export const Process = nativeBindings.Process;
|
|
39
39
|
export const PtySession = nativeBindings.PtySession;
|
|
40
40
|
export const Shell = nativeBindings.Shell;
|
|
41
|
+
export const TextPredictor = nativeBindings.TextPredictor;
|
|
41
42
|
export const TtyWriter = nativeBindings.TtyWriter;
|
|
42
43
|
export const VcsGitRepo = nativeBindings.VcsGitRepo;
|
|
43
44
|
export const VcsJjWorkspace = nativeBindings.VcsJjWorkspace;
|
|
@@ -45,7 +46,7 @@ export const VcsRepo = nativeBindings.VcsRepo;
|
|
|
45
46
|
|
|
46
47
|
// functions
|
|
47
48
|
export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime ?? missingNativeExport("__ompInstallTokioRuntime");
|
|
48
|
-
export const
|
|
49
|
+
export const __piNativesBuildVersion = nativeBindings.__piNativesBuildVersion;
|
|
49
50
|
export const appleFmAvailability = nativeBindings.appleFmAvailability ?? missingNativeExport("appleFmAvailability");
|
|
50
51
|
export const appleFmCancel = nativeBindings.appleFmCancel ?? missingNativeExport("appleFmCancel");
|
|
51
52
|
export const appleFmGenerate = nativeBindings.appleFmGenerate ?? missingNativeExport("appleFmGenerate");
|
|
@@ -98,7 +99,6 @@ export const isoStop = nativeBindings.isoStop ?? missingNativeExport("isoStop");
|
|
|
98
99
|
export const listWorkspace = nativeBindings.listWorkspace ?? missingNativeExport("listWorkspace");
|
|
99
100
|
export const macOSAutocorrectWord = nativeBindings.macOSAutocorrectWord ?? missingNativeExport("macOSAutocorrectWord");
|
|
100
101
|
export const macOSCheckSpelling = nativeBindings.macOSCheckSpelling ?? missingNativeExport("macOSCheckSpelling");
|
|
101
|
-
export const macOSCompleteWord = nativeBindings.macOSCompleteWord ?? missingNativeExport("macOSCompleteWord");
|
|
102
102
|
export const macOSSpellCheckerAvailable = nativeBindings.macOSSpellCheckerAvailable ?? missingNativeExport("macOSSpellCheckerAvailable");
|
|
103
103
|
export const macOSSpellingGuesses = nativeBindings.macOSSpellingGuesses ?? missingNativeExport("macOSSpellingGuesses");
|
|
104
104
|
export const matchesKey = nativeBindings.matchesKey ?? missingNativeExport("matchesKey");
|
package/native/loader-state.d.ts
CHANGED
|
@@ -75,7 +75,6 @@ export interface NativeLoaderContext {
|
|
|
75
75
|
addonFilenames: string[];
|
|
76
76
|
addonLabel: string;
|
|
77
77
|
candidates: string[];
|
|
78
|
-
versionSentinelExport: string;
|
|
79
78
|
isWorkspaceLoad: boolean;
|
|
80
79
|
nativesDir: string;
|
|
81
80
|
}
|
|
@@ -118,7 +117,6 @@ export function selectCpuVariant(input: SelectCpuVariantInput): SelectCpuVariant
|
|
|
118
117
|
export interface ValidateLoadedBindingsContext {
|
|
119
118
|
isWorkspaceLoad: boolean;
|
|
120
119
|
packageVersion: string;
|
|
121
|
-
versionSentinelExport: string;
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
export function validateLoadedBindings(
|
|
@@ -131,10 +129,8 @@ export function validateLoadedBindings(
|
|
|
131
129
|
export interface NativeAddonStatus {
|
|
132
130
|
/** Absolute path of the loaded `.node`. */
|
|
133
131
|
path: string;
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
/** Sentinel this loader's package version expects. */
|
|
137
|
-
expectedSentinel: string;
|
|
132
|
+
/** Release the loaded addon reports (post-link stamp or legacy sentinel), or `null` when unidentified. */
|
|
133
|
+
version: string | null;
|
|
138
134
|
/** `package.json#version` of the loader that loaded it. */
|
|
139
135
|
packageVersion: string;
|
|
140
136
|
/** True when the addon carries a different release than this package. */
|
package/native/loader-state.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import * as zlib from "node:zlib";
|
|
7
7
|
import packageJson from "../package.json" with { type: "json" };
|
|
8
8
|
import { embeddedAddon } from "./embedded-addon.js";
|
|
9
|
-
import {
|
|
9
|
+
import { bindingsHaveReleaseIdentity, bindingsReleaseVersion, containsVersionStamp } from "./version-sentinel.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Native addon loader for `@oh-my-pi/pi-natives`.
|
|
@@ -15,7 +15,7 @@ import { containsVersionSentinel, versionSentinelFor } from "./version-sentinel.
|
|
|
15
15
|
* `pi_natives.<platform>-<arch>*.node` is required, validated, and returned":
|
|
16
16
|
* platform/variant detection, candidate-path resolution, on-disk staging from
|
|
17
17
|
* `node_modules` (Windows update safety), embedded-addon extraction (Bun
|
|
18
|
-
* standalone binaries),
|
|
18
|
+
* standalone binaries), release-stamp validation, and the aggregated error
|
|
19
19
|
* surface for diagnostic-friendly failures.
|
|
20
20
|
*
|
|
21
21
|
* `native/index.js` is reduced to one `loadNative()` call plus the generated
|
|
@@ -656,28 +656,15 @@ function maybeStageNodeModulesAddon(ctx, errors) {
|
|
|
656
656
|
return stagedPath;
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
-
|
|
660
|
-
/** Any release sentinel a `.node` may carry (`__piNativesV{major}_{minor}_{patch}`). */
|
|
661
|
-
const VERSION_SENTINEL_ANY_RE = /^__piNativesV[A-Za-z0-9_]+$/;
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Release version encoded in a sentinel export name.
|
|
665
|
-
* @param {string} sentinel
|
|
666
|
-
* @returns {string}
|
|
667
|
-
*/
|
|
668
|
-
function sentinelVersion(sentinel) {
|
|
669
|
-
return sentinel.slice("__piNativesV".length).replace(/_/g, ".");
|
|
670
|
-
}
|
|
671
|
-
|
|
672
659
|
/**
|
|
673
|
-
* Before
|
|
660
|
+
* Before release identities existed, published native addons still shared
|
|
674
661
|
* this stable core ABI. Let those on-disk addons bridge a package-version bump
|
|
675
|
-
* when they expose the signature; keep every
|
|
662
|
+
* when they expose the signature; keep every identified addon and a current
|
|
676
663
|
* on-disk file paired with resident old exports on the strict path below.
|
|
677
664
|
*/
|
|
678
|
-
function isCompatiblePreSentinelNativeAddon(bindings,
|
|
679
|
-
if (
|
|
680
|
-
if (
|
|
665
|
+
function isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedStamp) {
|
|
666
|
+
if (diskHasExpectedStamp) return false;
|
|
667
|
+
if (bindingsHaveReleaseIdentity(bindings)) return false;
|
|
681
668
|
return (
|
|
682
669
|
typeof bindings.countTokens === "function" &&
|
|
683
670
|
typeof bindings.executeShell === "function" &&
|
|
@@ -692,53 +679,48 @@ function isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedSentinel) {
|
|
|
692
679
|
export function validateLoadedBindings(ctx, bindings, candidate) {
|
|
693
680
|
// In workspace dev (running out of `packages/natives/native/` rather than a
|
|
694
681
|
// `node_modules` install or a compiled bundle) the local `.node` only gains
|
|
695
|
-
// the
|
|
682
|
+
// the new release stamp after `bun --cwd=packages/natives run build`. Skip
|
|
696
683
|
// validation there so a stale post-pull dev tree boots while the rebuild
|
|
697
684
|
// completes; install and compiled-binary paths still validate. The mismatch
|
|
698
685
|
// is not swallowed silently: `native/index.js` exports `missingNativeExport`
|
|
699
686
|
// for every symbol the stale addon predates, so the first call through one
|
|
700
687
|
// reports the addon, both releases, and the rebuild command.
|
|
701
688
|
if (ctx.isWorkspaceLoad) return;
|
|
702
|
-
|
|
689
|
+
const residentVersion = bindingsReleaseVersion(bindings);
|
|
690
|
+
if (residentVersion === ctx.packageVersion) return;
|
|
703
691
|
|
|
704
|
-
// The
|
|
705
|
-
//
|
|
692
|
+
// The bindings report another release (or none). Distinguish two failure
|
|
693
|
+
// modes by what the file on disk carries:
|
|
706
694
|
// - disk stale: the `.node` on disk predates this loader (its own build);
|
|
707
695
|
// reinstalling re-syncs the file.
|
|
708
696
|
// - process stale: an in-place upgrade landed a new release on disk while
|
|
709
697
|
// this process still holds the previous addon generation resident in the
|
|
710
698
|
// dynamic-loader's native-module cache. `require` returns those old
|
|
711
|
-
// exports, which
|
|
699
|
+
// exports, which report the PRIOR release — disk is already consistent,
|
|
712
700
|
// so reinstall is a no-op and only restarting the process re-syncs.
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
// The restart diagnosis is valid only when the selected file itself carries
|
|
719
|
-
// the current sentinel; otherwise a restart would simply reload stale disk.
|
|
720
|
-
let diskHasExpectedSentinel = false;
|
|
701
|
+
// Resident exports alone cannot tell these apart: `require` returns the
|
|
702
|
+
// same exports in both cases. The restart diagnosis is valid only when the
|
|
703
|
+
// selected file itself carries the current stamp; otherwise a restart would
|
|
704
|
+
// simply reload stale disk.
|
|
705
|
+
let diskHasExpectedStamp = false;
|
|
721
706
|
try {
|
|
722
|
-
|
|
707
|
+
diskHasExpectedStamp = containsVersionStamp(fs.readFileSync(candidate), ctx.packageVersion);
|
|
723
708
|
} catch {
|
|
724
709
|
// The successful require above normally guarantees readability. If the
|
|
725
710
|
// file disappears concurrently, retain the safe reinstall diagnosis.
|
|
726
711
|
}
|
|
727
|
-
if (isCompatiblePreSentinelNativeAddon(bindings,
|
|
728
|
-
if (
|
|
729
|
-
const residentVersion = sentinelVersion(residentSentinel);
|
|
712
|
+
if (isCompatiblePreSentinelNativeAddon(bindings, diskHasExpectedStamp)) return;
|
|
713
|
+
if (residentVersion && diskHasExpectedStamp) {
|
|
730
714
|
throw new Error(
|
|
731
|
-
`Loaded ${candidate}, which
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
"still resident in this process. Disk is already consistent — restart omp to pick up " +
|
|
736
|
-
`${ctx.packageVersion} (reinstalling changes nothing).`,
|
|
715
|
+
`Loaded ${candidate}, which reports @oh-my-pi/pi-natives@${residentVersion}, but this loader is ` +
|
|
716
|
+
`@${ctx.packageVersion}. omp was upgraded to ${ctx.packageVersion} while this session was running; ` +
|
|
717
|
+
`the ${residentVersion} addon is still resident in this process. Disk is already consistent — ` +
|
|
718
|
+
`restart omp to pick up ${ctx.packageVersion} (reinstalling changes nothing).`,
|
|
737
719
|
);
|
|
738
720
|
}
|
|
739
721
|
throw new Error(
|
|
740
|
-
`Loaded ${candidate} but it
|
|
741
|
-
`
|
|
722
|
+
`Loaded ${candidate} but it reports ${residentVersion ? `@oh-my-pi/pi-natives@${residentVersion}` : "no release version"}, ` +
|
|
723
|
+
`not the @${ctx.packageVersion} this loader expects. The .node file on disk is from a different ` +
|
|
742
724
|
"release than this loader — reinstall to re-sync.",
|
|
743
725
|
);
|
|
744
726
|
}
|
|
@@ -746,7 +728,7 @@ export function validateLoadedBindings(ctx, bindings, candidate) {
|
|
|
746
728
|
/**
|
|
747
729
|
* Identity of the addon `loadNative()` returned, in the shape the
|
|
748
730
|
* missing-export diagnostic reports. Null until a load succeeds.
|
|
749
|
-
* @type {{ path: string;
|
|
731
|
+
* @type {{ path: string; version: string | null; packageVersion: string; stale: boolean } | null}
|
|
750
732
|
*/
|
|
751
733
|
let loadedAddon = null;
|
|
752
734
|
|
|
@@ -755,22 +737,21 @@ let loadedAddon = null;
|
|
|
755
737
|
* release the file came from, and the release this tree expects.
|
|
756
738
|
* @param {Record<string, unknown>} bindings
|
|
757
739
|
* @param {string} candidate
|
|
758
|
-
* @param {{ packageVersion: string
|
|
740
|
+
* @param {{ packageVersion: string }} ctx
|
|
759
741
|
*/
|
|
760
742
|
function describeLoadedAddon(bindings, candidate, ctx) {
|
|
761
|
-
const
|
|
743
|
+
const version = bindingsReleaseVersion(bindings);
|
|
762
744
|
return {
|
|
763
745
|
path: candidate,
|
|
764
|
-
|
|
765
|
-
expectedSentinel: ctx.versionSentinelExport,
|
|
746
|
+
version,
|
|
766
747
|
packageVersion: ctx.packageVersion,
|
|
767
|
-
stale:
|
|
748
|
+
stale: version !== ctx.packageVersion,
|
|
768
749
|
};
|
|
769
750
|
}
|
|
770
751
|
|
|
771
752
|
/**
|
|
772
753
|
* The addon behind this process's `@oh-my-pi/pi-natives` exports.
|
|
773
|
-
* @returns {{ path: string;
|
|
754
|
+
* @returns {{ path: string; version: string | null; packageVersion: string; stale: boolean } | null}
|
|
774
755
|
*/
|
|
775
756
|
export function nativeAddonStatus() {
|
|
776
757
|
return loadedAddon;
|
|
@@ -779,7 +760,7 @@ export function nativeAddonStatus() {
|
|
|
779
760
|
/**
|
|
780
761
|
* Stand-in for an export the loaded addon does not provide.
|
|
781
762
|
*
|
|
782
|
-
* A workspace tree tolerates a
|
|
763
|
+
* A workspace tree tolerates a release mismatch on purpose: a checkout that
|
|
783
764
|
* pulled a new release keeps running until `bun run build:native` finishes
|
|
784
765
|
* (see `validateLoadedBindings`), and PR CI loads release addons under a newer
|
|
785
766
|
* checkout the same way. Such an addon has no value for any symbol added after
|
|
@@ -815,12 +796,12 @@ export function missingNativeExportMessage(symbolName, addon = loadedAddon) {
|
|
|
815
796
|
if (!addon.stale) {
|
|
816
797
|
return `@oh-my-pi/pi-natives export \`${symbolName}\` is missing from ${addon.path}; ${rebuild}.`;
|
|
817
798
|
}
|
|
818
|
-
const loaded = addon.
|
|
819
|
-
? `the @oh-my-pi/pi-natives@${
|
|
820
|
-
: "an addon
|
|
799
|
+
const loaded = addon.version
|
|
800
|
+
? `the @oh-my-pi/pi-natives@${addon.version} addon`
|
|
801
|
+
: "an addon without a release stamp";
|
|
821
802
|
return (
|
|
822
803
|
`@oh-my-pi/pi-natives export \`${symbolName}\` is missing: ${addon.path} is ${loaded}, not ` +
|
|
823
|
-
`@${addon.packageVersion}
|
|
804
|
+
`@${addon.packageVersion} — ${rebuild}.`
|
|
824
805
|
);
|
|
825
806
|
}
|
|
826
807
|
|
|
@@ -927,14 +908,12 @@ export function initLoaderContext(overrides = {}) {
|
|
|
927
908
|
userDataDir,
|
|
928
909
|
});
|
|
929
910
|
|
|
930
|
-
//
|
|
931
|
-
// the
|
|
932
|
-
// `scripts/
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
// locked-file update into an actionable load-time error.
|
|
937
|
-
const versionSentinelExport = versionSentinelFor(packageVersion);
|
|
911
|
+
// Release validation compares `__piNativesBuildVersion()` — the version the
|
|
912
|
+
// build pipeline stamps into the addon after linking
|
|
913
|
+
// (`scripts/stamp-native-version.ts`) — with `package.json#version`, so a
|
|
914
|
+
// `.node` from a different release is rejected at load time instead of
|
|
915
|
+
// surfacing later as a silent `<sym> is not a function` crash from a
|
|
916
|
+
// Windows locked-file update.
|
|
938
917
|
|
|
939
918
|
return {
|
|
940
919
|
platformTag,
|
|
@@ -948,7 +927,6 @@ export function initLoaderContext(overrides = {}) {
|
|
|
948
927
|
addonFilenames,
|
|
949
928
|
addonLabel,
|
|
950
929
|
candidates,
|
|
951
|
-
versionSentinelExport,
|
|
952
930
|
isWorkspaceLoad,
|
|
953
931
|
nativesDir,
|
|
954
932
|
};
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export
|
|
1
|
+
/** Prefix of the post-link stamp slot linked into every addon. */
|
|
2
|
+
export const VERSION_STAMP_MAGIC: string;
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
export
|
|
4
|
+
/** Total stamp slot size in bytes: magic, version, NUL padding. */
|
|
5
|
+
export const VERSION_STAMP_SIZE: number;
|
|
6
|
+
|
|
7
|
+
/** Check whether addon bytes carry the stamp for exactly `version`. */
|
|
8
|
+
export function containsVersionStamp(bytes: Uint8Array, version: string): boolean;
|
|
9
|
+
|
|
10
|
+
/** Check whether pre-stamp addon bytes export the legacy `__piNativesV*` sentinel for exactly `version`. */
|
|
11
|
+
export function containsLegacyVersionSentinel(bytes: Uint8Array, version: string): boolean;
|
|
12
|
+
|
|
13
|
+
/** Release a loaded addon reports (stamp, or legacy `__piNativesV*` export), or `null`. */
|
|
14
|
+
export function bindingsReleaseVersion(bindings: Record<string, unknown>): string | null;
|
|
15
|
+
|
|
16
|
+
/** True when the bindings identify their release at all (stamp reader or legacy sentinel). */
|
|
17
|
+
export function bindingsHaveReleaseIdentity(bindings: Record<string, unknown>): boolean;
|
|
@@ -1,42 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Addon release-identity helpers shared by the native loader, the embed
|
|
3
|
+
* pipeline, and the post-link stamp tool (`scripts/stamp-native-version.ts`).
|
|
3
4
|
*
|
|
4
|
-
* Kept in its own module so `scripts/embed-native.ts` can reuse
|
|
5
|
-
*
|
|
5
|
+
* Kept in its own module so `scripts/embed-native.ts` can reuse them without
|
|
6
|
+
* importing `loader-state.js` — which pulls in the generated
|
|
6
7
|
* `embedded-addon.js` and its `with { type: "file" }` archive import. That
|
|
7
8
|
* chain fails to resolve when the archive is missing, which would break
|
|
8
9
|
* `gen:native:reset` on an inconsistent tree (populated manifest, deleted
|
|
9
10
|
* archive) before it can restore the checked-in null stub.
|
|
11
|
+
*
|
|
12
|
+
* Current addons carry a fixed-size stamp slot (`VERSION_STAMP_MAGIC` + the
|
|
13
|
+
* release version + NUL padding) written after linking and reported by
|
|
14
|
+
* `__piNativesBuildVersion()`. Addons published before that exported a
|
|
15
|
+
* per-release `__piNativesV{major}_{minor}_{patch}` function instead; those
|
|
16
|
+
* are still recognized so an old module resident across an in-place upgrade
|
|
17
|
+
* is diagnosed correctly.
|
|
10
18
|
*/
|
|
11
19
|
|
|
20
|
+
/** Prefix of the stamp slot linked into every addon (`crates/pi-natives/src/lib.rs`). */
|
|
21
|
+
export const VERSION_STAMP_MAGIC = "PI_NATIVES_VERSION_STAMP:";
|
|
22
|
+
|
|
23
|
+
/** Total stamp slot size in bytes: magic, version, NUL padding. */
|
|
24
|
+
export const VERSION_STAMP_SIZE = 64;
|
|
25
|
+
|
|
26
|
+
/** Export names of pre-stamp releases (`__piNativesV18_3_2`). */
|
|
27
|
+
const LEGACY_SENTINEL_RE = /^__piNativesV([A-Za-z0-9_]+)$/;
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
30
|
+
* Check whether addon bytes carry the stamp for exactly `version` (a stamp for
|
|
31
|
+
* `18.1.10` must not satisfy a lookup for `18.1.1`, so the version must be
|
|
32
|
+
* followed by the slot's NUL padding).
|
|
33
|
+
* @param {Uint8Array} bytes
|
|
34
|
+
* @param {string} version
|
|
35
|
+
* @returns {boolean}
|
|
16
36
|
*/
|
|
17
|
-
export function
|
|
18
|
-
|
|
37
|
+
export function containsVersionStamp(bytes, version) {
|
|
38
|
+
if (version.length === 0) return false;
|
|
39
|
+
const needle = Buffer.from(`${VERSION_STAMP_MAGIC}${version}\0`, "utf8");
|
|
40
|
+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).indexOf(needle) !== -1;
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
/**
|
|
22
|
-
* Check
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {string}
|
|
44
|
+
* Check whether pre-stamp addon bytes export the legacy sentinel for exactly
|
|
45
|
+
* `version` (`__piNativesV18_3_2`, not a longer `__piNativesV18_3_20`). The
|
|
46
|
+
* loader accepts such an addon for that release, so embedding may too.
|
|
47
|
+
* @param {Uint8Array} bytes
|
|
48
|
+
* @param {string} version
|
|
27
49
|
* @returns {boolean}
|
|
28
50
|
*/
|
|
29
|
-
export function
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!
|
|
39
|
-
offset = index + expected.length;
|
|
51
|
+
export function containsLegacyVersionSentinel(bytes, version) {
|
|
52
|
+
if (version.length === 0) return false;
|
|
53
|
+
const haystack = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
54
|
+
const needle = Buffer.from(`__piNativesV${version.replace(/[^A-Za-z0-9]/g, "_")}`, "utf8");
|
|
55
|
+
for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + 1)) {
|
|
56
|
+
const next = haystack[at + needle.length];
|
|
57
|
+
const continues =
|
|
58
|
+
next !== undefined &&
|
|
59
|
+
((next >= 0x30 && next <= 0x39) || (next >= 0x41 && next <= 0x5a) || (next >= 0x61 && next <= 0x7a) || next === 0x5f);
|
|
60
|
+
if (!continues) return true;
|
|
40
61
|
}
|
|
41
62
|
return false;
|
|
42
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Release version a loaded addon reports: the post-link stamp for current
|
|
67
|
+
* addons, the legacy sentinel export name for pre-stamp releases, `null` for
|
|
68
|
+
* unstamped builds and addons that predate both.
|
|
69
|
+
* @param {Record<string, unknown>} bindings
|
|
70
|
+
* @returns {string | null}
|
|
71
|
+
*/
|
|
72
|
+
export function bindingsReleaseVersion(bindings) {
|
|
73
|
+
const report = bindings.__piNativesBuildVersion;
|
|
74
|
+
if (typeof report === "function") {
|
|
75
|
+
try {
|
|
76
|
+
const version = report();
|
|
77
|
+
return typeof version === "string" && version.length > 0 ? version : null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const key of Object.keys(bindings)) {
|
|
83
|
+
const match = LEGACY_SENTINEL_RE.exec(key);
|
|
84
|
+
if (match) return match[1].replace(/_/g, ".");
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* True when the bindings identify their release at all (stamp reader or
|
|
91
|
+
* legacy sentinel), i.e. they are not a pre-sentinel addon.
|
|
92
|
+
* @param {Record<string, unknown>} bindings
|
|
93
|
+
* @returns {boolean}
|
|
94
|
+
*/
|
|
95
|
+
export function bindingsHaveReleaseIdentity(bindings) {
|
|
96
|
+
return (
|
|
97
|
+
typeof bindings.__piNativesBuildVersion === "function" ||
|
|
98
|
+
Object.keys(bindings).some(key => LEGACY_SENTINEL_RE.test(key))
|
|
99
|
+
);
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-natives",
|
|
3
|
-
"version": "18.3.
|
|
3
|
+
"version": "18.3.3",
|
|
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",
|
|
@@ -107,11 +107,11 @@
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
"optionalDependencies": {
|
|
110
|
-
"@oh-my-pi/pi-natives-linux-x64": "18.3.
|
|
111
|
-
"@oh-my-pi/pi-natives-linux-arm64": "18.3.
|
|
112
|
-
"@oh-my-pi/pi-natives-darwin-x64": "18.3.
|
|
113
|
-
"@oh-my-pi/pi-natives-darwin-arm64": "18.3.
|
|
114
|
-
"@oh-my-pi/pi-natives-win32-x64": "18.3.
|
|
115
|
-
"@oh-my-pi/pi-natives-win32-arm64": "18.3.
|
|
110
|
+
"@oh-my-pi/pi-natives-linux-x64": "18.3.3",
|
|
111
|
+
"@oh-my-pi/pi-natives-linux-arm64": "18.3.3",
|
|
112
|
+
"@oh-my-pi/pi-natives-darwin-x64": "18.3.3",
|
|
113
|
+
"@oh-my-pi/pi-natives-darwin-arm64": "18.3.3",
|
|
114
|
+
"@oh-my-pi/pi-natives-win32-x64": "18.3.3",
|
|
115
|
+
"@oh-my-pi/pi-natives-win32-arm64": "18.3.3"
|
|
116
116
|
}
|
|
117
117
|
}
|