@cortexkit/opencode-openai-auth 1.4.0 → 1.5.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/dist/bin.js +8 -8
- package/dist/{codex-request-hw0XcldE.d.ts → codex-request-BjGT9yWg.d.ts} +35 -27
- package/dist/index-2p3gcjsa.js +69 -0
- package/dist/{index-1tdnrt68.js → index-2rw81dtm.js} +1 -1
- package/dist/index-2yszc75z.js +2 -0
- package/dist/{index-ag04nyv7.js → index-3y453wg4.js} +2 -2
- package/dist/{index-t5m0dhmq.js → index-4w0nsq5s.js} +1 -1
- package/dist/index-6fdmqk6p.js +90 -0
- package/dist/{index-d98f1xdp.js → index-7vkn2e46.js} +1 -1
- package/dist/{index-rsvnabbe.js → index-djnfq652.js} +2 -2
- package/dist/{index-yhw3hnsh.js → index-e416r885.js} +2 -2
- package/dist/{index-jcyrm58f.js → index-fztcacjr.js} +1 -1
- package/dist/{index-1eq2s1cp.js → index-gmmakhbt.js} +1 -1
- package/dist/{index-s88n2bjn.js → index-krcqvyqf.js} +1 -1
- package/dist/{index-wgwze391.js → index-mhfja8yr.js} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{model-DphbMQce.d.ts → model-BMq5SCUB.d.ts} +1 -1
- package/dist/{port-file-Cx-wwe5Q.d.ts → port-file-CjMFUWcX.d.ts} +1 -1
- package/dist/{protocol-CDqdjh74.d.ts → protocol-BC3ndToM.d.ts} +1 -1
- package/dist/rpc/port-file.d.ts +1 -1
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +1 -1
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +3 -3
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/{sidebar-state-pTd9gg9W.d.ts → sidebar-state-DfYyqiOH.d.ts} +4 -4
- package/dist/sidebar-state.d.ts +1 -1
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-ed4az5gg.js +1 -0
- package/dist/{token-0z5g59p8.js → token-ycw7dyev.js} +1 -1
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +2 -2
- package/dist/v2/server.d.ts +2 -2
- package/dist/v2/server.js +2 -2
- package/package.json +3 -3
- package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +17 -2
- package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +72 -0
- package/src/tui-compiled/raw/shared/375d9cca2def037d-private-files.js +49 -3
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +95 -13
- package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +27 -0
- package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +1 -1
- package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +17 -2
- package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +72 -0
- package/src/tui-compiled/runtime/shared/375d9cca2def037d-private-files.js +49 -3
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +95 -13
- package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +27 -0
- package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +1 -1
- package/dist/index-5230x6s8.js +0 -2
- package/dist/index-brbqqer5.js +0 -90
- package/dist/index-vy729a0b.js +0 -69
- package/dist/token-util-jf9g6stk.js +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// the same locks as any other writer of those files.
|
|
5
5
|
import { formatQuota, isQuotaMap, projectQuota, quotaWindowName, } from "./d5a8f1061ee2f114-index.js";
|
|
6
6
|
import { DEFAULT_FORMER_MAIN_ID, orderForPlacement, resolveRoutingMode, } from "./3e7d45a77c6ad4d5-index.js";
|
|
7
|
-
import { projectFailure, } from "./94820e324f2b4c09-seam.js";
|
|
7
|
+
import { CommandError, projectFailure, } from "./94820e324f2b4c09-seam.js";
|
|
8
8
|
/** `disabledReason` recorded when the user disables an account from the menu. */
|
|
9
9
|
export const MENU_DISABLED_REASON = 'disabled from the command menu';
|
|
10
10
|
function isRecord(value) {
|
|
@@ -65,13 +65,16 @@ function addedText(name, result) {
|
|
|
65
65
|
function failureMessage(error) {
|
|
66
66
|
return projectFailure(error).text;
|
|
67
67
|
}
|
|
68
|
-
async function snapshot(
|
|
68
|
+
async function snapshot(options) {
|
|
69
|
+
const { store } = options;
|
|
69
70
|
const load = await store.read();
|
|
70
71
|
const read = await store.readSettings();
|
|
72
|
+
const vault = await options.routing?.vaultOrder?.();
|
|
71
73
|
return {
|
|
72
74
|
load,
|
|
73
75
|
rows: distinctRows(load),
|
|
74
76
|
settings: read.status === 'error' ? {} : read.settings,
|
|
77
|
+
...(vault ? { vault } : {}),
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
function accountView(row, options) {
|
|
@@ -323,6 +326,73 @@ function quotaSection(options, snap) {
|
|
|
323
326
|
},
|
|
324
327
|
};
|
|
325
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Sets the vault order. The roster's own refusal of the list (`invalid-order`:
|
|
331
|
+
* an account left out, which it counts without naming) is shown with its own
|
|
332
|
+
* message; any other failure is projected as usual. The kind is read from
|
|
333
|
+
* the error object so the menu does not import the vault client.
|
|
334
|
+
*/
|
|
335
|
+
async function reorderVault(vault, order) {
|
|
336
|
+
try {
|
|
337
|
+
return await vault.reorder(order);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error instanceof Error &&
|
|
341
|
+
error.name === 'ClaustrumConsumerError' &&
|
|
342
|
+
error.kind === 'invalid-order')
|
|
343
|
+
throw new CommandError('vault-invalid-order', error.message, {
|
|
344
|
+
cause: error,
|
|
345
|
+
});
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function invalidVaultOrder(message) {
|
|
350
|
+
return new CommandError('vault-invalid-order', message);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* How the Routing section names vault accounts: by label, since route ids
|
|
354
|
+
* are long and opaque. A typed entry may be a label or a route id, and must
|
|
355
|
+
* name exactly one account. Refusals quote only the entry the user typed.
|
|
356
|
+
*/
|
|
357
|
+
function vaultNames(vault) {
|
|
358
|
+
const rows = vault.rows.map((row) => ({
|
|
359
|
+
id: row.id,
|
|
360
|
+
label: row.label.trim() || row.id,
|
|
361
|
+
}));
|
|
362
|
+
const matches = (entry) => rows.filter((row) => row.id === entry || row.label === entry);
|
|
363
|
+
return {
|
|
364
|
+
ids: rows.map((row) => row.id),
|
|
365
|
+
nameOf: (id) => rows.find((row) => row.id === id)?.label ?? id,
|
|
366
|
+
/**
|
|
367
|
+
* What the order input starts with for an account: its label when
|
|
368
|
+
* typing that label back names this account alone, else its route id
|
|
369
|
+
* (two accounts sharing a label, or a label with a comma or line break
|
|
370
|
+
* in it, either of which would split it into two entries).
|
|
371
|
+
*/
|
|
372
|
+
entryOf: (id) => {
|
|
373
|
+
const row = rows.find((entry) => entry.id === id);
|
|
374
|
+
return row && !/[,\n]/.test(row.label) && matches(row.label).length === 1
|
|
375
|
+
? row.label
|
|
376
|
+
: id;
|
|
377
|
+
},
|
|
378
|
+
/** The route ids the typed entries name, in order; refuses before any write. */
|
|
379
|
+
resolve: (entries) => {
|
|
380
|
+
const ids = [];
|
|
381
|
+
for (const entry of entries) {
|
|
382
|
+
const found = matches(entry);
|
|
383
|
+
if (found.length === 0)
|
|
384
|
+
throw invalidVaultOrder(`"${entry}" is not a vault account.`);
|
|
385
|
+
if (found.length > 1)
|
|
386
|
+
throw invalidVaultOrder(`"${entry}" matches more than one vault account; enter those accounts by route id.`);
|
|
387
|
+
const id = found[0].id;
|
|
388
|
+
if (ids.includes(id))
|
|
389
|
+
throw invalidVaultOrder(`"${entry}" names an account already in the order.`);
|
|
390
|
+
ids.push(id);
|
|
391
|
+
}
|
|
392
|
+
return ids;
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
326
396
|
function routingSection(options, snap) {
|
|
327
397
|
const { store, extraLocks } = options;
|
|
328
398
|
const locks = extraLocks ? { extraLocks } : {};
|
|
@@ -336,14 +406,18 @@ function routingSection(options, snap) {
|
|
|
336
406
|
? raw
|
|
337
407
|
: 'ordered';
|
|
338
408
|
const resolved = resolveRoutingMode(current);
|
|
339
|
-
|
|
409
|
+
// In vault mode routing tries the vault accounts, so their order is the
|
|
410
|
+
// one shown and set here; the pool roster's order is not used.
|
|
411
|
+
const vault = snap.vault ? vaultNames(snap.vault) : undefined;
|
|
412
|
+
const ids = vault ? vault.ids : snap.rows.map((row) => row.id);
|
|
413
|
+
const nameOf = (id) => (vault ? vault.nameOf(id) : id);
|
|
340
414
|
const tried = orderForPlacement(ids, resolved.placement, options.routing?.formerMainId ?? DEFAULT_FORMER_MAIN_ID);
|
|
341
415
|
const label = choices.find((choice) => choice.value === current)?.label ?? current;
|
|
342
416
|
const lines = [`Mode: ${label}`];
|
|
343
417
|
if (ids.length > 0)
|
|
344
|
-
lines.push(
|
|
418
|
+
lines.push(`${vault ? 'Vault account order' : 'Roster order'}: ${ids.map(nameOf).join(', ')}`);
|
|
345
419
|
if (resolved.mode === 'ordered' && tried.join() !== ids.join())
|
|
346
|
-
lines.push(`Tried in order: ${tried.join(', ')}`);
|
|
420
|
+
lines.push(`Tried in order: ${tried.map(nameOf).join(', ')}`);
|
|
347
421
|
const actions = [
|
|
348
422
|
{
|
|
349
423
|
id: 'mode',
|
|
@@ -371,19 +445,27 @@ function routingSection(options, snap) {
|
|
|
371
445
|
{
|
|
372
446
|
kind: 'text',
|
|
373
447
|
id: 'order',
|
|
374
|
-
label:
|
|
375
|
-
|
|
448
|
+
label: vault
|
|
449
|
+
? 'Vault accounts, first to last, separated by commas'
|
|
450
|
+
: 'Account ids, first to last',
|
|
451
|
+
value: vault ? ids.map(vault.entryOf).join(', ') : ids.join(', '),
|
|
376
452
|
required: true,
|
|
377
453
|
},
|
|
378
454
|
],
|
|
379
455
|
run: async ({ values }) => {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
456
|
+
const text = String(values.order);
|
|
457
|
+
// A vault label may contain spaces, so vault entries are separated by
|
|
458
|
+
// commas or line breaks, not spaces; pool ids never contain spaces.
|
|
459
|
+
const entries = text
|
|
460
|
+
.split(vault ? /[,\n]+/ : /[\s,]+/)
|
|
461
|
+
.map((entry) => entry.trim())
|
|
462
|
+
.filter((entry) => entry.length > 0);
|
|
463
|
+
const result = snap.vault && vault
|
|
464
|
+
? await reorderVault(snap.vault, vault.resolve(entries))
|
|
465
|
+
: await store.reorder(entries, locks);
|
|
384
466
|
return result.outcome === 'unchanged'
|
|
385
467
|
? 'The order is unchanged.'
|
|
386
|
-
: `Order set to ${result.ids.join(', ')}.`;
|
|
468
|
+
: `Order set to ${result.ids.map(nameOf).join(', ')}.`;
|
|
387
469
|
},
|
|
388
470
|
});
|
|
389
471
|
return {
|
|
@@ -524,7 +606,7 @@ function limitsSection(options, snap) {
|
|
|
524
606
|
}
|
|
525
607
|
/** The four built-in sections, in their fixed order, from one read of the store. */
|
|
526
608
|
export async function builtinSections(options) {
|
|
527
|
-
const snap = await snapshot(options
|
|
609
|
+
const snap = await snapshot(options);
|
|
528
610
|
return [
|
|
529
611
|
accountsSection(options, snap),
|
|
530
612
|
quotaSection(options, snap),
|
|
@@ -10,12 +10,22 @@ const ENROLLMENT_SCHEMA = 1;
|
|
|
10
10
|
const ENROLLMENT_FILE_MAX_BYTES = 16 * 1024;
|
|
11
11
|
const ENROLLMENT_LOCK_TTL_MS = 30_000;
|
|
12
12
|
const TOKEN_RE = /^[0-9a-f]{64}$/;
|
|
13
|
+
/**
|
|
14
|
+
* The code a vault without the enrollment ceremony answers a proposal with.
|
|
15
|
+
* Setup reports it as "this vault cannot connect plugins" instead of waiting.
|
|
16
|
+
*/
|
|
17
|
+
export const UNSUPPORTED_ENROLLMENT_CODE = 'unknown_method';
|
|
13
18
|
/**
|
|
14
19
|
* Codes the vault's closed enrollment-refusal vocabulary marks permanent. The
|
|
15
20
|
* client labels some module error frames transient/retry regardless, so the
|
|
16
21
|
* producer's code takes precedence over the client's action for these.
|
|
17
22
|
*/
|
|
18
23
|
export const TERMINAL_ENROLLMENT_CODES = new Set([
|
|
24
|
+
// The daemon's answer to an operation it does not implement: a vault built
|
|
25
|
+
// before the enrollment ceremony. It arrives as a transport Error frame with
|
|
26
|
+
// no class, which claustrum-client before 0.13.0 labels transient/retry, but
|
|
27
|
+
// asking again can never succeed.
|
|
28
|
+
UNSUPPORTED_ENROLLMENT_CODE,
|
|
19
29
|
'invalid_params',
|
|
20
30
|
'pending_exists',
|
|
21
31
|
'not_found',
|
|
@@ -638,6 +648,23 @@ export class ClaustrumEnrollmentManager {
|
|
|
638
648
|
retryCode: refusal.code,
|
|
639
649
|
};
|
|
640
650
|
}
|
|
651
|
+
// A vault without the ceremony cannot hold a request for this
|
|
652
|
+
// proposal, now or from an earlier run (the state has no request
|
|
653
|
+
// id). So nothing is blocked and nothing is in progress: remove the
|
|
654
|
+
// pending state, still under the ceremony lock, so status reads as
|
|
655
|
+
// "not enrolled" and a reset or a later run starts afresh.
|
|
656
|
+
if (refusal.code === UNSUPPORTED_ENROLLMENT_CODE) {
|
|
657
|
+
await lock.assertOwned();
|
|
658
|
+
await unlink(this.#paths.statePath).catch((cause) => {
|
|
659
|
+
if (cause.code !== 'ENOENT')
|
|
660
|
+
throw cause;
|
|
661
|
+
});
|
|
662
|
+
return {
|
|
663
|
+
state: 'unavailable',
|
|
664
|
+
proposedName: state.proposedName,
|
|
665
|
+
code: refusal.code,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
641
668
|
return this.#block(state, refusal.code);
|
|
642
669
|
}
|
|
643
670
|
}
|
|
@@ -15,7 +15,7 @@ export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHO
|
|
|
15
15
|
export { acquireInsulaQuotaSource, } from "./d0f0b6be200311af-insula.js";
|
|
16
16
|
export { acceptAccount, declineAccount, isDeclined, } from "./7c140680fc81be92-interlock.js";
|
|
17
17
|
export { drainVaultQuota } from "./81dddd203f1b92a3-quota-writer.js";
|
|
18
|
-
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
18
|
+
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
19
19
|
export { ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, WATCH_RETRY_MS, } from "./dc9290c2a7552ba6-watch.js";
|
|
20
20
|
/**
|
|
21
21
|
* Connect the client that lists and fetches this consumer's vault credentials
|
|
@@ -5,7 +5,7 @@ import { readClaustrumEnrollmentToken } from "./b9588ad4725c1d10-enrollment.js";
|
|
|
5
5
|
import { ClaustrumConsumerError } from "./ca0fccaba1d87af4-errors.js";
|
|
6
6
|
import { acquireInsulaQuotaSource, } from "./d0f0b6be200311af-insula.js";
|
|
7
7
|
import { pendingVaultQuota, retainVaultQuotaWriter } from "./81dddd203f1b92a3-quota-writer.js";
|
|
8
|
-
import { acceptVaultRoute, applyVaultQuota, declineVaultRoute, forgetPendingVaultDiscovery, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
8
|
+
import { acceptVaultRoute, applyVaultQuota, declineVaultRoute, forgetPendingVaultDiscovery, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
9
9
|
import { SAFETY_POLL_MS } from "./dc9290c2a7552ba6-watch.js";
|
|
10
10
|
/**
|
|
11
11
|
* One plugin host's view of its vault accounts. It turns vault credentials
|
|
@@ -485,8 +485,12 @@ export class ClaustrumConsumer {
|
|
|
485
485
|
let current;
|
|
486
486
|
try {
|
|
487
487
|
current = await this.authorize(routeId, signal);
|
|
488
|
+
// Cancellation can land after authorize settles, before this continuation.
|
|
489
|
+
signal?.throwIfAborted();
|
|
488
490
|
}
|
|
489
|
-
catch {
|
|
491
|
+
catch (error) {
|
|
492
|
+
if (signal?.aborted)
|
|
493
|
+
throw error;
|
|
490
494
|
// Re-authorization failed: keep this 401 and report it against the
|
|
491
495
|
// receipt (served record version) this request was sent with.
|
|
492
496
|
}
|
|
@@ -522,6 +526,17 @@ export class ClaustrumConsumer {
|
|
|
522
526
|
await acceptVaultRoute(this.#options.rosterPath, routeId);
|
|
523
527
|
this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
524
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Set the order vault accounts are tried in (`reorderVaultRoster`), then
|
|
531
|
+
* re-read the roster, so the next routing decision in this process uses
|
|
532
|
+
* the new order without waiting for a discovery. Never contacts the vault.
|
|
533
|
+
*/
|
|
534
|
+
async reorder(routeIds) {
|
|
535
|
+
this.#assertOpen();
|
|
536
|
+
const result = await reorderVaultRoster(this.#options.rosterPath, routeIds);
|
|
537
|
+
this.#roster = pendingVaultQuota(this.#options.rosterPath, await readVaultRoster(this.#options.rosterPath));
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
525
540
|
/**
|
|
526
541
|
* Store a quota or profile observation for a vault route. The receipt the
|
|
527
542
|
* reading was taken with is required: the observation lands only while the
|
|
@@ -378,6 +378,78 @@ export function acceptVaultRoute(path, routeId) {
|
|
|
378
378
|
};
|
|
379
379
|
});
|
|
380
380
|
}
|
|
381
|
+
function invalidOrder(message) {
|
|
382
|
+
return new ClaustrumConsumerError('invalid-order', message);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Why `routeIds` is not an order of these rows: it must name every distinct
|
|
386
|
+
* route id exactly once and nothing else. The message quotes only ids the
|
|
387
|
+
* caller supplied; route ids it left out are counted, not named.
|
|
388
|
+
*/
|
|
389
|
+
function vaultOrderProblem(rows, routeIds) {
|
|
390
|
+
if (!Array.isArray(routeIds))
|
|
391
|
+
return 'route ids must be an array of route ids';
|
|
392
|
+
const rosterIds = new Set(rows.map((row) => row.routeId));
|
|
393
|
+
const given = new Set();
|
|
394
|
+
for (const id of routeIds) {
|
|
395
|
+
if (typeof id !== 'string')
|
|
396
|
+
return 'route ids must be an array of route ids';
|
|
397
|
+
if (given.has(id))
|
|
398
|
+
return `route id ${id} appears more than once`;
|
|
399
|
+
if (!rosterIds.has(id))
|
|
400
|
+
return `route id ${id} is not in the roster`;
|
|
401
|
+
given.add(id);
|
|
402
|
+
}
|
|
403
|
+
const missing = [...rosterIds].filter((id) => !given.has(id)).length;
|
|
404
|
+
if (missing > 0)
|
|
405
|
+
return `the order leaves out ${missing} roster route id${missing === 1 ? '' : 's'}`;
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Set the order vault accounts are tried in. Routing reads that order from
|
|
410
|
+
* the roster's row order (`vaultRoutingRows`), and a later discovery keeps
|
|
411
|
+
* each account at its position, so the order lasts until the next reorder.
|
|
412
|
+
* The order is this host's routing preference: the vault has no order of its
|
|
413
|
+
* own, and this never contacts it.
|
|
414
|
+
*
|
|
415
|
+
* `routeIds` must name every roster route id exactly once; anything else
|
|
416
|
+
* (an unknown, missing or repeated id) refuses with `invalid-order` and
|
|
417
|
+
* writes nothing. It runs under the roster's write lock, like every other
|
|
418
|
+
* roster write, so a quota reading or decline committed meanwhile is kept.
|
|
419
|
+
* Each row is kept as the same object, so only the positions change: every
|
|
420
|
+
* row's fields and the rest of the file stay as they were. An order equal to
|
|
421
|
+
* the current one writes nothing. A host with no roster file yet has no
|
|
422
|
+
* routes, so only the empty order is accepted.
|
|
423
|
+
*
|
|
424
|
+
* A `ClaustrumConsumer` in the same process does not see the new order until
|
|
425
|
+
* it re-reads the file: call `consumer.reorder` instead to update both.
|
|
426
|
+
*/
|
|
427
|
+
export function reorderVaultRoster(path, routeIds, options = {}) {
|
|
428
|
+
return mutateVaultRoster(path, (current) => {
|
|
429
|
+
const rows = current?.rows ?? [];
|
|
430
|
+
const problem = vaultOrderProblem(rows, routeIds);
|
|
431
|
+
if (problem)
|
|
432
|
+
throw invalidOrder(problem);
|
|
433
|
+
// Group by route id so a hand-edited file that repeats one keeps every
|
|
434
|
+
// copy: the later copies travel right after the first.
|
|
435
|
+
const byId = new Map();
|
|
436
|
+
for (const row of rows) {
|
|
437
|
+
const group = byId.get(row.routeId);
|
|
438
|
+
if (group)
|
|
439
|
+
group.push(row);
|
|
440
|
+
else
|
|
441
|
+
byId.set(row.routeId, [row]);
|
|
442
|
+
}
|
|
443
|
+
const ids = [...routeIds];
|
|
444
|
+
const next = ids.flatMap((id) => byId.get(id) ?? []);
|
|
445
|
+
if (!current || next.every((row, index) => row === rows[index]))
|
|
446
|
+
return { result: { ids, outcome: 'unchanged' } };
|
|
447
|
+
return {
|
|
448
|
+
next: { ...current, rows: next },
|
|
449
|
+
result: { ids, outcome: 'reordered' },
|
|
450
|
+
};
|
|
451
|
+
}, { ...(options.signal && { signal: options.signal }) });
|
|
452
|
+
}
|
|
381
453
|
/**
|
|
382
454
|
* What a receipt may do to a row's quota: `hold` (the row holds the receipt's
|
|
383
455
|
* credential and account), `bind` (the row has never had a known account and
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { lstat, readFile, rename, unlink } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
5
|
export const windowsPrivateFiles = process.platform === 'win32';
|
|
5
6
|
const migrationNotices = new Set();
|
|
6
7
|
function ioCode(detail) {
|
|
@@ -20,8 +21,51 @@ let backend;
|
|
|
20
21
|
export function setPrivateFilesBackendForTest(api) {
|
|
21
22
|
backend = api ? Promise.resolve(api) : undefined;
|
|
22
23
|
}
|
|
24
|
+
/** The fixed `reason` of a refusal because the Windows native addon did not load. */
|
|
25
|
+
export const PRIVATE_FILES_UNAVAILABLE_REASON = 'native-addon-unavailable';
|
|
26
|
+
function privateFilesUnavailable(cause) {
|
|
27
|
+
return Object.assign(new Error('Windows private-files native addon is unavailable', { cause }), {
|
|
28
|
+
code: 'ERR_PRIVATE_FILES_UNAVAILABLE',
|
|
29
|
+
reason: PRIVATE_FILES_UNAVAILABLE_REASON,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function isUnavailable(api, error) {
|
|
33
|
+
return (error instanceof api.PrivateFilesUnavailableError ||
|
|
34
|
+
error?.code ===
|
|
35
|
+
'ERR_PRIVATE_FILES_UNAVAILABLE');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Importing @cortexkit/private-files succeeds even when its Windows addon
|
|
39
|
+
* cannot load: the package loads the addon on first use, and some of its
|
|
40
|
+
* checks then report the failure as an ordinary refusal such as
|
|
41
|
+
* `unsupported-filesystem`. So prove the addon loads before handing the
|
|
42
|
+
* package out. Verifying a path that does not exist loads the addon first and
|
|
43
|
+
* then fails harmlessly; only the package's own "unavailable" error means the
|
|
44
|
+
* addon is missing.
|
|
45
|
+
*/
|
|
46
|
+
async function assertNativeAddonLoads(api) {
|
|
47
|
+
try {
|
|
48
|
+
await api.verifyPrivatePath(join(tmpdir(), `.common-auth-private-files-probe-${randomUUID()}`), 'file');
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (isUnavailable(api, error))
|
|
52
|
+
throw privateFilesUnavailable(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load the package once per process. On Windows a failed import or addon load
|
|
57
|
+
* is kept, so every later private-file operation refuses at once with
|
|
58
|
+
* ERR_PRIVATE_FILES_UNAVAILABLE. There is no fallback to permission bits:
|
|
59
|
+
* they do not protect files on Windows.
|
|
60
|
+
*/
|
|
23
61
|
function loadBackend() {
|
|
24
|
-
backend ??= import("@cortexkit/private-files")
|
|
62
|
+
backend ??= import("@cortexkit/private-files").then(async (api) => {
|
|
63
|
+
if (windowsPrivateFiles)
|
|
64
|
+
await assertNativeAddonLoads(api);
|
|
65
|
+
return api;
|
|
66
|
+
}, (cause) => {
|
|
67
|
+
throw windowsPrivateFiles ? privateFilesUnavailable(cause) : cause;
|
|
68
|
+
});
|
|
25
69
|
return backend;
|
|
26
70
|
}
|
|
27
71
|
export class PrivateFileError extends Error {
|
|
@@ -38,7 +82,9 @@ export class PrivateFileError extends Error {
|
|
|
38
82
|
orphanPath;
|
|
39
83
|
constructor(path, stage, cause) {
|
|
40
84
|
const detail = cause;
|
|
41
|
-
super(
|
|
85
|
+
super(detail?.code === 'ERR_PRIVATE_FILES_UNAVAILABLE'
|
|
86
|
+
? `Private file refused at ${stage}: ${path} (${PRIVATE_FILES_UNAVAILABLE_REASON}). The Windows native addon of @cortexkit/private-files could not be loaded, so owner-only access cannot be enforced and nothing was read or written. Reinstall the plugin so the private-files Windows prebuild for this architecture is present; Windows has no safe fallback to permission bits.`
|
|
87
|
+
: `Private file refused at ${stage}: ${path} (${detail?.reason ?? detail?.code ?? 'io-error'}). Keep this file in a folder that only you, the system or administrators can change, such as a folder in your home directory. If other users could read the file before, sign in again to replace the credentials it held. On Windows the file must also be on a local drive that keeps Windows file permissions, such as NTFS.`, { cause });
|
|
42
88
|
this.path = path;
|
|
43
89
|
this.stage = stage;
|
|
44
90
|
this.name = 'PrivateFileError';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// the same locks as any other writer of those files.
|
|
5
5
|
import { formatQuota, isQuotaMap, projectQuota, quotaWindowName, } from "./d5a8f1061ee2f114-index.js";
|
|
6
6
|
import { DEFAULT_FORMER_MAIN_ID, orderForPlacement, resolveRoutingMode, } from "./3e7d45a77c6ad4d5-index.js";
|
|
7
|
-
import { projectFailure, } from "./94820e324f2b4c09-seam.js";
|
|
7
|
+
import { CommandError, projectFailure, } from "./94820e324f2b4c09-seam.js";
|
|
8
8
|
/** `disabledReason` recorded when the user disables an account from the menu. */
|
|
9
9
|
export const MENU_DISABLED_REASON = 'disabled from the command menu';
|
|
10
10
|
function isRecord(value) {
|
|
@@ -65,13 +65,16 @@ function addedText(name, result) {
|
|
|
65
65
|
function failureMessage(error) {
|
|
66
66
|
return projectFailure(error).text;
|
|
67
67
|
}
|
|
68
|
-
async function snapshot(
|
|
68
|
+
async function snapshot(options) {
|
|
69
|
+
const { store } = options;
|
|
69
70
|
const load = await store.read();
|
|
70
71
|
const read = await store.readSettings();
|
|
72
|
+
const vault = await options.routing?.vaultOrder?.();
|
|
71
73
|
return {
|
|
72
74
|
load,
|
|
73
75
|
rows: distinctRows(load),
|
|
74
76
|
settings: read.status === 'error' ? {} : read.settings,
|
|
77
|
+
...(vault ? { vault } : {}),
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
function accountView(row, options) {
|
|
@@ -323,6 +326,73 @@ function quotaSection(options, snap) {
|
|
|
323
326
|
},
|
|
324
327
|
};
|
|
325
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Sets the vault order. The roster's own refusal of the list (`invalid-order`:
|
|
331
|
+
* an account left out, which it counts without naming) is shown with its own
|
|
332
|
+
* message; any other failure is projected as usual. The kind is read from
|
|
333
|
+
* the error object so the menu does not import the vault client.
|
|
334
|
+
*/
|
|
335
|
+
async function reorderVault(vault, order) {
|
|
336
|
+
try {
|
|
337
|
+
return await vault.reorder(order);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error instanceof Error &&
|
|
341
|
+
error.name === 'ClaustrumConsumerError' &&
|
|
342
|
+
error.kind === 'invalid-order')
|
|
343
|
+
throw new CommandError('vault-invalid-order', error.message, {
|
|
344
|
+
cause: error,
|
|
345
|
+
});
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function invalidVaultOrder(message) {
|
|
350
|
+
return new CommandError('vault-invalid-order', message);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* How the Routing section names vault accounts: by label, since route ids
|
|
354
|
+
* are long and opaque. A typed entry may be a label or a route id, and must
|
|
355
|
+
* name exactly one account. Refusals quote only the entry the user typed.
|
|
356
|
+
*/
|
|
357
|
+
function vaultNames(vault) {
|
|
358
|
+
const rows = vault.rows.map((row) => ({
|
|
359
|
+
id: row.id,
|
|
360
|
+
label: row.label.trim() || row.id,
|
|
361
|
+
}));
|
|
362
|
+
const matches = (entry) => rows.filter((row) => row.id === entry || row.label === entry);
|
|
363
|
+
return {
|
|
364
|
+
ids: rows.map((row) => row.id),
|
|
365
|
+
nameOf: (id) => rows.find((row) => row.id === id)?.label ?? id,
|
|
366
|
+
/**
|
|
367
|
+
* What the order input starts with for an account: its label when
|
|
368
|
+
* typing that label back names this account alone, else its route id
|
|
369
|
+
* (two accounts sharing a label, or a label with a comma or line break
|
|
370
|
+
* in it, either of which would split it into two entries).
|
|
371
|
+
*/
|
|
372
|
+
entryOf: (id) => {
|
|
373
|
+
const row = rows.find((entry) => entry.id === id);
|
|
374
|
+
return row && !/[,\n]/.test(row.label) && matches(row.label).length === 1
|
|
375
|
+
? row.label
|
|
376
|
+
: id;
|
|
377
|
+
},
|
|
378
|
+
/** The route ids the typed entries name, in order; refuses before any write. */
|
|
379
|
+
resolve: (entries) => {
|
|
380
|
+
const ids = [];
|
|
381
|
+
for (const entry of entries) {
|
|
382
|
+
const found = matches(entry);
|
|
383
|
+
if (found.length === 0)
|
|
384
|
+
throw invalidVaultOrder(`"${entry}" is not a vault account.`);
|
|
385
|
+
if (found.length > 1)
|
|
386
|
+
throw invalidVaultOrder(`"${entry}" matches more than one vault account; enter those accounts by route id.`);
|
|
387
|
+
const id = found[0].id;
|
|
388
|
+
if (ids.includes(id))
|
|
389
|
+
throw invalidVaultOrder(`"${entry}" names an account already in the order.`);
|
|
390
|
+
ids.push(id);
|
|
391
|
+
}
|
|
392
|
+
return ids;
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
326
396
|
function routingSection(options, snap) {
|
|
327
397
|
const { store, extraLocks } = options;
|
|
328
398
|
const locks = extraLocks ? { extraLocks } : {};
|
|
@@ -336,14 +406,18 @@ function routingSection(options, snap) {
|
|
|
336
406
|
? raw
|
|
337
407
|
: 'ordered';
|
|
338
408
|
const resolved = resolveRoutingMode(current);
|
|
339
|
-
|
|
409
|
+
// In vault mode routing tries the vault accounts, so their order is the
|
|
410
|
+
// one shown and set here; the pool roster's order is not used.
|
|
411
|
+
const vault = snap.vault ? vaultNames(snap.vault) : undefined;
|
|
412
|
+
const ids = vault ? vault.ids : snap.rows.map((row) => row.id);
|
|
413
|
+
const nameOf = (id) => (vault ? vault.nameOf(id) : id);
|
|
340
414
|
const tried = orderForPlacement(ids, resolved.placement, options.routing?.formerMainId ?? DEFAULT_FORMER_MAIN_ID);
|
|
341
415
|
const label = choices.find((choice) => choice.value === current)?.label ?? current;
|
|
342
416
|
const lines = [`Mode: ${label}`];
|
|
343
417
|
if (ids.length > 0)
|
|
344
|
-
lines.push(
|
|
418
|
+
lines.push(`${vault ? 'Vault account order' : 'Roster order'}: ${ids.map(nameOf).join(', ')}`);
|
|
345
419
|
if (resolved.mode === 'ordered' && tried.join() !== ids.join())
|
|
346
|
-
lines.push(`Tried in order: ${tried.join(', ')}`);
|
|
420
|
+
lines.push(`Tried in order: ${tried.map(nameOf).join(', ')}`);
|
|
347
421
|
const actions = [
|
|
348
422
|
{
|
|
349
423
|
id: 'mode',
|
|
@@ -371,19 +445,27 @@ function routingSection(options, snap) {
|
|
|
371
445
|
{
|
|
372
446
|
kind: 'text',
|
|
373
447
|
id: 'order',
|
|
374
|
-
label:
|
|
375
|
-
|
|
448
|
+
label: vault
|
|
449
|
+
? 'Vault accounts, first to last, separated by commas'
|
|
450
|
+
: 'Account ids, first to last',
|
|
451
|
+
value: vault ? ids.map(vault.entryOf).join(', ') : ids.join(', '),
|
|
376
452
|
required: true,
|
|
377
453
|
},
|
|
378
454
|
],
|
|
379
455
|
run: async ({ values }) => {
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
456
|
+
const text = String(values.order);
|
|
457
|
+
// A vault label may contain spaces, so vault entries are separated by
|
|
458
|
+
// commas or line breaks, not spaces; pool ids never contain spaces.
|
|
459
|
+
const entries = text
|
|
460
|
+
.split(vault ? /[,\n]+/ : /[\s,]+/)
|
|
461
|
+
.map((entry) => entry.trim())
|
|
462
|
+
.filter((entry) => entry.length > 0);
|
|
463
|
+
const result = snap.vault && vault
|
|
464
|
+
? await reorderVault(snap.vault, vault.resolve(entries))
|
|
465
|
+
: await store.reorder(entries, locks);
|
|
384
466
|
return result.outcome === 'unchanged'
|
|
385
467
|
? 'The order is unchanged.'
|
|
386
|
-
: `Order set to ${result.ids.join(', ')}.`;
|
|
468
|
+
: `Order set to ${result.ids.map(nameOf).join(', ')}.`;
|
|
387
469
|
},
|
|
388
470
|
});
|
|
389
471
|
return {
|
|
@@ -524,7 +606,7 @@ function limitsSection(options, snap) {
|
|
|
524
606
|
}
|
|
525
607
|
/** The four built-in sections, in their fixed order, from one read of the store. */
|
|
526
608
|
export async function builtinSections(options) {
|
|
527
|
-
const snap = await snapshot(options
|
|
609
|
+
const snap = await snapshot(options);
|
|
528
610
|
return [
|
|
529
611
|
accountsSection(options, snap),
|
|
530
612
|
quotaSection(options, snap),
|
|
@@ -10,12 +10,22 @@ const ENROLLMENT_SCHEMA = 1;
|
|
|
10
10
|
const ENROLLMENT_FILE_MAX_BYTES = 16 * 1024;
|
|
11
11
|
const ENROLLMENT_LOCK_TTL_MS = 30_000;
|
|
12
12
|
const TOKEN_RE = /^[0-9a-f]{64}$/;
|
|
13
|
+
/**
|
|
14
|
+
* The code a vault without the enrollment ceremony answers a proposal with.
|
|
15
|
+
* Setup reports it as "this vault cannot connect plugins" instead of waiting.
|
|
16
|
+
*/
|
|
17
|
+
export const UNSUPPORTED_ENROLLMENT_CODE = 'unknown_method';
|
|
13
18
|
/**
|
|
14
19
|
* Codes the vault's closed enrollment-refusal vocabulary marks permanent. The
|
|
15
20
|
* client labels some module error frames transient/retry regardless, so the
|
|
16
21
|
* producer's code takes precedence over the client's action for these.
|
|
17
22
|
*/
|
|
18
23
|
export const TERMINAL_ENROLLMENT_CODES = new Set([
|
|
24
|
+
// The daemon's answer to an operation it does not implement: a vault built
|
|
25
|
+
// before the enrollment ceremony. It arrives as a transport Error frame with
|
|
26
|
+
// no class, which claustrum-client before 0.13.0 labels transient/retry, but
|
|
27
|
+
// asking again can never succeed.
|
|
28
|
+
UNSUPPORTED_ENROLLMENT_CODE,
|
|
19
29
|
'invalid_params',
|
|
20
30
|
'pending_exists',
|
|
21
31
|
'not_found',
|
|
@@ -638,6 +648,23 @@ export class ClaustrumEnrollmentManager {
|
|
|
638
648
|
retryCode: refusal.code,
|
|
639
649
|
};
|
|
640
650
|
}
|
|
651
|
+
// A vault without the ceremony cannot hold a request for this
|
|
652
|
+
// proposal, now or from an earlier run (the state has no request
|
|
653
|
+
// id). So nothing is blocked and nothing is in progress: remove the
|
|
654
|
+
// pending state, still under the ceremony lock, so status reads as
|
|
655
|
+
// "not enrolled" and a reset or a later run starts afresh.
|
|
656
|
+
if (refusal.code === UNSUPPORTED_ENROLLMENT_CODE) {
|
|
657
|
+
await lock.assertOwned();
|
|
658
|
+
await unlink(this.#paths.statePath).catch((cause) => {
|
|
659
|
+
if (cause.code !== 'ENOENT')
|
|
660
|
+
throw cause;
|
|
661
|
+
});
|
|
662
|
+
return {
|
|
663
|
+
state: 'unavailable',
|
|
664
|
+
proposedName: state.proposedName,
|
|
665
|
+
code: refusal.code,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
641
668
|
return this.#block(state, refusal.code);
|
|
642
669
|
}
|
|
643
670
|
}
|
|
@@ -15,7 +15,7 @@ export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHO
|
|
|
15
15
|
export { acquireInsulaQuotaSource, } from "./d0f0b6be200311af-insula.js";
|
|
16
16
|
export { acceptAccount, declineAccount, isDeclined, } from "./7c140680fc81be92-interlock.js";
|
|
17
17
|
export { drainVaultQuota } from "./81dddd203f1b92a3-quota-writer.js";
|
|
18
|
-
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
18
|
+
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from "./30f5bc480f31f51c-roster.js";
|
|
19
19
|
export { ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, WATCH_RETRY_MS, } from "./dc9290c2a7552ba6-watch.js";
|
|
20
20
|
/**
|
|
21
21
|
* Connect the client that lists and fetches this consumer's vault credentials
|
package/dist/index-5230x6s8.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{jf as E}from"./index-d98f1xdp.js";import{randomUUID as k}from"node:crypto";import{lstat as S,readFile as O,rename as F,unlink as C}from"node:fs/promises";import{dirname as V,resolve as M}from"node:path";var b=process.platform==="win32",N=new Set;function T(j){if(j?.reason==="io-error"){if(j.win32Code===2||j.win32Code===3)return"ENOENT";if(j.win32Code===80||j.win32Code===183)return"EEXIST"}return j?.code??"ERR_PRIVATE_FILE"}var B;function $(){return B??=import("@cortexkit/private-files"),B}class R extends Error{path;stage;code;win32Code;reason;published=!1;durabilityConfirmed=!1;fileDurable=!1;attempts=0;elapsedMs=0;orphanPath;constructor(j,G,A){let z=A;super(`Private file refused at ${G}: ${j} (${z?.reason??z?.code??"io-error"}). Use a safe parent directory. Repair only a named secret file in safe ancestry whose owner the published policy trusts, without changing its owner or any existing directory. Previously exposed credentials may need rotation. Windows requires the private-files native prebuild and an ACL-persisting local filesystem such as NTFS.`,{cause:A});this.path=j,this.stage=G,this.name="PrivateFileError",this.code=T(z),this.win32Code=z?.win32Code,this.reason=z?.reason}}async function q(j,G,A){try{return await A()}catch(z){if(z instanceof R)throw z;throw new R(j,G,z)}}var g;async function Z(j,G){j=M(j),G??=g,await q(j,"safe-parent",async()=>{let A=await(await $()).verifySafeParent(j,G);if(A.ok===!1){if(A.reason==="io-error"||A.reason==="wrong-kind")await S(j);throw new R(A.level??A.path,"safe-parent",{...A,code:"ERR_PRIVATE_FILES_SECURITY"})}})}async function U(j,G,A=!1){if(j=M(j),G==="directory")return Z(j);await q(j,"verify",async()=>{let z=await $(),K=await z.verifyOwnerOnly(j),H=!1;if(!K.ok){let{reason:L}=K.refusal;if(L==="io-error")await z.verifyPrivatePath(j,G);if(A&&[z.EXTRA_ACE,z.NOT_PROTECTED,z.NULL_DACL,z.EMPTY_DACL].includes(L))await Z(V(j)),await z.securePrivatePath(j,G),H=!0;else throw Object.assign(Error("Owner-only verification refused"),{reason:L,code:"ERR_PRIVATE_FILES_SECURITY"})}if(await z.verifyPrivatePath(j,G),H&&!N.has(j))N.add(j),process.emitWarning(`Private permissions tightened: ${j}. This object may have been readable before; rotate credentials if needed.`,"PrivateFileMigration")})}async function w(j,G=!1,A){j=M(j),await q(j,"directory",async()=>{let z=await $();try{return await S(j),await Z(j,A)}catch(H){if(T(H)!=="ENOENT")throw H}let K=V(j);if(K===j)throw Object.assign(Error("Missing root"),{code:"ENOENT"});await w(K,!1,A);try{await z.createOwnerOnly(j,{kind:"dir"})}catch(H){if(T(H)!=="EEXIST")throw H}await Z(j,A)})}async function d(j,G=!1){if(!b)return O(j,"utf8");return(await P(j,G)).toString("utf8")}async function P(j,G=!1){if(!b)return O(j);if(j=M(j),G)await Z(V(j)),await U(j,"file",!0);return q(j,"read",async()=>(await $()).readOwnerOnly(j))}async function f(j,G){await Z(V(M(j))),await q(j,"create",async()=>{await(await $()).createPrivateFile(M(j),G)})}async function l(j){await Z(V(M(j))),await q(j,"directory-create",async()=>{await(await $()).createOwnerOnly(M(j),{kind:"dir"})})}async function y(j,G,A={}){j=M(j),G=M(G);let z=A.now??(()=>performance.now()),K=z(),H=A.deadline??1/0,L,_=[0,10,20,40,80,100],x,J=0;for(let W of _){let I=Math.min(L??1/0,H);if(z()>=I)break;if(W)await(A.sleep??((X)=>new Promise((Y)=>setTimeout(Y,X))))(Math.min(W,Math.max(0,I-z())));if(z()>=I)break;let D=z();if(await A.beforeRename?.(),L!==void 0)L+=z()-D;if(z()>=H)break;J++;try{return await(A.rename??F)(j,G),{attempts:J,elapsedMs:z()-K}}catch(X){x??=X;let Y=X;if(!(Y.win32Code!==void 0?Y.win32Code===32||Y.win32Code===33:["EBUSY","EPERM","EACCES"].includes(Y.code??"")&&!Y.reason))break;L??=z()+500}}let Q=new R(G,"rename",x??Object.assign(Error("Publication deadline expired"),{code:"ETIMEDOUT"}));throw Q.attempts=J,Q.elapsedMs=z()-K,Q}async function n(j,G,A={}){j=M(j),await w(V(j));try{await U(j,"file",!0)}catch(J){if(J.code!=="ENOENT")throw J}let z=M(A.stagePath??`${j}.${k()}.tmp`);if(V(z)!==V(j))throw new R(j,"stage-directory",{code:"EXDEV"});let K=!1,H=!1,L={attempts:0,elapsedMs:0},_="stage-create",x=(J)=>{try{A.onOutcome?.({path:j,stage:J?.stage??"published",published:H,fileDurable:H,durabilityConfirmed:!1,attempts:J?.attempts??L.attempts,elapsedMs:J?.elapsedMs??L.elapsedMs,...J&&{code:J.code,win32Code:J.win32Code}})}catch{}};try{if(await f(z,G),K=!0,_="before-rename",L=await y(z,j,A),K=!1,H=!0,_="published-callback",A.onRenamed?.(),_="published-verify",await U(j,"file"),A.durable&&A.durability!=="file-only-on-windows")throw new R(j,"directory-sync-unsupported",{code:"ENOTSUP"});x()}catch(J){let Q=J instanceof R?J:new R(j,_,J);if(Q.published=H,Q.fileDurable=H,H)Object.assign(Q,L);if(K){try{await C(z)}catch{Q.orphanPath=z}K=!1}throw x(Q),Q}finally{if(K)await C(z).catch(()=>{})}}
|
|
2
|
-
export{b as Ue,R as Ve,q as We,Z as Xe,U as Ye,w as Ze,d as _e,f as $e,l as af,y as bf,n as cf};
|