@gajae-code/natives 0.11.0 → 0.11.2

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/index.d.ts CHANGED
@@ -114,8 +114,9 @@ export declare class NotificationServer {
114
114
  /** Register the reply callback. Must be called before [`Self::start`]. */
115
115
  onReply(callback: (err: null | Error, reply: ReplyEvent) => void): void
116
116
  /**
117
- * Register the inbound-message callback (free-text injections and in-thread
118
- * config commands). Must be called before [`Self::start`].
117
+ * Register the authenticated inbound-message callback (free-text,
118
+ * side-question request/cancel, and in-thread config/control commands).
119
+ * Must be called before [`Self::start`].
119
120
  */
120
121
  onInbound(callback: (err: null | Error, msg: InboundEvent) => void): void
121
122
  /**
@@ -123,6 +124,11 @@ export declare class NotificationServer {
123
124
  * [`Self::start`].
124
125
  */
125
126
  onSdkFrame(callback: (err: null | Error, frame: SdkFrameEvent) => void): void
127
+ /**
128
+ * Register the negotiated-capabilities callback. Must be called before
129
+ * [`Self::start`].
130
+ */
131
+ onNegotiatedCapabilities(callback: (err: null | Error, connectionId: string, capabilities: string[]) => void): void
126
132
  /**
127
133
  * Register the connection-close callback. Must be called before
128
134
  * [`Self::start`].
@@ -176,14 +182,30 @@ export declare class NotificationServer {
176
182
  /**
177
183
  * Broadcast an ephemeral threaded-session frame. `frame_json` is a JSON
178
184
  * `ServerMessage` (e.g. `identity_header`, `context_update`, `turn_stream`,
179
- * `image_attachment`, `session_closed`, `config_update`, `hello`). Not
180
- * buffered for replay.
185
+ * `ephemeral_turn_result`, `image_attachment`, `session_closed`,
186
+ * `config_update`, `hello`). Not buffered for replay.
181
187
  *
182
188
  * # Errors
183
189
  * Fails if not started or `frame_json` is not a valid `ServerMessage`.
184
190
  */
185
191
  pushFrame(frameJson: string): void
186
- /** Send raw JSON to one connected v3 SDK client. */
192
+ /**
193
+ * Broadcast a TypeScript-constructed turn frame without re-parsing JSON.
194
+ * External frames must continue through [`Self::push_frame`] for serde
195
+ * validation.
196
+ */
197
+ pushTurnStreamUnchecked(sessionId: string, phase: string, text: string, finalAnswer?: boolean | undefined | null, messageRef?: string | undefined | null): void
198
+ /**
199
+ * Broadcast a file attachment from raw N-API bytes, encoding the unchanged
200
+ * base64 wire field only in Rust.
201
+ */
202
+ pushFileAttachmentUnchecked(sessionId: string, name: string, mime: string | undefined | null, data: Buffer, caption?: string | undefined | null): void
203
+ /**
204
+ * Return counters guarding the known-good frame crossing against
205
+ * regressions.
206
+ */
207
+ knownGoodFrameStats(): KnownGoodFrameStats
208
+ /** Send a validated, bounded JSON envelope to one connected v3 SDK client. */
187
209
  sendTo(connectionId: string, json: string): void
188
210
  /**
189
211
  * Publish a replayable `session_ready` readiness signal. `ready_json` is a
@@ -248,6 +270,8 @@ export declare class NotificationServer {
248
270
  clientCount(): number
249
271
  /** Stop the server (idempotent) and remove the endpoint discovery file. */
250
272
  stop(): void
273
+ /** Stop the server and resolve only after all native socket owners exit. */
274
+ stopAndWait(): Promise<void>
251
275
  }
252
276
 
253
277
  /** Stable process reference. */
@@ -349,7 +373,7 @@ export declare class Shell {
349
373
  * `packages/natives/native/index.js` (which derives the name from
350
374
  * `package.json#version`).
351
375
  */
352
- export declare function __piNativesV0_11_0(): void
376
+ export declare function __piNativesV0_11_2(): void
353
377
 
354
378
  /**
355
379
  * Apply conservative pre-execution rewrites to a bash command.
@@ -360,6 +384,8 @@ export declare function __piNativesV0_11_0(): void
360
384
  */
361
385
  export declare function applyBashFixups(command: string): BashFixupResult
362
386
 
387
+ export declare function applyOwnerOnlyPathSecurity(path: string, kind: "directory" | "file"): NativeOwnerOnlySecurityResult
388
+
363
389
  /** Typed terminal acknowledgement result returned by acknowledgement promises. */
364
390
  export interface AskSelectedAckOutcomeEvent {
365
391
  status: string
@@ -569,6 +595,8 @@ export interface BuildInfo {
569
595
  languageSet: string
570
596
  }
571
597
 
598
+ export declare function canonicalExistingDirectoryIdentity(path: string | Uint8Array): NativeCanonicalDirectoryIdentity
599
+
572
600
  /** Clipboard image payload encoded as PNG bytes. */
573
601
  export interface ClipboardImage {
574
602
  /** PNG-encoded image bytes. */
@@ -681,6 +709,29 @@ export declare enum Ellipsis {
681
709
  */
682
710
  export declare function encodeSixel(bytes: Uint8Array, targetWidthPx: number, targetHeightPx: number): string
683
711
 
712
+ /**
713
+ * Remove an already durably planned detached directory only when a fresh
714
+ * descriptor-relative snapshot exactly equals the persisted snapshot. The
715
+ * caller-planned root remains in place while its opened descriptor is
716
+ * authoritative throughout recursive removal.
717
+ */
718
+ export declare function exactRemoveDirectoryTree(path: string, snapshot: NativeDirectoryTreeSnapshot): NativeExactUnlinkResult
719
+
720
+ /**
721
+ * Restore only the detached object that still has the supplied platform
722
+ * identity. The detached and original paths must retain the same validated
723
+ * parent, and restoration never replaces an existing original path.
724
+ */
725
+ export declare function exactRestore(detachedPath: string, originalPath: string, identity: NativeExactFileIdentity): NativeExactUnlinkResult
726
+
727
+ /**
728
+ * Delete only the regular file that still has the supplied platform identity.
729
+ *
730
+ * This never follows a symlink or reparse point in the target path and reports
731
+ * validation failures as typed results rather than deleting a replacement.
732
+ */
733
+ export declare function exactUnlink(path: string, identity: NativeExactFileIdentity): NativeExactUnlinkResult
734
+
684
735
  /**
685
736
  * Execute a brush shell command.
686
737
  *
@@ -1043,26 +1094,51 @@ export interface HtmlToMarkdownOptions {
1043
1094
  }
1044
1095
 
1045
1096
  /**
1046
- * An inbound message forwarded to the TypeScript host: a free-text injection,
1047
- * in-thread config command, or deterministic control command.
1097
+ * An authenticated inbound message forwarded to the TypeScript host: free-text
1098
+ * injection, ephemeral side-question request/cancel, in-thread config command,
1099
+ * or deterministic control command.
1048
1100
  */
1049
1101
  export interface InboundEvent {
1050
- /** Inbound kind (`user_message`, `config_command`, or `control_command`). */
1102
+ /**
1103
+ * Inbound kind (`user_message`, `ephemeral_turn`,
1104
+ * `ephemeral_turn_cancel`, `config_command`, or `control_command`).
1105
+ */
1051
1106
  kind: string
1107
+ /**
1108
+ * Server-authenticated identity of the WebSocket connection that delivered
1109
+ * this event.
1110
+ */
1111
+ connectionId: string
1052
1112
  /** The session this inbound belongs to. */
1053
1113
  sessionId: string
1054
- /** Free-text body (`user_message` only). */
1114
+ /** Free-text body (`user_message` or `ephemeral_turn` only). */
1055
1115
  text?: string
1056
- /** Telegram update id for dedupe (`user_message` only). */
1116
+ /**
1117
+ * Telegram update id for dedupe (`user_message`, `ephemeral_turn`, or
1118
+ * `ephemeral_turn_cancel` only).
1119
+ */
1057
1120
  updateId?: number
1058
- /** Originating thread/topic id (`user_message` only). */
1121
+ /**
1122
+ * Originating thread/topic id (`user_message`, `ephemeral_turn`, or
1123
+ * `ephemeral_turn_cancel` only).
1124
+ */
1059
1125
  threadId?: string
1126
+ /**
1127
+ * Originating Telegram message id (`ephemeral_turn` and
1128
+ * `ephemeral_turn_cancel` only).
1129
+ */
1130
+ messageId?: number
1060
1131
  /** Requested verbosity `"lean"|"verbose"` (`config_command` only). */
1061
1132
  verbosity?: string
1062
1133
  /** Requested redaction state (`config_command` only). */
1063
1134
  redact?: boolean
1064
- /** Client-generated request id (`control_command` only). */
1135
+ /**
1136
+ * Client-generated request id (`ephemeral_turn`, `ephemeral_turn_cancel`,
1137
+ * or `control_command` only).
1138
+ */
1065
1139
  requestId?: string
1140
+ /** Cancellation reason (`ephemeral_turn_cancel` only). */
1141
+ reason?: string
1066
1142
  /** JSON-encoded command payload (`control_command` only). */
1067
1143
  commandJson?: string
1068
1144
  /**
@@ -1214,6 +1290,19 @@ export declare enum KeyEventType {
1214
1290
  Release = 3
1215
1291
  }
1216
1292
 
1293
+ /** Observable counters for the internal known-good N-API frame lane. */
1294
+ export interface KnownGoodFrameStats {
1295
+ /** Frames constructed as `TurnStream` without parsing a JSON string. */
1296
+ knownGoodTurnStreamFrames: number
1297
+ /** JSON serde parses of externally supplied `turn_stream` frames. */
1298
+ turnStreamSerdeValidationParses: number
1299
+ /**
1300
+ * Base64 characters encoded in Rust for `file_attachment` frames (the JS
1301
+ * side crosses raw `Buffer` bytes and never allocates the base64 string).
1302
+ */
1303
+ fileAttachmentRustBase64Chars: number
1304
+ }
1305
+
1217
1306
  /** A lifecycle request forwarded to the TypeScript daemon for orchestration. */
1218
1307
  export interface LifecycleRequestEvent {
1219
1308
  /** One of `"session_create"`, `"session_close"`, `"session_resume"`. */
@@ -1413,6 +1502,103 @@ export interface MinimizerResult {
1413
1502
 
1414
1503
  export declare function nativeBuildInfo(): BuildInfo
1415
1504
 
1505
+ /** Result of resolving an existing directory to its stable platform identity. */
1506
+ export type NativeCanonicalDirectoryIdentity =
1507
+ | { ok: true; platform: "posix" | "win32"; canonicalPath: string; code?: never }
1508
+ | {
1509
+ ok: false;
1510
+ platform?: never;
1511
+ canonicalPath?: never;
1512
+ code: "not_found" | "not_directory" | "not_utf8" | "network_unsupported" | "identity_unavailable" | "io_error";
1513
+ }
1514
+
1515
+ /**
1516
+ * A deterministic, no-follow description of a directory tree. `relative_path`
1517
+ * is UTF-8, uses `/` separators, and is empty only for the root entry.
1518
+ */
1519
+ export interface NativeDirectoryTreeEntry {
1520
+ relativePath: string
1521
+ kind: string
1522
+ dev: string
1523
+ ino: string
1524
+ size: string
1525
+ mtimeNs: string
1526
+ sha256?: string
1527
+ }
1528
+
1529
+ export interface NativeDirectoryTreeResult {
1530
+ ok: boolean
1531
+ code?: string
1532
+ snapshot?: NativeDirectoryTreeSnapshot
1533
+ }
1534
+
1535
+ /**
1536
+ * Stable evidence returned by `snapshot_directory_tree` and consumed verbatim
1537
+ * by `exact_remove_directory_tree`.
1538
+ */
1539
+ export interface NativeDirectoryTreeSnapshot {
1540
+ rootDev: string
1541
+ rootIno: string
1542
+ entries: Array<NativeDirectoryTreeEntry>
1543
+ }
1544
+
1545
+ /**
1546
+ * Caller-supplied identity and preauthorized quarantine evidence for exact
1547
+ * deletion.
1548
+ */
1549
+ export interface NativeExactFileIdentity {
1550
+ dev: bigint
1551
+ ino: bigint
1552
+ size: bigint
1553
+ mtimeNs: bigint
1554
+ /**
1555
+ * When true, atomically detach a directory rather than deleting a regular
1556
+ * file.
1557
+ */
1558
+ directory?: boolean
1559
+ /**
1560
+ * Keep a regular file in quarantine after its identity has been verified
1561
+ * instead of unlinking it. This makes cross-device retirement recoverable.
1562
+ */
1563
+ detachOnly?: boolean
1564
+ /**
1565
+ * A caller-persisted, single-component no-replace quarantine destination.
1566
+ * Required for every exact deletion so authority survives a post-detach
1567
+ * crash.
1568
+ */
1569
+ quarantineName?: string
1570
+ /**
1571
+ * SHA-256 of regular-file bytes. Required for regular-file deletion and
1572
+ * verified from the detached object before unlinking it.
1573
+ */
1574
+ sha256?: string
1575
+ }
1576
+
1577
+ /** Typed result of an identity-bound regular-file deletion or directory detach. */
1578
+ export interface NativeExactUnlinkResult {
1579
+ ok: boolean
1580
+ code?: string
1581
+ detachedPath?: string
1582
+ }
1583
+
1584
+ /** Result of applying or checking owner-only path security. */
1585
+ export type NativeOwnerOnlySecurityResult =
1586
+ | { ok: true; code?: never }
1587
+ | {
1588
+ ok: false;
1589
+ code:
1590
+ | "not_found"
1591
+ | "not_directory"
1592
+ | "network_unsupported"
1593
+ | "reparse_point"
1594
+ | "acl_unavailable"
1595
+ | "acl_apply_failed"
1596
+ | "acl_verify_failed"
1597
+ | "identity_unavailable"
1598
+ | "owner_mismatch"
1599
+ | "io_error";
1600
+ }
1601
+
1416
1602
  /** Bound endpoint info returned from [`NotificationServer::start`]. */
1417
1603
  export interface NotificationEndpoint {
1418
1604
  /** Bind host (loopback). */
@@ -1541,6 +1727,8 @@ export declare function ptyTimeoutCount(): bigint
1541
1727
  */
1542
1728
  export declare function readImageFromClipboard(): Promise<ClipboardImage | undefined | null>
1543
1729
 
1730
+ export declare function renameNoReplacePath(sourcePath: string, destinationPath: string): NativeExactUnlinkResult
1731
+
1544
1732
  /** A client reply forwarded to the TypeScript host for gate resolution. */
1545
1733
  export interface ReplyEvent {
1546
1734
  /**
@@ -1696,6 +1884,13 @@ export interface SliceResult {
1696
1884
  */
1697
1885
  export declare function sliceWithWidth(line: string, startCol: number, length: number, strict: boolean | undefined | null, tabWidth: number): SliceResult
1698
1886
 
1887
+ /**
1888
+ * Capture a deterministic, descriptor-relative snapshot of a regular-file and
1889
+ * directory-only tree. Symlinks, special files, non-UTF-8 names, and topology
1890
+ * changes are rejected rather than followed.
1891
+ */
1892
+ export declare function snapshotDirectoryTree(path: string): NativeDirectoryTreeResult
1893
+
1699
1894
  export declare function summarizeCode(options: SummaryOptions): SummaryResult
1700
1895
 
1701
1896
  export interface SummaryOptions {
@@ -1747,6 +1942,8 @@ export declare function truncateLinesToWidth(lines: Array<string>, maxWidth: num
1747
1942
 
1748
1943
  export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind: Ellipsis | undefined | null, pad: boolean | undefined | null, tabWidth: number): string
1749
1944
 
1945
+ export declare function verifyOwnerOnlyPathSecurity(path: string, kind: "directory" | "file"): NativeOwnerOnlySecurityResult
1946
+
1750
1947
  /**
1751
1948
  * Calculate visible width of text, excluding ANSI escape sequences.
1752
1949
  *
package/native/index.js CHANGED
@@ -27,15 +27,20 @@ export const PtySession = nativeBindings.PtySession;
27
27
  export const Shell = nativeBindings.Shell;
28
28
 
29
29
  // functions
30
- export const __piNativesV0_11_0 = nativeBindings.__piNativesV0_11_0;
30
+ export const __piNativesV0_11_2 = nativeBindings.__piNativesV0_11_2;
31
31
  export const applyBashFixups = nativeBindings.applyBashFixups;
32
+ export const applyOwnerOnlyPathSecurity = nativeBindings.applyOwnerOnlyPathSecurity;
32
33
  export const astEdit = nativeBindings.astEdit;
33
34
  export const astGrep = nativeBindings.astGrep;
35
+ export const canonicalExistingDirectoryIdentity = nativeBindings.canonicalExistingDirectoryIdentity;
34
36
  export const computerScreenshot = nativeBindings.computerScreenshot;
35
37
  export const copyToClipboard = nativeBindings.copyToClipboard;
36
38
  export const detectMacOSAppearance = nativeBindings.detectMacOSAppearance;
37
39
  export const diffLines = nativeBindings.diffLines;
38
40
  export const encodeSixel = nativeBindings.encodeSixel;
41
+ export const exactRemoveDirectoryTree = nativeBindings.exactRemoveDirectoryTree;
42
+ export const exactRestore = nativeBindings.exactRestore;
43
+ export const exactUnlink = nativeBindings.exactUnlink;
39
44
  export const executeShell = nativeBindings.executeShell;
40
45
  export const extractSegments = nativeBindings.extractSegments;
41
46
  export const fuzzyFind = nativeBindings.fuzzyFind;
@@ -67,12 +72,15 @@ export const parseKey = nativeBindings.parseKey;
67
72
  export const parseKittySequence = nativeBindings.parseKittySequence;
68
73
  export const ptyTimeoutCount = nativeBindings.ptyTimeoutCount;
69
74
  export const readImageFromClipboard = nativeBindings.readImageFromClipboard;
75
+ export const renameNoReplacePath = nativeBindings.renameNoReplacePath;
70
76
  export const search = nativeBindings.search;
71
77
  export const sliceWithWidth = nativeBindings.sliceWithWidth;
78
+ export const snapshotDirectoryTree = nativeBindings.snapshotDirectoryTree;
72
79
  export const summarizeCode = nativeBindings.summarizeCode;
73
80
  export const supportsLanguage = nativeBindings.supportsLanguage;
74
81
  export const truncateLinesToWidth = nativeBindings.truncateLinesToWidth;
75
82
  export const truncateToWidth = nativeBindings.truncateToWidth;
83
+ export const verifyOwnerOnlyPathSecurity = nativeBindings.verifyOwnerOnlyPathSecurity;
76
84
  export const visibleWidth = nativeBindings.visibleWidth;
77
85
  export const visibleWidths = nativeBindings.visibleWidths;
78
86
  export const wrapTextWithAnsi = nativeBindings.wrapTextWithAnsi;
@@ -71,4 +71,12 @@ export interface LoadFromCandidatesResult<T> {
71
71
 
72
72
  export function loadFromCandidates<T>(input: LoadFromCandidatesInput<T>): LoadFromCandidatesResult<T>;
73
73
 
74
+ export interface CachedEmbeddedExtractionIsFreshInput {
75
+ targetPath: string;
76
+ embeddedPath: string;
77
+ sizeOf: (path: string) => number | null;
78
+ }
79
+
80
+ export function cachedEmbeddedExtractionIsFresh(input: CachedEmbeddedExtractionIsFreshInput): boolean;
81
+
74
82
  export function loadNative(): Record<string, unknown>;
@@ -223,6 +223,23 @@ export function loadFromCandidates({ candidates, requireCandidate, validateCandi
223
223
  return { bindings: null, errors };
224
224
  }
225
225
 
226
+ /**
227
+ * Decide whether a previously extracted embedded addon may be reused. A cached
228
+ * extraction from an earlier build of the same version carries the same version
229
+ * sentinel yet can expose a different native surface, so it is fresh only when
230
+ * its byte size matches the embedded payload. `sizeOf` returns the byte size of
231
+ * a path, or `null` when it cannot be inspected.
232
+ * @param {{ targetPath: string; embeddedPath: string; sizeOf: (path: string) => number | null }} input
233
+ * @returns {boolean}
234
+ */
235
+ export function cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath, sizeOf }) {
236
+ const cachedSize = sizeOf(targetPath);
237
+ if (cachedSize === null) return false;
238
+ const embeddedSize = sizeOf(embeddedPath);
239
+ if (embeddedSize === null) return false;
240
+ return cachedSize === embeddedSize;
241
+ }
242
+
226
243
  // =========================================================================
227
244
  // Side-effectful loader. Everything below runs only when `loadNative()` is
228
245
  // called from `native/index.js` — tests that only import the pure helpers
@@ -310,7 +327,23 @@ function maybeExtractEmbeddedAddon(ctx, errors) {
310
327
  const selectedEmbeddedFile = selectEmbeddedAddonFile(ctx.selectedVariant);
311
328
  if (!selectedEmbeddedFile) return null;
312
329
  const targetPath = path.join(ctx.versionedDir, selectedEmbeddedFile.filename);
313
- if (fs.existsSync(targetPath)) return targetPath;
330
+ if (fs.existsSync(targetPath)) {
331
+ // Guard against intra-version drift: a cached extraction written by an earlier
332
+ // build of the same version carries the same version sentinel but can expose a
333
+ // different native surface (e.g. a symbol added mid-cycle). The embedded addon
334
+ // is the source of truth, so reuse the cached file only when it matches the
335
+ // embedded payload size and re-extract otherwise.
336
+ const sizeOf = candidate => {
337
+ try {
338
+ return fs.statSync(candidate).size;
339
+ } catch {
340
+ return null;
341
+ }
342
+ };
343
+ if (cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath: selectedEmbeddedFile.filePath, sizeOf })) {
344
+ return targetPath;
345
+ }
346
+ }
314
347
 
315
348
  try {
316
349
  fs.mkdirSync(ctx.versionedDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-code/natives",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Native Rust bindings for grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
5
5
  "type": "module",
6
6
  "homepage": "https://gajae-code.com",
@@ -61,11 +61,11 @@
61
61
  "README.md"
62
62
  ],
63
63
  "optionalDependencies": {
64
- "@gajae-code/natives-darwin-arm64": "0.11.0",
65
- "@gajae-code/natives-darwin-x64": "0.11.0",
66
- "@gajae-code/natives-linux-arm64": "0.11.0",
67
- "@gajae-code/natives-linux-x64": "0.11.0",
68
- "@gajae-code/natives-win32-x64": "0.11.0"
64
+ "@gajae-code/natives-darwin-arm64": "0.11.2",
65
+ "@gajae-code/natives-darwin-x64": "0.11.2",
66
+ "@gajae-code/natives-linux-arm64": "0.11.2",
67
+ "@gajae-code/natives-linux-x64": "0.11.2",
68
+ "@gajae-code/natives-win32-x64": "0.11.2"
69
69
  },
70
70
  "exports": {
71
71
  ".": {