@oh-my-pi/pi-natives 18.16.0 → 18.18.0

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.
@@ -9,56 +9,6 @@ governs only the material it names.
9
9
  VENDORED SOURCE AND DATA
10
10
  ========================
11
11
 
12
- -------------------------------------------------------------------------------
13
- brush-core 0.5.0 — crates/vendor/brush-core
14
-
15
- This directory contains a vendored, locally-patched copy of brush-core from the
16
- brush shell project (https://github.com/reubeno/brush), used under the MIT
17
- License. It is wired into the workspace via `[patch.crates-io]` so these
18
- modifications can be maintained against the published release (brush-core
19
- 0.5.0).
20
-
21
- Copyright (c) 2024 reuben olinsky
22
-
23
- Local modifications include: Windows path handling fixes (drive-alias and
24
- rooted-path normalization in globs), an async background-PID fix for the nohup
25
- wrapper, treating `/dev/null` as poll-ready to avoid an infinite poll on macOS,
26
- and indentation/formatting standardization. The remaining logic tracks upstream
27
- so behavior matches the published crate.
28
-
29
- MIT License
30
-
31
- Permission is hereby granted, free of charge, to any person obtaining a copy
32
- of this software and associated documentation files (the "Software"), to deal
33
- in the Software without restriction, including without limitation the rights
34
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
- copies of the Software, and to permit persons to whom the Software is
36
- furnished to do so, subject to the following conditions:
37
-
38
- The above copyright notice and this permission notice shall be included in all
39
- copies or substantial portions of the Software.
40
-
41
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
- SOFTWARE.
48
-
49
- -------------------------------------------------------------------------------
50
- brush-builtins — crates/pi-builtins
51
-
52
- The POSIX/bash builtins began as a locally-patched copy of brush-builtins from
53
- the brush shell project (https://github.com/reubeno/brush), used under the MIT
54
- License; the full license text is reproduced with brush-core above.
55
-
56
- Copyright (c) 2024 reuben olinsky
57
-
58
- Local modifications include: implementing previously-stubbed builtins, the
59
- `wait` builtin (PID targeting plus the `-f`/`-n`/`-p` flags), and cancellation
60
- support for the `read` builtin via poll.
61
-
62
12
  -------------------------------------------------------------------------------
63
13
  Command-line utilities — crates/pi-builtins
64
14
 
@@ -11855,35 +11805,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11855
11805
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11856
11806
  SOFTWARE.
11857
11807
 
11858
- -------------------------------------------------------------------------------
11859
- License: MIT License (MIT)
11860
- Used by:
11861
- - brush-parser 0.4.0 (https://github.com/reubeno/brush)
11862
-
11863
- Full license text:
11864
-
11865
- MIT License
11866
-
11867
- Copyright (c) 2024 reuben olinsky
11868
-
11869
- Permission is hereby granted, free of charge, to any person obtaining a copy
11870
- of this software and associated documentation files (the "Software"), to deal
11871
- in the Software without restriction, including without limitation the rights
11872
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11873
- copies of the Software, and to permit persons to whom the Software is
11874
- furnished to do so, subject to the following conditions:
11875
-
11876
- The above copyright notice and this permission notice shall be included in all
11877
- copies or substantial portions of the Software.
11878
-
11879
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11880
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11881
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11882
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
11883
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11884
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11885
- SOFTWARE.
11886
-
11887
11808
  -------------------------------------------------------------------------------
11888
11809
  License: MIT License (MIT)
11889
11810
  Used by:
@@ -12098,7 +12019,6 @@ SOFTWARE.
12098
12019
  -------------------------------------------------------------------------------
12099
12020
  License: MIT License (MIT)
12100
12021
  Used by:
12101
- - brush-core 0.5.0 (https://github.com/reubeno/brush)
12102
12022
  - napi 3.14.2 (https://github.com/napi-rs/napi-rs)
12103
12023
  - asn1-rs-impl 0.2.0 (https://github.com/rusticata/asn1-rs.git)
12104
12024
  - ast-grep-core 0.39.9 (https://github.com/ast-grep/ast-grep)
package/native/index.d.ts CHANGED
@@ -87,8 +87,8 @@ export declare class DesktopSession {
87
87
  retire(): void
88
88
  /**
89
89
  * Sets when the current run ends, `remaining_ms` from now; each run sets
90
- * it before its first call. On macOS, input waiting for the user to pause
91
- * in its app gives up shortly before it.
90
+ * it before its first call. Input waiting for the user to pause in its app
91
+ * (macOS) or for a Wayland permission prompt gives up shortly before it.
92
92
  */
93
93
  setRunDeadline(remainingMs: number): void
94
94
  capture(target: string, caps?: CaptureCaps | undefined | null): Promise<DesktopCapture>
@@ -567,7 +567,46 @@ export declare class PtySession {
567
567
  kill(): void
568
568
  }
569
569
 
570
- /** Persistent brush-core shell session. */
570
+ /**
571
+ * Actual command state, source units and bounded history for one shell run,
572
+ * including observed assignments and producer-attributed output.
573
+ *
574
+ * Create one over the command text and
575
+ * pass it as [`ShellRunOptions::trace`]; poll [`RunTrace::version`] while the
576
+ * run is in flight and take [`RunTrace::snapshot`] when it changed.
577
+ */
578
+ export declare class RunTrace {
579
+ /** A trace of `script`, the exact command text about to run. */
580
+ constructor(script: string)
581
+ /**
582
+ * Revision of semantic events, assignments and visible output; unchanged
583
+ * means `snapshot()` is unchanged, even when adjacent output coalesces.
584
+ */
585
+ version(): number
586
+ /**
587
+ * The `ShellTrace` JSON with byte-offset layout, current node states and
588
+ * ordered replay `history`. The newest 20,000 records retain at most
589
+ * 2 MiB visible output and 8 MiB payload; `historyTruncated` explicitly
590
+ * reports eviction. History timestamps are measured ns since `RunStart`;
591
+ * output identifies its real producer (`u`, `n`, `command`) when known.
592
+ * Semantic entries carry their actual `vpid`; `rootVpid` distinguishes
593
+ * root lifecycle from owned tool children even after journal trimming.
594
+ * A UTF-8 scalar spanning producers has exact byte `contributors`
595
+ * instead of an invented primary producer. Decoding ends at capture EOF.
596
+ * Segmented minimizer submissions share one epoch and final status;
597
+ * their history carries `segment`, with `logicalEnd`/`logicalStatus`
598
+ * finalizing the last actual root RunEnd after output draining.
599
+ * Serialization borrows the locked model rather than cloning its history.
600
+ */
601
+ snapshot(): string
602
+ /**
603
+ * Render directly over the borrowed native state; no snapshot or syntax
604
+ * roundtrip.
605
+ */
606
+ present(options: ShellVizOptions): string
607
+ }
608
+
609
+ /** Persistent embedded bash engine (pi-shell) shell session. */
571
610
  export declare class Shell {
572
611
  /**
573
612
  * Create a new shell session from optional configuration.
@@ -607,6 +646,15 @@ export declare class Shell {
607
646
  pids(): Array<number>
608
647
  }
609
648
 
649
+ /**
650
+ * Persisted native visualization, restored once rather than reparsed on each
651
+ * frame.
652
+ */
653
+ export declare class ShellVisualization {
654
+ constructor(snapshot: string)
655
+ present(options: ShellVizOptions): string
656
+ }
657
+
610
658
  /** One word-completion engine running on its own thread. */
611
659
  export declare class TextPredictor {
612
660
  /**
@@ -1442,10 +1490,8 @@ export declare function countTokens(input: string | string[], encoding?: Encodin
1442
1490
  /**
1443
1491
  * Decode one complete SIXEL control string into a PNG on the calling thread.
1444
1492
  *
1445
- * The decoder is deliberately bounded before handing the stream to
1446
- * `icy_sixel`: raster declarations, repeats, and row advances are scanned
1447
- * first so hostile dimensions cannot make the dependency allocate its much
1448
- * larger internal maximum.
1493
+ * Raster declarations, repeats, and row advances are bounded before allocating
1494
+ * the decoded RGBA buffer.
1449
1495
  */
1450
1496
  export declare function decodeSixelToPng(bytes: Uint8Array): Uint8Array
1451
1497
 
@@ -2035,7 +2081,7 @@ export declare enum Encoding {
2035
2081
  export declare function execReplace(argv: Array<string>): void
2036
2082
 
2037
2083
  /**
2038
- * Execute a brush shell command.
2084
+ * Execute a command in the embedded bash engine (pi-shell).
2039
2085
  *
2040
2086
  * Creates a fresh session for each call. The `on_chunk` callback receives
2041
2087
  * streamed stdout/stderr output. Returns the exit code when the command
@@ -3364,6 +3410,19 @@ export declare function renderMermaidAscii(text: string, options?: MermaidRender
3364
3410
  */
3365
3411
  export declare function renderSnapcompactPng(text: string, options: SnapcompactRenderOptions): Promise<string>
3366
3412
 
3413
+ /**
3414
+ * Run the pi-shell engine as a process shell in this process (`omp zsh`,
3415
+ * `omp bash`, `omp sh`): `argv` is the shell's whole command line,
3416
+ * `argv[0]` naming it (`zsh`, `bash` or `sh` pick the dialect), as a real
3417
+ * shell gets it. Synchronous: blocks the calling thread until the shell
3418
+ * exits and returns the status the process should exit with (0–255; 101
3419
+ * after a panic). The shell uses the process's own stdin, stdout, stderr,
3420
+ * terminal and inherited descriptors (duplicated, never taken over); the
3421
+ * job-control and termination signals the runtime handles are reset to
3422
+ * their defaults for the run and restored after it.
3423
+ */
3424
+ export declare function runShellMain(argv: Array<string>): number
3425
+
3367
3426
  /**
3368
3427
  * Search content for a pattern (one-shot, compiles pattern each time).
3369
3428
  * For repeated searches with the same pattern, use [`grep`] with file filters.
@@ -3415,7 +3474,10 @@ export interface SearchResult {
3415
3474
 
3416
3475
  export declare function setHangulCompatJamoWidthOverride(value: number): void
3417
3476
 
3418
- /** Options for executing a shell command via brush-core. */
3477
+ /**
3478
+ * Options for executing a shell command via the embedded bash engine
3479
+ * (pi-shell).
3480
+ */
3419
3481
  export interface ShellExecuteOptions {
3420
3482
  /** Command string to execute in the shell. */
3421
3483
  command: string
@@ -3785,6 +3847,8 @@ export interface ShellRunOptions {
3785
3847
  filesystem?: ShellFilesystem
3786
3848
  /** Receives a copy of every byte crossing the run's pipeline pipes. */
3787
3849
  streams?: PipeStreams
3850
+ /** Folds the run's engine events into a live model of the command. */
3851
+ trace?: RunTrace
3788
3852
  }
3789
3853
 
3790
3854
  /** Result of running a shell command. */
@@ -3806,6 +3870,28 @@ export interface ShellRunResult {
3806
3870
  workingDir?: string
3807
3871
  }
3808
3872
 
3873
+ /** Native non-executing visualization transformations. */
3874
+ export interface ShellVizLayers {
3875
+ color: boolean
3876
+ inline: boolean
3877
+ idioms: boolean
3878
+ effects: boolean
3879
+ unwrap: boolean
3880
+ }
3881
+
3882
+ /** Native presentation controls. Node identities are opaque engine keys. */
3883
+ export interface ShellVizOptions {
3884
+ view: 'raw' | 'pseudo'
3885
+ layers: ShellVizLayers
3886
+ split: boolean
3887
+ cursor?: number
3888
+ cursorSeq?: number
3889
+ playing: boolean
3890
+ speed: number
3891
+ selectedNode?: string
3892
+ expandedNodes: Array<string>
3893
+ }
3894
+
3809
3895
  /**
3810
3896
  * Visible slice of a line after ANSI-aware column selection
3811
3897
  * (`sliceWithWidth`).
package/native/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { loadNative, missingNativeExport } from "./loader-state.js";
1
+ import { loadNative, missingNativeClass, missingNativeExport } from "./loader-state.js";
2
2
  import { adaptDesktopSession } from "./desktop-adapter.js";
3
3
 
4
4
  /**
@@ -23,31 +23,33 @@ import { adaptDesktopSession } from "./desktop-adapter.js";
23
23
  const nativeBindings = loadNative();
24
24
  // --- generated native exports (do not edit) ---
25
25
  // classes
26
- export const AudioCapture = nativeBindings.AudioCapture;
27
- export const AudioPlayback = nativeBindings.AudioPlayback;
26
+ export const AudioCapture = nativeBindings.AudioCapture ?? missingNativeClass("AudioCapture");
27
+ export const AudioPlayback = nativeBindings.AudioPlayback ?? missingNativeClass("AudioPlayback");
28
28
  export const DesktopSession = adaptDesktopSession(nativeBindings.DesktopSession);
29
- export const DiffStream = nativeBindings.DiffStream;
30
- export const EditSession = nativeBindings.EditSession;
31
- export const EditStore = nativeBindings.EditStore;
32
- export const FileLock = nativeBindings.FileLock;
33
- export const HighlightStream = nativeBindings.HighlightStream;
34
- export const IpcConnection = nativeBindings.IpcConnection;
35
- export const IpcServer = nativeBindings.IpcServer;
36
- export const LineMatcher = nativeBindings.LineMatcher;
37
- export const LiveWebRtcPeer = nativeBindings.LiveWebRtcPeer;
38
- export const MacAppearanceObserver = nativeBindings.MacAppearanceObserver;
39
- export const MacOSFrontApp = nativeBindings.MacOSFrontApp;
40
- export const NativeOAuthCallback = nativeBindings.NativeOAuthCallback;
41
- export const PipeStreams = nativeBindings.PipeStreams;
42
- export const PowerAssertion = nativeBindings.PowerAssertion;
43
- export const Process = nativeBindings.Process;
44
- export const PtySession = nativeBindings.PtySession;
45
- export const Shell = nativeBindings.Shell;
46
- export const TextPredictor = nativeBindings.TextPredictor;
47
- export const TtyWriter = nativeBindings.TtyWriter;
48
- export const VcsGitRepo = nativeBindings.VcsGitRepo;
49
- export const VcsJjWorkspace = nativeBindings.VcsJjWorkspace;
50
- export const VcsRepo = nativeBindings.VcsRepo;
29
+ export const DiffStream = nativeBindings.DiffStream ?? missingNativeClass("DiffStream");
30
+ export const EditSession = nativeBindings.EditSession ?? missingNativeClass("EditSession");
31
+ export const EditStore = nativeBindings.EditStore ?? missingNativeClass("EditStore");
32
+ export const FileLock = nativeBindings.FileLock ?? missingNativeClass("FileLock");
33
+ export const HighlightStream = nativeBindings.HighlightStream ?? missingNativeClass("HighlightStream");
34
+ export const IpcConnection = nativeBindings.IpcConnection ?? missingNativeClass("IpcConnection");
35
+ export const IpcServer = nativeBindings.IpcServer ?? missingNativeClass("IpcServer");
36
+ export const LineMatcher = nativeBindings.LineMatcher ?? missingNativeClass("LineMatcher");
37
+ export const LiveWebRtcPeer = nativeBindings.LiveWebRtcPeer ?? missingNativeClass("LiveWebRtcPeer");
38
+ export const MacAppearanceObserver = nativeBindings.MacAppearanceObserver ?? missingNativeClass("MacAppearanceObserver");
39
+ export const MacOSFrontApp = nativeBindings.MacOSFrontApp ?? missingNativeClass("MacOSFrontApp");
40
+ export const NativeOAuthCallback = nativeBindings.NativeOAuthCallback ?? missingNativeClass("NativeOAuthCallback");
41
+ export const PipeStreams = nativeBindings.PipeStreams ?? missingNativeClass("PipeStreams");
42
+ export const PowerAssertion = nativeBindings.PowerAssertion ?? missingNativeClass("PowerAssertion");
43
+ export const Process = nativeBindings.Process ?? missingNativeClass("Process");
44
+ export const PtySession = nativeBindings.PtySession ?? missingNativeClass("PtySession");
45
+ export const RunTrace = nativeBindings.RunTrace ?? missingNativeClass("RunTrace");
46
+ export const Shell = nativeBindings.Shell ?? missingNativeClass("Shell");
47
+ export const ShellVisualization = nativeBindings.ShellVisualization ?? missingNativeClass("ShellVisualization");
48
+ export const TextPredictor = nativeBindings.TextPredictor ?? missingNativeClass("TextPredictor");
49
+ export const TtyWriter = nativeBindings.TtyWriter ?? missingNativeClass("TtyWriter");
50
+ export const VcsGitRepo = nativeBindings.VcsGitRepo ?? missingNativeClass("VcsGitRepo");
51
+ export const VcsJjWorkspace = nativeBindings.VcsJjWorkspace ?? missingNativeClass("VcsJjWorkspace");
52
+ export const VcsRepo = nativeBindings.VcsRepo ?? missingNativeClass("VcsRepo");
51
53
 
52
54
  // functions
53
55
  export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime ?? missingNativeExport("__ompInstallTokioRuntime");
@@ -135,6 +137,7 @@ export const readImageFromClipboard = nativeBindings.readImageFromClipboard ?? m
135
137
  export const readTextFromClipboard = nativeBindings.readTextFromClipboard ?? missingNativeExport("readTextFromClipboard");
136
138
  export const renderMermaidAscii = nativeBindings.renderMermaidAscii ?? missingNativeExport("renderMermaidAscii");
137
139
  export const renderSnapcompactPng = nativeBindings.renderSnapcompactPng ?? missingNativeExport("renderSnapcompactPng");
140
+ export const runShellMain = nativeBindings.runShellMain ?? missingNativeExport("runShellMain");
138
141
  export const search = nativeBindings.search ?? missingNativeExport("search");
139
142
  export const setHangulCompatJamoWidthOverride = nativeBindings.setHangulCompatJamoWidthOverride ?? missingNativeExport("setHangulCompatJamoWidthOverride");
140
143
  export const sliceWithWidth = nativeBindings.sliceWithWidth ?? missingNativeExport("sliceWithWidth");
@@ -145,6 +145,29 @@ export function missingNativeExport(
145
145
  addon?: NativeAddonStatus | null,
146
146
  ): (() => never) | undefined;
147
147
 
148
+ /**
149
+ * Stub for a class the addon does not provide: `undefined` on a current addon,
150
+ * a class whose constructor throws on a stale one.
151
+ */
152
+ export function missingNativeClass(
153
+ symbolName: string,
154
+ addon?: NativeAddonStatus | null,
155
+ ): (new (...args: unknown[]) => never) | undefined;
156
+
157
+ /** Notice a workspace tree prints when its addon is from another release. */
158
+ export function staleWorkspaceAddonNotice(addon: NativeAddonStatus): string;
159
+
160
+ /**
161
+ * Write {@link staleWorkspaceAddonNotice} once, from the main thread, and only
162
+ * on a workspace load. The latch is per loaded copy of this module. Returns
163
+ * whether this call wrote it.
164
+ */
165
+ export function reportStaleWorkspaceAddon(
166
+ ctx: { isWorkspaceLoad: boolean },
167
+ addon: NativeAddonStatus,
168
+ deps?: { write?: (text: string) => void; state?: { reported: boolean } },
169
+ ): boolean;
170
+
148
171
  /** Actionable text for {@link missingNativeExport}. */
149
172
  export function missingNativeExportMessage(symbolName: string, addon?: NativeAddonStatus | null): string;
150
173
 
@@ -693,6 +693,48 @@ export function missingNativeExport(symbolName, addon = loadedAddon) {
693
693
  };
694
694
  }
695
695
 
696
+ /**
697
+ * Static reads a {@link missingNativeClass} stand-in answers normally rather
698
+ * than throwing: the function-object intrinsics every caller and inspector
699
+ * touches, and `then`, so an accidental `await` on the export resolves to the
700
+ * class instead of exploding somewhere unrelated.
701
+ */
702
+ const CLASS_STUB_PLAIN_STATICS = new Set(["prototype", "name", "length", "constructor", "then"]);
703
+
704
+ /**
705
+ * Class counterpart of {@link missingNativeExport}: a stand-in that fails with
706
+ * the addon's identity attached instead of as a bare `undefined` export, which
707
+ * surfaces as `undefined is not a constructor` at the callsite and names
708
+ * nothing.
709
+ *
710
+ * Same staleness rule as the function stub: only a stale addon gets a
711
+ * stand-in, so `typeof native.Thing === "function"` capability probes against
712
+ * a current addon keep their meaning.
713
+ *
714
+ * Several of these classes are entered through a static rather than a
715
+ * constructor — `FileLock.tryAcquire`, `IpcServer.listen`, `Process.fromPid`,
716
+ * `PowerAssertion.start` — so the stand-in answers any other static read with
717
+ * the same diagnostic. Only a stale addon reaches it, and only for a class
718
+ * that addon does not carry, so nothing on a healthy tree meets a proxy.
719
+ * @param {string} symbolName
720
+ * @param {ReturnType<typeof nativeAddonStatus>} [addon]
721
+ * @returns {(new (...args: unknown[]) => never) | undefined}
722
+ */
723
+ export function missingNativeClass(symbolName, addon = loadedAddon) {
724
+ if (!addon?.stale) return undefined;
725
+ const stub = class MissingNativeClass {
726
+ constructor() {
727
+ throw new Error(missingNativeExportMessage(symbolName, addon));
728
+ }
729
+ };
730
+ return new Proxy(stub, {
731
+ get(target, key, receiver) {
732
+ if (typeof key === "symbol" || CLASS_STUB_PLAIN_STATICS.has(key)) return Reflect.get(target, key, receiver);
733
+ throw new Error(missingNativeExportMessage(`${symbolName}.${key}`, addon));
734
+ },
735
+ });
736
+ }
737
+
696
738
  /**
697
739
  * Actionable text for {@link missingNativeExport}. `addon` is injectable so the
698
740
  * wording can be pinned without a stale `.node` on disk.
@@ -715,6 +757,106 @@ export function missingNativeExportMessage(symbolName, addon = loadedAddon) {
715
757
  );
716
758
  }
717
759
 
760
+ /**
761
+ * One-line notice for a workspace tree running an addon from another release.
762
+ *
763
+ * The missing-symbol stubs only speak when a caller touches a symbol the addon
764
+ * does not carry. A symbol it does carry runs that release's behaviour — an
765
+ * older shell, an older grep — with no marker at all, so this notice is the
766
+ * only provenance those failures get.
767
+ *
768
+ * `stale` is any release difference, so the wording must not assume the addon
769
+ * is the older side.
770
+ * @param {NonNullable<ReturnType<typeof nativeAddonStatus>>} addon
771
+ * @returns {string}
772
+ */
773
+ export function staleWorkspaceAddonNotice(addon) {
774
+ const loaded = addon.version ? `@oh-my-pi/pi-natives@${addon.version}` : "an addon without a release stamp";
775
+ return (
776
+ `[pi-natives] ${addon.path} is ${loaded}, but this checkout is @${addon.packageVersion}. ` +
777
+ "Symbols this checkout expects may be missing and shared ones may behave differently; " +
778
+ "run `bun run build:native` before trusting results from this tree.\n"
779
+ );
780
+ }
781
+
782
+ /**
783
+ * Whether the copy of this module running on the current thread has already
784
+ * written the mismatched-addon notice. One per loaded copy, which is one per
785
+ * main thread in every shape this package ships; the write itself is confined
786
+ * to the main thread by {@link onMainThread}.
787
+ */
788
+ const staleNoticeState = { reported: false };
789
+
790
+ /** Memoized answer from {@link onMainThread}; `undefined` until first asked. */
791
+ let mainThreadAnswer;
792
+
793
+ /**
794
+ * True on a process's main thread, false inside a worker thread.
795
+ *
796
+ * Resolved through `createRequire` on first use rather than imported at module
797
+ * scope: `packages/coding-agent/src/cli.ts` keeps `node:worker_threads` out of
798
+ * ordinary startup on purpose, and this module is reached statically from
799
+ * `sqlite-locks` on launches that never load an addon. Asking only when a
800
+ * mismatch has already been found keeps that boundary. A runtime without the
801
+ * builtin answers "main thread", which is the safe side: the notice prints.
802
+ * @returns {boolean}
803
+ */
804
+ function onMainThread() {
805
+ if (mainThreadAnswer === undefined) {
806
+ try {
807
+ mainThreadAnswer = createRequire(import.meta.url)("node:worker_threads").isMainThread !== false;
808
+ } catch {
809
+ mainThreadAnswer = true;
810
+ }
811
+ }
812
+ return mainThreadAnswer;
813
+ }
814
+
815
+ /**
816
+ * Write {@link staleWorkspaceAddonNotice} once, from the main thread, when a
817
+ * workspace tree loads an addon of another release.
818
+ *
819
+ * Three guards, each load-bearing.
820
+ *
821
+ * Main thread only, because fd 2 is shared and a worker reaches `loadNative()`
822
+ * long after interactive mode owns the terminal — the subagent, JS-eval and
823
+ * tab workers all start then, so a per-worker write paints into a rendered
824
+ * frame. The main thread has already said it at startup; a worker repeating it
825
+ * adds nothing and costs the display. A worker *process* keeps the notice: it
826
+ * has its own main thread, and `subprocess/worker-client.ts` captures its
827
+ * stderr to a file rather than the terminal.
828
+ *
829
+ * Latched, because `loadNative()` is not a startup-only call: the lazy
830
+ * entrypoints in `clipboard.js` and friends call it on every invocation.
831
+ *
832
+ * Workspace loads only, because {@link validateLoadedBindings} has a second
833
+ * success path — it accepts a pre-sentinel addon on an installed load
834
+ * (`isCompatiblePreSentinelNativeAddon`), which reports no release and so
835
+ * counts as `stale`. That user has no repository to build.
836
+ *
837
+ * The write is a synchronous `fs.writeSync` to fd 2 for the same reason as
838
+ * {@link startupMarker}: it must survive a crash that follows it. `write` and
839
+ * `state` are injectable so a test can exercise the guards without consuming
840
+ * this copy's latch.
841
+ * @param {{ isWorkspaceLoad: boolean }} ctx
842
+ * @param {NonNullable<ReturnType<typeof nativeAddonStatus>>} addon
843
+ * @param {{ write?: (text: string) => void; state?: { reported: boolean } }} [deps]
844
+ * @returns {boolean} whether this call wrote the notice
845
+ */
846
+ export function reportStaleWorkspaceAddon(ctx, addon, deps = {}) {
847
+ const state = deps.state ?? staleNoticeState;
848
+ // Thread identity last: the three cheap gates reject every ordinary launch
849
+ // before `node:worker_threads` is resolved at all.
850
+ if (!ctx.isWorkspaceLoad || !addon.stale || state.reported || !onMainThread()) return false;
851
+ state.reported = true;
852
+ try {
853
+ (deps.write ?? (text => fs.writeSync(2, text)))(staleWorkspaceAddonNotice(addon));
854
+ } catch {
855
+ // stderr unavailable; the notice is best-effort, the stubs still speak
856
+ }
857
+ return true;
858
+ }
859
+
718
860
  /**
719
861
  * Install the addon's bounded Tokio runtime now that `dlopen` has returned and
720
862
  * the dynamic-loader lock is released. The Rust `#[module_init]` deliberately
@@ -881,6 +1023,7 @@ export function loadNative() {
881
1023
  installNativeTokioRuntime(bindings);
882
1024
  configureGrammarDir(bindings, ctx.nativesDir);
883
1025
  loadedAddon = describeLoadedAddon(bindings, candidate, ctx);
1026
+ reportStaleWorkspaceAddon(ctx, loadedAddon);
884
1027
  cleanupStaleNativeVersions({ nativesDir: ctx.nativesDir, currentVersion: ctx.packageVersion });
885
1028
  startupMarker("native:loadNative:done");
886
1029
  return bindings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-natives",
3
- "version": "18.16.0",
3
+ "version": "18.18.0",
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",
@@ -35,7 +35,7 @@
35
35
  "build": "bun ../../scripts/bazel-natives.ts host --dest native",
36
36
  "build:bindings": "bun scripts/build-bindings.ts",
37
37
  "check": "oxlint . && oxfmt --check --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts' && bun run check:types",
38
- "check:types": "tsgo -p tsconfig.json --noEmit",
38
+ "check:types": "bun check --no-script -p tsconfig.json",
39
39
  "lint": "oxlint .",
40
40
  "test": "bun test --parallel",
41
41
  "fix": "oxlint --fix --fix-suggestions . && bun run fmt",
@@ -49,7 +49,6 @@
49
49
  "@napi-rs/cli": "3.7.2",
50
50
  "@types/bun": "^1.3.14",
51
51
  "cli-truncate": "6.1.1",
52
- "diff": "9.0.0",
53
52
  "gpt-tokenizer": "4.0.0",
54
53
  "mitata": "1.0.34",
55
54
  "slice-ansi": "9.0.0",
@@ -121,11 +120,11 @@
121
120
  }
122
121
  },
123
122
  "optionalDependencies": {
124
- "@oh-my-pi/pi-natives-linux-x64": "18.16.0",
125
- "@oh-my-pi/pi-natives-linux-arm64": "18.16.0",
126
- "@oh-my-pi/pi-natives-darwin-x64": "18.16.0",
127
- "@oh-my-pi/pi-natives-darwin-arm64": "18.16.0",
128
- "@oh-my-pi/pi-natives-win32-x64": "18.16.0",
129
- "@oh-my-pi/pi-natives-win32-arm64": "18.16.0"
123
+ "@oh-my-pi/pi-natives-linux-x64": "18.18.0",
124
+ "@oh-my-pi/pi-natives-linux-arm64": "18.18.0",
125
+ "@oh-my-pi/pi-natives-darwin-x64": "18.18.0",
126
+ "@oh-my-pi/pi-natives-darwin-arm64": "18.18.0",
127
+ "@oh-my-pi/pi-natives-win32-x64": "18.18.0",
128
+ "@oh-my-pi/pi-natives-win32-arm64": "18.18.0"
130
129
  }
131
130
  }