@gajae-code/natives 0.10.2 → 0.11.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.
package/native/index.d.ts CHANGED
@@ -118,6 +118,16 @@ export declare class NotificationServer {
118
118
  * config commands). Must be called before [`Self::start`].
119
119
  */
120
120
  onInbound(callback: (err: null | Error, msg: InboundEvent) => void): void
121
+ /**
122
+ * Register the raw v3 SDK frame callback. Must be called before
123
+ * [`Self::start`].
124
+ */
125
+ onSdkFrame(callback: (err: null | Error, frame: SdkFrameEvent) => void): void
126
+ /**
127
+ * Register the connection-close callback. Must be called before
128
+ * [`Self::start`].
129
+ */
130
+ onConnectionClose(callback: (err: null | Error, connectionId: string) => void): void
121
131
  /**
122
132
  * Bind the loopback endpoint and start serving. Resolves with the bound
123
133
  * endpoint info once the socket is bound.
@@ -129,12 +139,32 @@ export declare class NotificationServer {
129
139
  /**
130
140
  * Broadcast an `action_needed` ask. `needed_json` is a JSON `ActionNeeded`.
131
141
  *
132
- * `repliable` should be `true` only in unattended/RPC mode.
142
+ * `repliable` should be `true` only when an SDK workflow-gate resolver is
143
+ * available.
133
144
  *
134
145
  * # Errors
135
146
  * Fails if not started or `needed_json` is invalid.
136
147
  */
137
148
  registerAsk(neededJson: string, repliable: boolean): void
149
+ /**
150
+ * Register a correlated workflow-gate ask. `workflow_json` must be an
151
+ * `action_needed` wire frame carrying a nonempty `workflowGateId`.
152
+ */
153
+ registerWorkflowGateAsk(workflowJson: string, repliable: boolean): void
154
+ /**
155
+ * Register an ask and return an opaque in-process capability. Pass it
156
+ * unchanged to [`Self::retire_if_unclaimed`]; do not construct, persist,
157
+ * inspect, or treat it as workflow-gate authority. A supplied
158
+ * `workflowGateId` is preserved.
159
+ */
160
+ registerArbitratedAsk(neededJson: string, repliable: boolean): PresentationLease
161
+ /**
162
+ * Atomically terminalize the exact presentation named by an opaque lease.
163
+ * The typed status proves whether it retired, was already terminal, was
164
+ * claimed, or became stale without exposing claims, receipts, registration
165
+ * state, or workflow-gate authority.
166
+ */
167
+ retireIfUnclaimed(lease: PresentationLease): RetireIfUnclaimedResult
138
168
  /**
139
169
  * Broadcast an ephemeral `action_needed` idle ping. `needed_json` is JSON
140
170
  * `ActionNeeded`.
@@ -153,6 +183,8 @@ export declare class NotificationServer {
153
183
  * Fails if not started or `frame_json` is not a valid `ServerMessage`.
154
184
  */
155
185
  pushFrame(frameJson: string): void
186
+ /** Send raw JSON to one connected v3 SDK client. */
187
+ sendTo(connectionId: string, json: string): void
156
188
  /**
157
189
  * Publish a replayable `session_ready` readiness signal. `ready_json` is a
158
190
  * JSON `SessionReady`. Unlike [`Self::push_frame`], this frame is buffered
@@ -165,11 +197,10 @@ export declare class NotificationServer {
165
197
  */
166
198
  pushSessionReady(readyJson: string): void
167
199
  /**
168
- * Resolve an action locally (the CLI/TUI answered). `answer_json` is an
169
- * optional JSON `ReplyAnswer`.
170
- *
171
- * # Errors
172
- * Fails if not started or `answer_json` is invalid.
200
+ * Resolve a legacy/non-arbitrated action locally (the CLI/TUI answered).
201
+ * Arbitrated presentations require their opaque exact lease to be passed to
202
+ * [`Self::retire_if_unclaimed`], so an id-only local resolution fails
203
+ * closed.
173
204
  */
174
205
  resolveLocal(id: string, answerJson?: string | undefined | null): void
175
206
  /**
@@ -207,7 +238,7 @@ export declare class NotificationServer {
207
238
  */
208
239
  reject(id: string, reason?: string | undefined | null): void
209
240
  /**
210
- * Update whether the unattended gate resolver is currently available.
241
+ * Update whether the SDK workflow-gate resolver is currently available.
211
242
  *
212
243
  * # Errors
213
244
  * Fails if not started.
@@ -217,6 +248,8 @@ export declare class NotificationServer {
217
248
  clientCount(): number
218
249
  /** Stop the server (idempotent) and remove the endpoint discovery file. */
219
250
  stop(): void
251
+ /** Stop the server and resolve only after all native socket owners exit. */
252
+ stopAndWait(): Promise<void>
220
253
  }
221
254
 
222
255
  /** Stable process reference. */
@@ -227,6 +260,8 @@ export declare class Process {
227
260
  static fromPath(path: string): Array<Process>
228
261
  /** Operating-system process identifier for this process reference. */
229
262
  get pid(): number
263
+ /** Kernel-derived identity evidence for this exact process incarnation. */
264
+ get incarnation(): string
230
265
  /** Parent process id for this process, when available. */
231
266
  get ppid(): number | null
232
267
  /** Launch arguments for this process. */
@@ -316,7 +351,7 @@ export declare class Shell {
316
351
  * `packages/natives/native/index.js` (which derives the name from
317
352
  * `package.json#version`).
318
353
  */
319
- export declare function __piNativesV0_10_2(): void
354
+ export declare function __piNativesV0_11_1(): void
320
355
 
321
356
  /**
322
357
  * Apply conservative pre-execution rewrites to a bash command.
@@ -1420,6 +1455,18 @@ export declare function parseKey(data: string, kittyProtocolActive: boolean): st
1420
1455
  */
1421
1456
  export declare function parseKittySequence(data: string): ParsedKittyResult | null
1422
1457
 
1458
+ /**
1459
+ * Opaque in-process presentation capability.
1460
+ *
1461
+ * Returned by [`NotificationServer::register_arbitrated_ask`]. Pass it
1462
+ * unchanged to [`NotificationServer::retire_if_unclaimed`]; do not construct,
1463
+ * persist, inspect, or treat it as workflow-gate authority.
1464
+ */
1465
+ export interface PresentationLease {
1466
+ actionId: string
1467
+ registrationEpoch: number
1468
+ }
1469
+
1423
1470
  /** Current state of a process reference. */
1424
1471
  export declare enum ProcessStatus {
1425
1472
  /** The referenced process is still running. */
@@ -1498,7 +1545,10 @@ export declare function readImageFromClipboard(): Promise<ClipboardImage | undef
1498
1545
 
1499
1546
  /** A client reply forwarded to the TypeScript host for gate resolution. */
1500
1547
  export interface ReplyEvent {
1501
- /** The action id being answered (the real broker `gate_id` for asks). */
1548
+ /**
1549
+ * The transient action/presentation id being answered. This is not the
1550
+ * durable workflow gate id.
1551
+ */
1502
1552
  id: string
1503
1553
  /** JSON-encoded `ReplyAnswer` (number, string, or `{selected,custom}`). */
1504
1554
  answerJson: string
@@ -1508,6 +1558,17 @@ export interface ReplyEvent {
1508
1558
  replyReceiptId: string
1509
1559
  }
1510
1560
 
1561
+ /** Public status of exact direct retirement. Claims and receipts remain native. */
1562
+ export interface RetireIfUnclaimedResult {
1563
+ status: 'retired' | 'already_terminal' | 'claimed' | 'stale'
1564
+ }
1565
+
1566
+ /** A raw v3 SDK frame paired with its actual WebSocket connection id. */
1567
+ export interface SdkFrameEvent {
1568
+ connectionId: string
1569
+ json: string
1570
+ }
1571
+
1511
1572
  /**
1512
1573
  * Search content for a pattern (one-shot, compiles pattern each time).
1513
1574
  * For repeated searches with the same pattern, use [`grep`] with file filters.
package/native/index.js CHANGED
@@ -27,7 +27,7 @@ export const PtySession = nativeBindings.PtySession;
27
27
  export const Shell = nativeBindings.Shell;
28
28
 
29
29
  // functions
30
- export const __piNativesV0_10_2 = nativeBindings.__piNativesV0_10_2;
30
+ export const __piNativesV0_11_1 = nativeBindings.__piNativesV0_11_1;
31
31
  export const applyBashFixups = nativeBindings.applyBashFixups;
32
32
  export const astEdit = nativeBindings.astEdit;
33
33
  export const astGrep = nativeBindings.astGrep;
@@ -47,6 +47,7 @@ export interface ResolveLoaderCandidatesInput {
47
47
  addonFilenames: string[];
48
48
  isCompiledBinary: boolean;
49
49
  stageFromNodeModules?: boolean;
50
+ isWorkspaceLoad?: boolean;
50
51
  optionalPackageNativeDirs?: string[];
51
52
  nativeDir: string;
52
53
  execDir: string;
@@ -56,4 +57,18 @@ export interface ResolveLoaderCandidatesInput {
56
57
 
57
58
  export function resolveLoaderCandidates(input: ResolveLoaderCandidatesInput): string[];
58
59
 
60
+ export interface LoadFromCandidatesInput<T> {
61
+ candidates: string[];
62
+ requireCandidate: (candidate: string) => T;
63
+ validateCandidate: (bindings: T, candidate: string) => void;
64
+ describeCandidate: (candidate: string) => string;
65
+ }
66
+
67
+ export interface LoadFromCandidatesResult<T> {
68
+ bindings: T | null;
69
+ errors: string[];
70
+ }
71
+
72
+ export function loadFromCandidates<T>(input: LoadFromCandidatesInput<T>): LoadFromCandidatesResult<T>;
73
+
59
74
  export function loadNative(): Record<string, unknown>;
@@ -146,6 +146,7 @@ export function shouldStageNodeModulesAddon({ platform, isCompiledBinary, native
146
146
  * addonFilenames: string[];
147
147
  * isCompiledBinary: boolean;
148
148
  * stageFromNodeModules?: boolean;
149
+ * isWorkspaceLoad?: boolean;
149
150
  * optionalPackageNativeDirs?: string[];
150
151
  * nativeDir: string;
151
152
  * execDir: string;
@@ -158,12 +159,14 @@ export function resolveLoaderCandidates({
158
159
  addonFilenames,
159
160
  isCompiledBinary,
160
161
  stageFromNodeModules = false,
162
+ isWorkspaceLoad = false,
161
163
  optionalPackageNativeDirs = [],
162
164
  nativeDir,
163
165
  execDir,
164
166
  versionedDir,
165
167
  userDataDir,
166
168
  }) {
169
+ const workspaceCandidates = addonFilenames.map(filename => path.join(nativeDir, filename));
167
170
  const optionalPackageCandidates = optionalPackageNativeDirs.flatMap(optionalNativeDir =>
168
171
  addonFilenames.map(filename => path.join(optionalNativeDir, filename)),
169
172
  );
@@ -171,7 +174,10 @@ export function resolveLoaderCandidates({
171
174
  path.join(nativeDir, filename),
172
175
  path.join(execDir, filename),
173
176
  ]);
174
- const baseReleaseCandidates = [...optionalPackageCandidates, ...legacyReleaseCandidates];
177
+ const legacyExecCandidates = addonFilenames.map(filename => path.join(execDir, filename));
178
+ const baseReleaseCandidates = isWorkspaceLoad
179
+ ? [...workspaceCandidates, ...optionalPackageCandidates, ...legacyExecCandidates]
180
+ : [...optionalPackageCandidates, ...legacyReleaseCandidates];
175
181
  const compiledCandidates = addonFilenames.flatMap(filename => [
176
182
  path.join(versionedDir, filename),
177
183
  path.join(userDataDir, filename),
@@ -188,6 +194,35 @@ export function resolveLoaderCandidates({
188
194
  return [...new Set(releaseCandidates)];
189
195
  }
190
196
 
197
+ /**
198
+ * Deterministically try candidate paths in order using injected operations.
199
+ * This leaves file loading and compatibility policy at the call site while
200
+ * making fallback behavior testable without a native addon on disk.
201
+ *
202
+ * @template T
203
+ * @param {{
204
+ * candidates: string[];
205
+ * requireCandidate: (candidate: string) => T;
206
+ * validateCandidate: (bindings: T, candidate: string) => void;
207
+ * describeCandidate: (candidate: string) => string;
208
+ * }} input
209
+ * @returns {{ bindings: T | null; errors: string[] }}
210
+ */
211
+ export function loadFromCandidates({ candidates, requireCandidate, validateCandidate, describeCandidate }) {
212
+ const errors = [];
213
+ for (const candidate of candidates) {
214
+ try {
215
+ const bindings = requireCandidate(candidate);
216
+ validateCandidate(bindings, candidate);
217
+ return { bindings, errors };
218
+ } catch (err) {
219
+ const message = err instanceof Error ? err.message : String(err);
220
+ errors.push(`${describeCandidate(candidate)}: ${message}`);
221
+ }
222
+ }
223
+ return { bindings: null, errors };
224
+ }
225
+
191
226
  // =========================================================================
192
227
  // Side-effectful loader. Everything below runs only when `loadNative()` is
193
228
  // called from `native/index.js` — tests that only import the pure helpers
@@ -342,12 +377,6 @@ function maybeStageNodeModulesAddon(ctx, errors) {
342
377
  }
343
378
 
344
379
  function validateLoadedBindings(ctx, bindings, candidate) {
345
- // In workspace dev (running out of `packages/natives/native/` rather than a
346
- // `node_modules` install or a compiled bundle) the local `.node` only gains
347
- // the renamed sentinel after `bun --cwd=packages/natives run build`. Skip
348
- // validation there so a stale post-pull dev tree boots while the rebuild
349
- // completes; install and compiled-binary paths still validate.
350
- if (ctx.isWorkspaceLoad) return;
351
380
  if (typeof bindings[ctx.versionSentinelExport] === "function") return;
352
381
  throw new Error(
353
382
  `Loaded ${candidate} but it does not expose the @gajae-code/natives@${ctx.packageVersion} ` +
@@ -405,6 +434,8 @@ function initLoaderContext(require_) {
405
434
  isCompiledBinary,
406
435
  nativeDir,
407
436
  });
437
+ const isWorkspaceLoad =
438
+ !isCompiledBinary && !nativeDir.includes("\\node_modules\\") && !nativeDir.includes("/node_modules/");
408
439
 
409
440
  const selectedVariant = resolveCpuVariant(getVariantOverride());
410
441
  const addonFilenames = getAddonFilenames({ tag: platformTag, arch: process.arch, variant: selectedVariant });
@@ -419,6 +450,7 @@ function initLoaderContext(require_) {
419
450
  addonFilenames,
420
451
  isCompiledBinary,
421
452
  stageFromNodeModules,
453
+ isWorkspaceLoad,
422
454
  optionalPackageNativeDirs,
423
455
  nativeDir,
424
456
  execDir,
@@ -434,8 +466,6 @@ function initLoaderContext(require_) {
434
466
  // turns the silent `<sym> is not a function` crash from a Windows
435
467
  // locked-file update into an actionable load-time error.
436
468
  const versionSentinelExport = `__piNativesV${packageVersion.replace(/[^A-Za-z0-9]/g, "_")}`;
437
- const isWorkspaceLoad =
438
- !isCompiledBinary && !nativeDir.includes("\\node_modules\\") && !nativeDir.includes("/node_modules/");
439
469
 
440
470
  return {
441
471
  platformTag,
@@ -450,7 +480,6 @@ function initLoaderContext(require_) {
450
480
  addonLabel,
451
481
  candidates,
452
482
  versionSentinelExport,
453
- isWorkspaceLoad,
454
483
  };
455
484
  }
456
485
 
@@ -464,16 +493,14 @@ export function loadNative() {
464
493
  const prepended = [embeddedCandidate, stagedCandidate].filter(c => typeof c === "string");
465
494
  const runtimeCandidates = prepended.length > 0 ? [...prepended, ...ctx.candidates] : ctx.candidates;
466
495
 
467
- for (const candidate of runtimeCandidates) {
468
- try {
469
- const bindings = require_(candidate);
470
- validateLoadedBindings(ctx, bindings, candidate);
471
- return bindings;
472
- } catch (err) {
473
- const message = err instanceof Error ? err.message : String(err);
474
- errors.push(`${candidate}: ${message}`);
475
- }
476
- }
496
+ const loaded = loadFromCandidates({
497
+ candidates: runtimeCandidates,
498
+ requireCandidate: candidate => require_(candidate),
499
+ validateCandidate: (bindings, candidate) => validateLoadedBindings(ctx, bindings, candidate),
500
+ describeCandidate: candidate => candidate,
501
+ });
502
+ if (loaded.bindings) return loaded.bindings;
503
+ errors.push(...loaded.errors);
477
504
 
478
505
  if (!SUPPORTED_PLATFORMS.includes(ctx.platformTag)) {
479
506
  throw new Error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-code/natives",
3
- "version": "0.10.2",
3
+ "version": "0.11.1",
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.10.2",
65
- "@gajae-code/natives-darwin-x64": "0.10.2",
66
- "@gajae-code/natives-linux-arm64": "0.10.2",
67
- "@gajae-code/natives-linux-x64": "0.10.2",
68
- "@gajae-code/natives-win32-x64": "0.10.2"
64
+ "@gajae-code/natives-darwin-arm64": "0.11.1",
65
+ "@gajae-code/natives-darwin-x64": "0.11.1",
66
+ "@gajae-code/natives-linux-arm64": "0.11.1",
67
+ "@gajae-code/natives-linux-x64": "0.11.1",
68
+ "@gajae-code/natives-win32-x64": "0.11.1"
69
69
  },
70
70
  "exports": {
71
71
  ".": {