@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index-ww91hyzr.js +221 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
4
|
-
|
|
5
|
-
const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis)
|
|
6
|
-
const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis)
|
|
7
|
-
|
|
8
|
-
// A concurrent contender renaming the freshly-created eviction-marker directory
|
|
9
|
-
// away surfaces the vanished parent differently per platform: ENOENT on Linux,
|
|
10
|
-
// EINVAL or ENOTDIR on macOS/APFS. All three mean the marker is no longer ours
|
|
11
|
-
// to hold — a lost race the caller should retry, not a fatal lock error.
|
|
12
|
-
export function isLostMarkerRaceError(error: unknown): boolean {
|
|
13
|
-
const code = (error as NodeJS.ErrnoException | null)?.code
|
|
14
|
-
return code === 'ENOENT' || code === 'EINVAL' || code === 'ENOTDIR'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function acquireRefreshFileLock(options: {
|
|
18
|
-
name: string
|
|
19
|
-
ttlMs: number
|
|
20
|
-
/**
|
|
21
|
-
* File the lock is named after. Required: the lock and the write it guards
|
|
22
|
-
* must target the same file, and a default resolved in here could only ever
|
|
23
|
-
* be one host's path.
|
|
24
|
-
*/
|
|
25
|
-
path: string
|
|
26
|
-
now?: () => number
|
|
27
|
-
renew?: boolean
|
|
28
|
-
renewIntervalMs?: number
|
|
29
|
-
onStep?: (
|
|
30
|
-
step:
|
|
31
|
-
| 'stale-marker-stat'
|
|
32
|
-
| 'stale-marker-claimed'
|
|
33
|
-
| 'stale-lock-confirmed'
|
|
34
|
-
| 'eviction-marker-acquired'
|
|
35
|
-
| 'renewal-owner-confirmed'
|
|
36
|
-
| 'renewal-marker-unavailable'
|
|
37
|
-
| 'renewal-write-fenced'
|
|
38
|
-
| 'renewal-write-ready'
|
|
39
|
-
| 'relinquish-read'
|
|
40
|
-
| 'renewal-finished'
|
|
41
|
-
| 'release-owner-confirmed',
|
|
42
|
-
) => void | Promise<void>
|
|
43
|
-
}): Promise<{ release: () => Promise<void> } | null> {
|
|
44
|
-
const lockPath = `${options.path}.${options.name}.lock`
|
|
45
|
-
const legacyOwnerPath = join(lockPath, 'owner.json')
|
|
46
|
-
const ownerId = randomUUID()
|
|
47
|
-
const now = options.now ?? Date.now
|
|
48
|
-
let renewTimer: ReturnType<typeof setTimeout> | null = null
|
|
49
|
-
let released = false
|
|
50
|
-
let renewalInFlight: Promise<void> | null = null
|
|
51
|
-
// Fencing-token eviction: a directory-based marker (mkdir O_EXCL) serializes
|
|
52
|
-
// destructive removal and generation-sensitive owner mutations. The marker
|
|
53
|
-
// holds an owner file so ownership survives a stale-marker recovery rename:
|
|
54
|
-
// the recovering contender renames the stale directory, then must re-check
|
|
55
|
-
// ownership before acting — preventing the 3rd interleaving where a stale
|
|
56
|
-
// observer renames the FRESH marker the mkdir-winner created.
|
|
57
|
-
const evictPath = `${lockPath}.evicting`
|
|
58
|
-
const evictOwnerPath = join(evictPath, 'owner.json')
|
|
59
|
-
const evictOwnerId = randomUUID()
|
|
60
|
-
const EVICT_TTL = 5_000
|
|
61
|
-
const MAX_STEAL_ATTEMPTS = 8
|
|
62
|
-
|
|
63
|
-
async function readOwner() {
|
|
64
|
-
try {
|
|
65
|
-
return JSON.parse(await readFile(lockPath, 'utf8'))
|
|
66
|
-
} catch (error) {
|
|
67
|
-
const code = (error as NodeJS.ErrnoException).code
|
|
68
|
-
if (code !== 'EISDIR') throw error
|
|
69
|
-
return JSON.parse(await readFile(legacyOwnerPath, 'utf8'))
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function writeOwner() {
|
|
74
|
-
await writeFile(
|
|
75
|
-
lockPath,
|
|
76
|
-
`${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`,
|
|
77
|
-
{ encoding: 'utf8', mode: 0o600 },
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function tryAcquire() {
|
|
82
|
-
try {
|
|
83
|
-
await writeFile(
|
|
84
|
-
lockPath,
|
|
85
|
-
`${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`,
|
|
86
|
-
{ encoding: 'utf8', mode: 0o600, flag: 'wx' },
|
|
87
|
-
)
|
|
88
|
-
return true
|
|
89
|
-
} catch (error) {
|
|
90
|
-
const code = (error as NodeJS.ErrnoException).code
|
|
91
|
-
if (code === 'EEXIST' || code === 'EISDIR') return false
|
|
92
|
-
if (code === 'ENOENT') {
|
|
93
|
-
// Reboots can clear runtime temp directories before the next lock acquisition.
|
|
94
|
-
await mkdir(dirname(lockPath), { recursive: true })
|
|
95
|
-
try {
|
|
96
|
-
await writeFile(
|
|
97
|
-
lockPath,
|
|
98
|
-
`${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`,
|
|
99
|
-
{ encoding: 'utf8', mode: 0o600, flag: 'wx' },
|
|
100
|
-
)
|
|
101
|
-
return true
|
|
102
|
-
} catch (retryError) {
|
|
103
|
-
const retryCode = (retryError as NodeJS.ErrnoException).code
|
|
104
|
-
if (retryCode === 'EEXIST' || retryCode === 'EISDIR') return false
|
|
105
|
-
throw retryError
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
throw error
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function backoff() {
|
|
113
|
-
await new Promise((resolve) =>
|
|
114
|
-
setTimeout(resolve, Math.floor(Math.random() * 4)),
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function lockIsLive() {
|
|
119
|
-
try {
|
|
120
|
-
const currentOwner = await readOwner()
|
|
121
|
-
return Number(currentOwner?.expiresAt) > now()
|
|
122
|
-
} catch {
|
|
123
|
-
try {
|
|
124
|
-
const current = await stat(lockPath)
|
|
125
|
-
return current.mtimeMs + options.ttlMs > now()
|
|
126
|
-
} catch {
|
|
127
|
-
// Lock doesn't exist — safe to acquire.
|
|
128
|
-
return false
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Fail-closed: any read error means we do NOT own the marker.
|
|
134
|
-
async function ownsEvictionMarker() {
|
|
135
|
-
try {
|
|
136
|
-
const owner = JSON.parse(await readFile(evictOwnerPath, 'utf8'))
|
|
137
|
-
return owner?.ownerId === evictOwnerId
|
|
138
|
-
} catch {
|
|
139
|
-
return false
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function releaseEvictionMarker() {
|
|
144
|
-
if (await ownsEvictionMarker()) {
|
|
145
|
-
await rm(evictPath, { recursive: true, force: true }).catch(() => {})
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function tryAcquireEvictionMarker() {
|
|
150
|
-
await mkdir(evictPath)
|
|
151
|
-
try {
|
|
152
|
-
await writeFile(
|
|
153
|
-
evictOwnerPath,
|
|
154
|
-
`${JSON.stringify({ ownerId: evictOwnerId, createdAt: now() })}\n`,
|
|
155
|
-
{ encoding: 'utf8', mode: 0o600, flag: 'wx' },
|
|
156
|
-
)
|
|
157
|
-
} catch (error) {
|
|
158
|
-
// A competing contender can rename our just-created marker directory
|
|
159
|
-
// away between the mkdir above and this write (the stale-marker steal
|
|
160
|
-
// path below does exactly that). That is a lost race, not a failure, so
|
|
161
|
-
// report it as such and let the caller back off and retry rather than
|
|
162
|
-
// failing the whole lock acquisition.
|
|
163
|
-
if (isLostMarkerRaceError(error)) return false
|
|
164
|
-
await releaseEvictionMarker()
|
|
165
|
-
throw error
|
|
166
|
-
}
|
|
167
|
-
if (options.onStep) await options.onStep('eviction-marker-acquired')
|
|
168
|
-
return true
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function recoverStaleEvictionMarker(): Promise<
|
|
172
|
-
'fresh' | 'missing' | 'recovered'
|
|
173
|
-
> {
|
|
174
|
-
let evictStat: Awaited<ReturnType<typeof stat>>
|
|
175
|
-
try {
|
|
176
|
-
evictStat = await stat(evictPath)
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return 'missing'
|
|
179
|
-
throw error
|
|
180
|
-
}
|
|
181
|
-
if (evictStat.mtimeMs + EVICT_TTL > now()) return 'fresh'
|
|
182
|
-
|
|
183
|
-
if (options.onStep) await options.onStep('stale-marker-stat')
|
|
184
|
-
const claimedPath = `${evictPath}.${randomUUID()}`
|
|
185
|
-
try {
|
|
186
|
-
await rename(evictPath, claimedPath)
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return 'missing'
|
|
189
|
-
throw error
|
|
190
|
-
}
|
|
191
|
-
if (options.onStep) await options.onStep('stale-marker-claimed')
|
|
192
|
-
await rm(claimedPath, { recursive: true, force: true }).catch(() => {})
|
|
193
|
-
return 'recovered'
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function withEvictionMarker(action: () => Promise<void>) {
|
|
197
|
-
try {
|
|
198
|
-
if (!(await tryAcquireEvictionMarker())) return false
|
|
199
|
-
} catch (error) {
|
|
200
|
-
if ((error as NodeJS.ErrnoException).code === 'EEXIST') return false
|
|
201
|
-
throw error
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
await action()
|
|
206
|
-
} finally {
|
|
207
|
-
await releaseEvictionMarker()
|
|
208
|
-
}
|
|
209
|
-
return true
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Marker loss after a write may mean our record replaced a successor's.
|
|
213
|
-
// Delete only a record still owned by us; a concurrent successor write can
|
|
214
|
-
// then yield zero winners, never two.
|
|
215
|
-
async function relinquishLockAfterMarkerLoss() {
|
|
216
|
-
for (let attempt = 0; attempt < MAX_STEAL_ATTEMPTS; attempt++) {
|
|
217
|
-
if (options.onStep) await options.onStep('relinquish-read')
|
|
218
|
-
let owner: { ownerId?: string } | undefined
|
|
219
|
-
try {
|
|
220
|
-
owner = await readOwner()
|
|
221
|
-
} catch {
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
if (owner?.ownerId !== ownerId) return
|
|
225
|
-
try {
|
|
226
|
-
await rm(lockPath, { recursive: true, force: true })
|
|
227
|
-
return
|
|
228
|
-
} catch {
|
|
229
|
-
await backoff()
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function scheduleRenewal() {
|
|
235
|
-
if (!options.renew || released) return
|
|
236
|
-
const intervalMs =
|
|
237
|
-
options.renewIntervalMs ?? Math.max(1_000, Math.floor(options.ttlMs / 3))
|
|
238
|
-
renewTimer = setRefreshLockRenewalTimeout(() => {
|
|
239
|
-
const renewal = (async () => {
|
|
240
|
-
let shouldReschedule = !released
|
|
241
|
-
try {
|
|
242
|
-
const markerAcquired = await withEvictionMarker(async () => {
|
|
243
|
-
const owner = await readOwner()
|
|
244
|
-
const currentNow = now()
|
|
245
|
-
if (released || owner?.ownerId !== ownerId) {
|
|
246
|
-
shouldReschedule = false
|
|
247
|
-
return
|
|
248
|
-
}
|
|
249
|
-
// An expired lease is no longer ours to extend; a contender may
|
|
250
|
-
// already be eligible to acquire it.
|
|
251
|
-
if (Number(owner?.expiresAt) <= currentNow) {
|
|
252
|
-
shouldReschedule = false
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
if (options.onStep) await options.onStep('renewal-owner-confirmed')
|
|
256
|
-
if (released) {
|
|
257
|
-
shouldReschedule = false
|
|
258
|
-
return
|
|
259
|
-
}
|
|
260
|
-
if (!(await ownsEvictionMarker())) return
|
|
261
|
-
if (options.onStep) await options.onStep('renewal-write-fenced')
|
|
262
|
-
if (released) {
|
|
263
|
-
shouldReschedule = false
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
if (!(await ownsEvictionMarker())) return
|
|
267
|
-
if (options.onStep) await options.onStep('renewal-write-ready')
|
|
268
|
-
await writeOwner()
|
|
269
|
-
if (!(await ownsEvictionMarker())) {
|
|
270
|
-
// Marker read errors fail closed: prompt relinquish avoids ambiguity
|
|
271
|
-
// instead of waiting for TTL; both outcomes keep zero or one winner.
|
|
272
|
-
shouldReschedule = false
|
|
273
|
-
await relinquishLockAfterMarkerLoss()
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
})
|
|
277
|
-
if (!markerAcquired && options.onStep) {
|
|
278
|
-
await options.onStep('renewal-marker-unavailable')
|
|
279
|
-
}
|
|
280
|
-
} catch {
|
|
281
|
-
// Transient marker and filesystem failures retry on the next interval.
|
|
282
|
-
} finally {
|
|
283
|
-
if (options.onStep) {
|
|
284
|
-
try {
|
|
285
|
-
await options.onStep('renewal-finished')
|
|
286
|
-
} catch {
|
|
287
|
-
// Test seams must not turn an otherwise-safe renewal into a rejection.
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (shouldReschedule && !released) scheduleRenewal()
|
|
291
|
-
}
|
|
292
|
-
})()
|
|
293
|
-
renewalInFlight = renewal
|
|
294
|
-
void renewal.finally(() => {
|
|
295
|
-
if (renewalInFlight === renewal) renewalInFlight = null
|
|
296
|
-
})
|
|
297
|
-
}, intervalMs)
|
|
298
|
-
if ('unref' in renewTimer) renewTimer.unref()
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
let acquired = await tryAcquire()
|
|
302
|
-
if (!acquired) {
|
|
303
|
-
for (let attempt = 0; attempt < MAX_STEAL_ATTEMPTS; attempt++) {
|
|
304
|
-
acquired = await tryAcquire()
|
|
305
|
-
if (acquired) break
|
|
306
|
-
if (await lockIsLive()) return null
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
if (!(await tryAcquireEvictionMarker())) {
|
|
310
|
-
await backoff()
|
|
311
|
-
continue
|
|
312
|
-
}
|
|
313
|
-
} catch (evictError) {
|
|
314
|
-
const code = (evictError as NodeJS.ErrnoException).code
|
|
315
|
-
if (code !== 'EEXIST') throw evictError
|
|
316
|
-
|
|
317
|
-
const recovered = await recoverStaleEvictionMarker()
|
|
318
|
-
if (recovered === 'fresh') return null
|
|
319
|
-
await backoff()
|
|
320
|
-
continue
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
if (await lockIsLive()) return null
|
|
325
|
-
// Fence check 1: verify we still own the marker before acting on the
|
|
326
|
-
// stale-lock-confirmed decision.
|
|
327
|
-
if (!(await ownsEvictionMarker())) return null
|
|
328
|
-
if (options.onStep) await options.onStep('stale-lock-confirmed')
|
|
329
|
-
// Fence check 2: re-verify ownership after the seam (another contender
|
|
330
|
-
// may have renamed our fresh marker while we were paused here).
|
|
331
|
-
if (!(await ownsEvictionMarker())) return null
|
|
332
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => {})
|
|
333
|
-
// Fence check 3: re-verify ownership after removing the stale lock.
|
|
334
|
-
if (!(await ownsEvictionMarker())) return null
|
|
335
|
-
acquired = await tryAcquire()
|
|
336
|
-
if (!acquired) return null
|
|
337
|
-
// Fence check 4: re-verify ownership after acquiring the lock. If the
|
|
338
|
-
// marker was stolen between tryAcquire and this check, release the
|
|
339
|
-
// just-acquired lock and return null (fail-closed).
|
|
340
|
-
if (!(await ownsEvictionMarker())) {
|
|
341
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => {})
|
|
342
|
-
acquired = false
|
|
343
|
-
return null
|
|
344
|
-
}
|
|
345
|
-
break
|
|
346
|
-
} finally {
|
|
347
|
-
await releaseEvictionMarker()
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!acquired) return null
|
|
353
|
-
|
|
354
|
-
scheduleRenewal()
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
release: async () => {
|
|
358
|
-
released = true
|
|
359
|
-
if (renewTimer) {
|
|
360
|
-
clearRefreshLockRenewalTimeout(renewTimer)
|
|
361
|
-
renewTimer = null
|
|
362
|
-
}
|
|
363
|
-
await renewalInFlight
|
|
364
|
-
for (let attempt = 0; attempt < MAX_STEAL_ATTEMPTS; attempt++) {
|
|
365
|
-
try {
|
|
366
|
-
const markerAcquired = await withEvictionMarker(async () => {
|
|
367
|
-
const owner = await readOwner()
|
|
368
|
-
if (owner?.ownerId !== ownerId) return
|
|
369
|
-
if (options.onStep) await options.onStep('release-owner-confirmed')
|
|
370
|
-
if (!(await ownsEvictionMarker())) return
|
|
371
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => {})
|
|
372
|
-
})
|
|
373
|
-
if (markerAcquired) return
|
|
374
|
-
await recoverStaleEvictionMarker()
|
|
375
|
-
} catch {
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
await backoff()
|
|
379
|
-
}
|
|
380
|
-
// Do not delete by pathname without the marker: bounded retries leave the
|
|
381
|
-
// lease to expire rather than risking removal of a successor's lock.
|
|
382
|
-
},
|
|
383
|
-
}
|
|
384
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { execFileSync as defaultExecFileSync } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
type BrowserExec = (
|
|
4
|
-
file: string,
|
|
5
|
-
args: string[],
|
|
6
|
-
options: { stdio: 'ignore'; timeout: number },
|
|
7
|
-
) => unknown
|
|
8
|
-
|
|
9
|
-
export function openUrl(
|
|
10
|
-
url: string,
|
|
11
|
-
platform: NodeJS.Platform = process.platform,
|
|
12
|
-
execFileSync: BrowserExec = defaultExecFileSync,
|
|
13
|
-
) {
|
|
14
|
-
try {
|
|
15
|
-
if (platform === 'win32') {
|
|
16
|
-
execFileSync('cmd', ['/c', 'start', '', url], {
|
|
17
|
-
stdio: 'ignore',
|
|
18
|
-
timeout: 3000,
|
|
19
|
-
})
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
execFileSync(platform === 'darwin' ? 'open' : 'xdg-open', [url], {
|
|
24
|
-
stdio: 'ignore',
|
|
25
|
-
timeout: 3000,
|
|
26
|
-
})
|
|
27
|
-
} catch {
|
|
28
|
-
// Browser launch is best effort; the printed URL remains the fallback.
|
|
29
|
-
}
|
|
30
|
-
}
|