@gajae-code/natives 0.10.1 → 0.11.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.
package/native/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class ComputerController {
12
12
  keypress(expectedEpoch: number | undefined | null, keys: Array<string>): void
13
13
  wait(expectedEpoch: number | undefined | null, ms: number): void
14
14
  }
15
+
15
16
  /**
16
17
  * Long-lived macOS appearance observer.
17
18
  *
@@ -117,6 +118,16 @@ export declare class NotificationServer {
117
118
  * config commands). Must be called before [`Self::start`].
118
119
  */
119
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
120
131
  /**
121
132
  * Bind the loopback endpoint and start serving. Resolves with the bound
122
133
  * endpoint info once the socket is bound.
@@ -128,12 +139,32 @@ export declare class NotificationServer {
128
139
  /**
129
140
  * Broadcast an `action_needed` ask. `needed_json` is a JSON `ActionNeeded`.
130
141
  *
131
- * `repliable` should be `true` only in unattended/RPC mode.
142
+ * `repliable` should be `true` only when an SDK workflow-gate resolver is
143
+ * available.
132
144
  *
133
145
  * # Errors
134
146
  * Fails if not started or `needed_json` is invalid.
135
147
  */
136
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
137
168
  /**
138
169
  * Broadcast an ephemeral `action_needed` idle ping. `needed_json` is JSON
139
170
  * `ActionNeeded`.
@@ -152,6 +183,8 @@ export declare class NotificationServer {
152
183
  * Fails if not started or `frame_json` is not a valid `ServerMessage`.
153
184
  */
154
185
  pushFrame(frameJson: string): void
186
+ /** Send raw JSON to one connected v3 SDK client. */
187
+ sendTo(connectionId: string, json: string): void
155
188
  /**
156
189
  * Publish a replayable `session_ready` readiness signal. `ready_json` is a
157
190
  * JSON `SessionReady`. Unlike [`Self::push_frame`], this frame is buffered
@@ -164,11 +197,10 @@ export declare class NotificationServer {
164
197
  */
165
198
  pushSessionReady(readyJson: string): void
166
199
  /**
167
- * Resolve an action locally (the CLI/TUI answered). `answer_json` is an
168
- * optional JSON `ReplyAnswer`.
169
- *
170
- * # Errors
171
- * 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.
172
204
  */
173
205
  resolveLocal(id: string, answerJson?: string | undefined | null): void
174
206
  /**
@@ -206,7 +238,7 @@ export declare class NotificationServer {
206
238
  */
207
239
  reject(id: string, reason?: string | undefined | null): void
208
240
  /**
209
- * Update whether the unattended gate resolver is currently available.
241
+ * Update whether the SDK workflow-gate resolver is currently available.
210
242
  *
211
243
  * # Errors
212
244
  * Fails if not started.
@@ -226,6 +258,8 @@ export declare class Process {
226
258
  static fromPath(path: string): Array<Process>
227
259
  /** Operating-system process identifier for this process reference. */
228
260
  get pid(): number
261
+ /** Kernel-derived identity evidence for this exact process incarnation. */
262
+ get incarnation(): string
229
263
  /** Parent process id for this process, when available. */
230
264
  get ppid(): number | null
231
265
  /** Launch arguments for this process. */
@@ -315,7 +349,7 @@ export declare class Shell {
315
349
  * `packages/natives/native/index.js` (which derives the name from
316
350
  * `package.json#version`).
317
351
  */
318
- export declare function __piNativesV0_10_1(): void
352
+ export declare function __piNativesV0_11_0(): void
319
353
 
320
354
  /**
321
355
  * Apply conservative pre-execution rewrites to a bash command.
@@ -1419,6 +1453,18 @@ export declare function parseKey(data: string, kittyProtocolActive: boolean): st
1419
1453
  */
1420
1454
  export declare function parseKittySequence(data: string): ParsedKittyResult | null
1421
1455
 
1456
+ /**
1457
+ * Opaque in-process presentation capability.
1458
+ *
1459
+ * Returned by [`NotificationServer::register_arbitrated_ask`]. Pass it
1460
+ * unchanged to [`NotificationServer::retire_if_unclaimed`]; do not construct,
1461
+ * persist, inspect, or treat it as workflow-gate authority.
1462
+ */
1463
+ export interface PresentationLease {
1464
+ actionId: string
1465
+ registrationEpoch: number
1466
+ }
1467
+
1422
1468
  /** Current state of a process reference. */
1423
1469
  export declare enum ProcessStatus {
1424
1470
  /** The referenced process is still running. */
@@ -1497,7 +1543,10 @@ export declare function readImageFromClipboard(): Promise<ClipboardImage | undef
1497
1543
 
1498
1544
  /** A client reply forwarded to the TypeScript host for gate resolution. */
1499
1545
  export interface ReplyEvent {
1500
- /** The action id being answered (the real broker `gate_id` for asks). */
1546
+ /**
1547
+ * The transient action/presentation id being answered. This is not the
1548
+ * durable workflow gate id.
1549
+ */
1501
1550
  id: string
1502
1551
  /** JSON-encoded `ReplyAnswer` (number, string, or `{selected,custom}`). */
1503
1552
  answerJson: string
@@ -1507,6 +1556,17 @@ export interface ReplyEvent {
1507
1556
  replyReceiptId: string
1508
1557
  }
1509
1558
 
1559
+ /** Public status of exact direct retirement. Claims and receipts remain native. */
1560
+ export interface RetireIfUnclaimedResult {
1561
+ status: 'retired' | 'already_terminal' | 'claimed' | 'stale'
1562
+ }
1563
+
1564
+ /** A raw v3 SDK frame paired with its actual WebSocket connection id. */
1565
+ export interface SdkFrameEvent {
1566
+ connectionId: string
1567
+ json: string
1568
+ }
1569
+
1510
1570
  /**
1511
1571
  * Search content for a pattern (one-shot, compiles pattern each time).
1512
1572
  * 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_1 = nativeBindings.__piNativesV0_10_1;
30
+ export const __piNativesV0_11_0 = nativeBindings.__piNativesV0_11_0;
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.1",
3
+ "version": "0.11.0",
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.1",
65
- "@gajae-code/natives-darwin-x64": "0.10.1",
66
- "@gajae-code/natives-linux-arm64": "0.10.1",
67
- "@gajae-code/natives-linux-x64": "0.10.1",
68
- "@gajae-code/natives-win32-x64": "0.10.1"
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"
69
69
  },
70
70
  "exports": {
71
71
  ".": {