@msvesper/vesper-link 0.1.5 → 0.1.6

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.
@@ -39,6 +39,7 @@ export interface InstallerDependencies {
39
39
  requiredCompanion?: ManagedRuntime['companion'];
40
40
  bootstrapAuthKey?: string;
41
41
  failIfLoginRequired?: boolean;
42
+ replaceExistingTransport?: boolean;
42
43
  }): Promise<RemoteTransport>;
43
44
  getHealth: typeof getHealth;
44
45
  getCapabilities: typeof getCapabilities;
package/dist/installer.js CHANGED
@@ -85,6 +85,7 @@ export async function installVesperLink(options) {
85
85
  requiredCompanion: runtime.companion,
86
86
  bootstrapAuthKey,
87
87
  failIfLoginRequired: options.requireExistingIdentity,
88
+ replaceExistingTransport: options.requireExistingIdentity,
88
89
  onNeedsLogin: (url) => {
89
90
  options.onNeedsLogin?.(url);
90
91
  },
@@ -21,8 +21,13 @@ export interface AcquireTransportOptions {
21
21
  spawnChild?: (executable: string, args: string[], env: NodeJS.ProcessEnv) => ChildProcessWithoutNullStreams;
22
22
  onNeedsLogin?: (url: string) => void;
23
23
  failIfLoginRequired?: boolean;
24
+ replaceExistingTransport?: boolean;
24
25
  bootstrapAuthKey?: string;
25
26
  startTimeoutMs?: number;
27
+ handoffTimeoutMs?: number;
28
+ terminateProcess?: (pid: number) => void;
29
+ isProcessAlive?: (pid: number) => boolean;
30
+ restartPriorTransport?: (executable: string, args: string[], env: NodeJS.ProcessEnv) => void;
26
31
  }
27
32
  export declare function consumeBootstrapAuthKey(explicitAuthKey: string | undefined, environment?: NodeJS.ProcessEnv): string | undefined;
28
33
  export declare function acquireRemoteTransport(options: AcquireTransportOptions): Promise<RemoteTransport>;
package/dist/transport.js CHANGED
@@ -2,6 +2,7 @@ import * as fs from 'node:fs/promises';
2
2
  import { spawn } from 'node:child_process';
3
3
  import { isIPv4 } from 'node:net';
4
4
  import * as http from 'node:http';
5
+ import * as path from 'node:path';
5
6
  import { Readable } from 'node:stream';
6
7
  import { assertCompanionProtocol, VESPER_NET_PROTOCOL_VERSION, resolveCompanion } from './companion.js';
7
8
  const START_TIMEOUT_MS = 10 * 60_000;
@@ -9,7 +10,9 @@ const ATTACH_TIMEOUT_MS = 2_000;
9
10
  const POLL_INTERVAL_MS = 100;
10
11
  const MAX_CONTROL_BYTES = 64 * 1024;
11
12
  const LOCAL_SECRET_HEADER = 'x-vesper-transport-secret';
13
+ const EXPECTED_OWNER_PID_HEADER = 'x-vesper-expected-owner-pid';
12
14
  const KEEPALIVE_MS = 10_000;
15
+ const HANDOFF_TIMEOUT_MS = 10_000;
13
16
  const PROXY_ENVIRONMENT_KEYS = new Set([
14
17
  'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
15
18
  ]);
@@ -17,6 +20,8 @@ const AUTH_KEY_ENVIRONMENT_KEYS = new Set([
17
20
  'vesper_ts_authkey', 'ts_authkey', 'ts_auth_key',
18
21
  ]);
19
22
  const AUTH_KEY_PATTERN = /^tskey-auth-[A-Za-z0-9_-]{16,512}$/;
23
+ const TRANSPORT_SECRET_ENVIRONMENT_KEY = 'VESPER_TRANSPORT_SECRET';
24
+ const TRANSPORT_SECRET_PATTERN = /^[0-9a-fA-F]{64}$/;
20
25
  function directLoopbackFetch(input, init = {}) {
21
26
  const inputRequest = input instanceof Request ? input : undefined;
22
27
  const url = new URL(inputRequest?.url ?? input.toString());
@@ -140,7 +145,16 @@ async function readRendezvous(filePath) {
140
145
  throw error;
141
146
  }
142
147
  }
143
- async function attach(paths, hostUrl, fetchImpl, requiredExecutablePath) {
148
+ function validateLease(candidate, hostUrl) {
149
+ if (candidate.protocolVersion !== VESPER_NET_PROTOCOL_VERSION || candidate.hostUrl !== hostUrl
150
+ || typeof candidate.tailnetName !== 'string' || !candidate.tailnetName
151
+ || typeof candidate.nodeName !== 'string' || !candidate.nodeName
152
+ || typeof candidate.ipv4 !== 'string' || !isIPv4(candidate.ipv4)
153
+ || typeof candidate.executablePath !== 'string' || !candidate.executablePath) {
154
+ throw new Error('Running vesper-net transport targets a different host or protocol');
155
+ }
156
+ }
157
+ async function inspectTransport(paths, hostUrl, fetchImpl, renewLease) {
144
158
  let rendezvous;
145
159
  try {
146
160
  rendezvous = await readRendezvous(paths.transportRendezvousPath);
@@ -154,54 +168,49 @@ async function attach(paths, hostUrl, fetchImpl, requiredExecutablePath) {
154
168
  if (!rendezvous || !isAlive(rendezvous.pid)) {
155
169
  return null;
156
170
  }
157
- const requestLease = async (inspectOnly) => {
158
- try {
159
- const operation = inspectOnly ? 'identity' : 'lease';
160
- return await fetchImpl(`${rendezvous.endpoint}/__vesper_transport/${operation}`, {
161
- headers: { [LOCAL_SECRET_HEADER]: rendezvous.secret },
162
- signal: AbortSignal.timeout(ATTACH_TIMEOUT_MS),
163
- });
164
- }
165
- catch {
166
- return null;
167
- }
168
- };
169
- const readLease = async (inspectOnly) => {
170
- const response = await requestLease(inspectOnly);
171
- if (!response?.ok) {
172
- return null;
173
- }
174
- return await response.json();
175
- };
176
- let lease = await readLease(requiredExecutablePath !== undefined);
177
- if (!lease) {
171
+ let response;
172
+ try {
173
+ response = await fetchImpl(`${rendezvous.endpoint}/__vesper_transport/${renewLease ? 'lease' : 'identity'}`, {
174
+ headers: { [LOCAL_SECRET_HEADER]: rendezvous.secret },
175
+ signal: AbortSignal.timeout(ATTACH_TIMEOUT_MS),
176
+ });
177
+ }
178
+ catch {
178
179
  return null;
179
180
  }
180
- function validateLease(candidate) {
181
- if (candidate.protocolVersion !== VESPER_NET_PROTOCOL_VERSION || candidate.hostUrl !== hostUrl
182
- || typeof candidate.tailnetName !== 'string' || !candidate.tailnetName
183
- || typeof candidate.nodeName !== 'string' || !candidate.nodeName
184
- || typeof candidate.ipv4 !== 'string' || !isIPv4(candidate.ipv4)
185
- || typeof candidate.executablePath !== 'string' || !candidate.executablePath) {
186
- throw new Error('Running vesper-net transport targets a different host or protocol');
187
- }
181
+ if (!response.ok) {
182
+ return null;
183
+ }
184
+ const lease = await response.json();
185
+ validateLease(lease, hostUrl);
186
+ return { rendezvous, lease };
187
+ }
188
+ function sameExecutable(left, right) {
189
+ return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
190
+ }
191
+ async function canonicalExecutable(executable) {
192
+ return fs.realpath(executable).catch(() => path.resolve(executable));
193
+ }
194
+ async function attach(paths, hostUrl, fetchImpl, requiredExecutablePath) {
195
+ let inspected = await inspectTransport(paths, hostUrl, fetchImpl, requiredExecutablePath === undefined);
196
+ if (!inspected) {
197
+ return null;
188
198
  }
189
- validateLease(lease);
190
199
  if (requiredExecutablePath) {
191
- const reportedExecutablePath = lease.executablePath;
192
- const actualExecutablePath = await fs.realpath(reportedExecutablePath).catch(() => reportedExecutablePath);
193
- const sameExecutable = process.platform === 'win32'
194
- ? actualExecutablePath.toLowerCase() === requiredExecutablePath.toLowerCase()
195
- : actualExecutablePath === requiredExecutablePath;
196
- if (!sameExecutable) {
200
+ const actualExecutablePath = await canonicalExecutable(inspected.lease.executablePath);
201
+ if (!sameExecutable(actualExecutablePath, requiredExecutablePath)) {
202
+ return null;
203
+ }
204
+ inspected = await inspectTransport(paths, hostUrl, fetchImpl, true);
205
+ if (!inspected) {
197
206
  return null;
198
207
  }
199
- lease = await readLease(false);
200
- if (!lease) {
208
+ const renewedExecutablePath = await canonicalExecutable(inspected.lease.executablePath);
209
+ if (!sameExecutable(renewedExecutablePath, requiredExecutablePath)) {
201
210
  return null;
202
211
  }
203
- validateLease(lease);
204
212
  }
213
+ const { rendezvous, lease } = inspected;
205
214
  const transportFetch = async (input, init) => {
206
215
  const remote = input instanceof Request ? new URL(input.url) : new URL(input.toString());
207
216
  if (remote.origin !== new URL(hostUrl).origin) {
@@ -238,6 +247,122 @@ async function attach(paths, hostUrl, fetchImpl, requiredExecutablePath) {
238
247
  },
239
248
  };
240
249
  }
250
+ function isManagedRuntimeExecutable(paths, executable) {
251
+ const relative = path.relative(path.resolve(paths.runtimeDir), path.resolve(executable));
252
+ const executableName = path.basename(executable).toLowerCase();
253
+ return relative !== '' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)
254
+ && (executableName === 'vesper-net' || executableName === 'vesper-net.exe')
255
+ && relative.split(path.sep).includes('node_modules');
256
+ }
257
+ async function prepareTransportHandoff(options, fetchImpl, requiredExecutablePath) {
258
+ const inspected = await inspectTransport(options.paths, options.hostUrl, fetchImpl, false);
259
+ if (!inspected) {
260
+ return undefined;
261
+ }
262
+ const oldExecutablePath = await canonicalExecutable(inspected.lease.executablePath);
263
+ if (sameExecutable(oldExecutablePath, requiredExecutablePath)) {
264
+ return undefined;
265
+ }
266
+ if (!isManagedRuntimeExecutable(options.paths, oldExecutablePath)) {
267
+ throw new Error(`Refusing to replace vesper-net transport outside the managed runtime: ${oldExecutablePath}`);
268
+ }
269
+ return { inspected, oldExecutablePath };
270
+ }
271
+ async function stopTransportForHandoff(options, fetchImpl, handoff) {
272
+ const { inspected, oldExecutablePath } = handoff;
273
+ const oldPid = inspected.rendezvous.pid;
274
+ const processIsAlive = options.isProcessAlive ?? isAlive;
275
+ if (inspected.lease.supportsShutdown === true) {
276
+ let response;
277
+ try {
278
+ response = await fetchImpl(`${inspected.rendezvous.endpoint}/__vesper_transport/shutdown`, {
279
+ method: 'POST', headers: {
280
+ [LOCAL_SECRET_HEADER]: inspected.rendezvous.secret,
281
+ [EXPECTED_OWNER_PID_HEADER]: String(oldPid),
282
+ },
283
+ signal: AbortSignal.timeout(ATTACH_TIMEOUT_MS),
284
+ });
285
+ }
286
+ catch {
287
+ throw new Error(`Failed to gracefully stop prior vesper-net transport (pid=${oldPid})`);
288
+ }
289
+ if (response.status === 409) {
290
+ // The inspected owner has already yielded the inherited listener.
291
+ return;
292
+ }
293
+ if (!response.ok) {
294
+ throw new Error(`Failed to gracefully stop prior vesper-net transport (pid=${oldPid})`);
295
+ }
296
+ if (await waitForProcessExit(oldPid, options.handoffTimeoutMs ?? HANDOFF_TIMEOUT_MS, processIsAlive)) {
297
+ return;
298
+ }
299
+ throw new Error(`Timed out stopping prior vesper-net transport (pid=${oldPid})`);
300
+ }
301
+ const revalidated = await inspectTransport(options.paths, options.hostUrl, fetchImpl, false);
302
+ if (!revalidated) {
303
+ throw new Error(`Timed out stopping prior vesper-net transport (pid=${oldPid})`);
304
+ }
305
+ const revalidatedExecutablePath = await canonicalExecutable(revalidated.lease.executablePath);
306
+ if (revalidated.rendezvous.pid !== oldPid
307
+ || revalidated.rendezvous.endpoint !== inspected.rendezvous.endpoint
308
+ || revalidated.rendezvous.secret !== inspected.rendezvous.secret
309
+ || !sameExecutable(revalidatedExecutablePath, oldExecutablePath)
310
+ || !isManagedRuntimeExecutable(options.paths, revalidatedExecutablePath)) {
311
+ throw new Error('vesper-net transport changed ownership during upgrade handoff');
312
+ }
313
+ (options.terminateProcess ?? ((pid) => process.kill(pid, 'SIGTERM')))(oldPid);
314
+ if (!await waitForProcessExit(oldPid, options.handoffTimeoutMs ?? HANDOFF_TIMEOUT_MS, processIsAlive)) {
315
+ throw new Error(`Timed out stopping prior vesper-net transport (pid=${oldPid})`);
316
+ }
317
+ }
318
+ async function evictUnexpectedTransportOwner(options, fetchImpl, requiredExecutablePath) {
319
+ const inspected = await inspectTransport(options.paths, options.hostUrl, fetchImpl, false);
320
+ if (!inspected) {
321
+ return;
322
+ }
323
+ const executablePath = await canonicalExecutable(inspected.lease.executablePath);
324
+ if (sameExecutable(executablePath, requiredExecutablePath)) {
325
+ return;
326
+ }
327
+ if (!isManagedRuntimeExecutable(options.paths, executablePath)) {
328
+ throw new Error(`Refusing to replace vesper-net transport outside the managed runtime: ${executablePath}`);
329
+ }
330
+ await stopTransportForHandoff(options, fetchImpl, { inspected, oldExecutablePath: executablePath });
331
+ }
332
+ async function waitForProcessExit(pid, timeoutMs, processIsAlive) {
333
+ const deadline = Date.now() + timeoutMs;
334
+ while (Date.now() < deadline) {
335
+ if (!processIsAlive(pid)) {
336
+ return true;
337
+ }
338
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
339
+ }
340
+ return !processIsAlive(pid);
341
+ }
342
+ function defaultRestartPriorTransport(executable, args, env) {
343
+ const child = spawn(executable, args, { env, detached: true, windowsHide: true, stdio: 'ignore' });
344
+ child.unref();
345
+ }
346
+ async function restartPriorTransport(options, handoff, args, error) {
347
+ if (handoff) {
348
+ try {
349
+ const oldPid = handoff.inspected.rendezvous.pid;
350
+ const processIsAlive = options.isProcessAlive ?? isAlive;
351
+ if (processIsAlive(oldPid) && !await waitForProcessExit(oldPid, options.handoffTimeoutMs ?? HANDOFF_TIMEOUT_MS, processIsAlive)) {
352
+ throw new Error(`Prior vesper-net transport is still exiting (pid=${oldPid})`);
353
+ }
354
+ (options.restartPriorTransport ?? defaultRestartPriorTransport)(handoff.oldExecutablePath, args, companionEnvironment(process.env));
355
+ }
356
+ catch (recoveryError) {
357
+ throw new AggregateError([error, recoveryError], 'vesper-net candidate failed and prior transport restart failed');
358
+ }
359
+ }
360
+ throw error;
361
+ }
362
+ async function failAfterHandoff(options, handoff, baseArgs, child, error) {
363
+ child.kill('SIGTERM');
364
+ return restartPriorTransport(options, handoff, baseArgs, error);
365
+ }
241
366
  function defaultSpawn(executable, args, env) {
242
367
  return spawn(executable, args, {
243
368
  env, detached: true, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
@@ -246,7 +371,8 @@ function defaultSpawn(executable, args, env) {
246
371
  function companionEnvironment(environment, bootstrapAuthKey) {
247
372
  const filtered = Object.fromEntries(Object.entries(environment).filter(([key]) => {
248
373
  const normalized = key.toLowerCase();
249
- return !PROXY_ENVIRONMENT_KEYS.has(normalized) && !AUTH_KEY_ENVIRONMENT_KEYS.has(normalized);
374
+ return !PROXY_ENVIRONMENT_KEYS.has(normalized) && !AUTH_KEY_ENVIRONMENT_KEYS.has(normalized)
375
+ && normalized !== TRANSPORT_SECRET_ENVIRONMENT_KEY.toLowerCase();
250
376
  }));
251
377
  if (bootstrapAuthKey !== undefined) {
252
378
  filtered.VESPER_TS_AUTHKEY = bootstrapAuthKey;
@@ -292,7 +418,7 @@ export async function acquireRemoteTransport(options) {
292
418
  await (options.probeCompanion ?? assertCompanionProtocol)(companion, protocolProbeEnvironment(process.env));
293
419
  }
294
420
  const requiredExecutablePath = companion
295
- ? await fs.realpath(companion.executable).catch(() => companion.executable)
421
+ ? await canonicalExecutable(companion.executable)
296
422
  : undefined;
297
423
  const existing = await attach(options.paths, options.hostUrl, fetchImpl, requiredExecutablePath);
298
424
  if (existing) {
@@ -304,16 +430,30 @@ export async function acquireRemoteTransport(options) {
304
430
  if (!options.requiredCompanion) {
305
431
  await (options.probeCompanion ?? assertCompanionProtocol)(companion, protocolProbeEnvironment(process.env));
306
432
  }
307
- const deadline = Date.now() + (options.startTimeoutMs ?? START_TIMEOUT_MS);
308
- const args = [
433
+ const handoff = options.replaceExistingTransport && requiredExecutablePath
434
+ ? await prepareTransportHandoff(options, fetchImpl, requiredExecutablePath)
435
+ : undefined;
436
+ if (handoff && !TRANSPORT_SECRET_PATTERN.test(handoff.inspected.rendezvous.secret)) {
437
+ throw new Error('Running vesper-net transport has a malformed handoff secret');
438
+ }
439
+ const baseArgs = [
309
440
  '--mode', 'transport', '--state-dir', options.paths.tsnetDir, '--hostname', 'vesper-link',
310
441
  '--host-url', options.hostUrl, '--rendezvous', options.paths.transportRendezvousPath,
311
442
  '--lock', options.paths.transportLockPath, '--idle-timeout', '30s',
312
443
  ];
313
- const child = (options.spawnChild ?? defaultSpawn)(companion.executable, args, companionEnvironment(process.env, bootstrapAuthKey));
444
+ const candidateArgs = handoff
445
+ ? [...baseArgs, '--transport-listen', new URL(handoff.inspected.rendezvous.endpoint).host]
446
+ : baseArgs;
447
+ const candidateEnvironment = companionEnvironment(process.env, bootstrapAuthKey);
448
+ if (handoff) {
449
+ candidateEnvironment[TRANSPORT_SECRET_ENVIRONMENT_KEY] = handoff.inspected.rendezvous.secret;
450
+ }
451
+ const child = (options.spawnChild ?? defaultSpawn)(companion.executable, candidateArgs, candidateEnvironment);
314
452
  child.stdin.end();
315
453
  let controlBuffer = '';
316
454
  let fatalMessage;
455
+ let waitingForOwner = false;
456
+ let ownerAcquired = false;
317
457
  child.once('error', (error) => { fatalMessage = `spawn failed: ${error.message}`; });
318
458
  child.once('exit', (code, signal) => {
319
459
  fatalMessage ??= `exited before ready (code=${code ?? 'none'}, signal=${signal ?? 'none'})`;
@@ -339,6 +479,12 @@ export async function acquireRemoteTransport(options) {
339
479
  fatalMessage = 'vesper-net protocol mismatch';
340
480
  child.kill('SIGTERM');
341
481
  }
482
+ else if (event.type === 'waiting_for_owner') {
483
+ waitingForOwner = true;
484
+ }
485
+ else if (event.type === 'owner_acquired') {
486
+ ownerAcquired = true;
487
+ }
342
488
  else if (event.type === 'needs_login' && typeof event.loginUrl === 'string') {
343
489
  if (bootstrapAuthKey !== undefined) {
344
490
  fatalMessage = 'automatic enrollment key was not accepted; copy a fresh Memory Link command';
@@ -373,14 +519,43 @@ export async function acquireRemoteTransport(options) {
373
519
  });
374
520
  // Never persist helper stderr. Drain it to prevent a blocked pipe.
375
521
  child.stderr.resume();
522
+ const deadline = Date.now() + (options.startTimeoutMs ?? START_TIMEOUT_MS);
523
+ let handoffNeedsRecovery = false;
524
+ if (handoff) {
525
+ // Wait until the candidate confirms it has contended on the owner lock.
526
+ // This closes the stop/start gap where an old runtime could otherwise win.
527
+ while (!waitingForOwner && !ownerAcquired && !fatalMessage && Date.now() < deadline) {
528
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
529
+ }
530
+ if (fatalMessage) {
531
+ child.kill('SIGTERM');
532
+ return restartPriorTransport(options, undefined, baseArgs, new Error(`vesper-net transport failed: ${fatalMessage}`));
533
+ }
534
+ if (!waitingForOwner && !ownerAcquired) {
535
+ child.kill('SIGTERM');
536
+ return restartPriorTransport(options, undefined, baseArgs, new Error('Timed out waiting for vesper-net candidate to queue for transport ownership'));
537
+ }
538
+ if (waitingForOwner) {
539
+ try {
540
+ await stopTransportForHandoff(options, fetchImpl, handoff);
541
+ }
542
+ catch (error) {
543
+ child.kill('SIGTERM');
544
+ throw error;
545
+ }
546
+ }
547
+ handoffNeedsRecovery = true;
548
+ }
376
549
  while (Date.now() < deadline) {
377
550
  let attached;
378
551
  try {
552
+ if (handoff && requiredExecutablePath) {
553
+ await evictUnexpectedTransportOwner(options, fetchImpl, requiredExecutablePath);
554
+ }
379
555
  attached = await attach(options.paths, options.hostUrl, fetchImpl, requiredExecutablePath);
380
556
  }
381
557
  catch (error) {
382
- child.kill('SIGTERM');
383
- throw error;
558
+ return failAfterHandoff(options, handoffNeedsRecovery ? handoff : undefined, baseArgs, child, error);
384
559
  }
385
560
  if (attached) {
386
561
  if (attached.ownerPid === child.pid) {
@@ -394,11 +569,11 @@ export async function acquireRemoteTransport(options) {
394
569
  return attached.transport;
395
570
  }
396
571
  if (fatalMessage) {
397
- child.kill('SIGTERM');
398
- throw new Error(`vesper-net transport failed: ${fatalMessage}`);
572
+ const error = new Error(`vesper-net transport failed: ${fatalMessage}`);
573
+ return failAfterHandoff(options, handoffNeedsRecovery ? handoff : undefined, baseArgs, child, error);
399
574
  }
400
575
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
401
576
  }
402
- child.kill('SIGTERM');
403
- throw new Error('Timed out waiting for vesper-net transport');
577
+ const error = new Error('Timed out waiting for vesper-net transport');
578
+ return failAfterHandoff(options, handoffNeedsRecovery ? handoff : undefined, baseArgs, child, error);
404
579
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VESPER_LINK_VERSION = "0.1.5";
1
+ export declare const VESPER_LINK_VERSION = "0.1.6";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VESPER_LINK_VERSION = '0.1.5';
1
+ export const VESPER_LINK_VERSION = '0.1.6';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msvesper/vesper-link",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Connect agent sessions and tools to Vesper Memory",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,8 +28,8 @@
28
28
  "access": "public"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@msvesper/vesper-net-linux-x64": "0.1.5",
32
- "@msvesper/vesper-net-win32-x64": "0.1.5"
31
+ "@msvesper/vesper-net-linux-x64": "0.1.6",
32
+ "@msvesper/vesper-net-win32-x64": "0.1.6"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.15.0",