@cortexkit/antigravity-auth-core 3.0.21 → 3.0.22

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.
@@ -244,8 +244,8 @@ export async function acquireRefreshFileLockWithClock(options) {
244
244
  }
245
245
  catch (readError) {
246
246
  // The holder can release after exclusive creation reports a collision.
247
- // Only absence permits another acquisition attempt; security and sharing
248
- // refusals must still escape rather than being treated as contention.
247
+ // Only absence permits acquisition to continue here. Sharing failures
248
+ // escape to withLock's deadline-bounded retry, never to stale eviction.
249
249
  if (readError.code !== 'ENOENT')
250
250
  throw readError;
251
251
  }
@@ -1,3 +1,5 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { PrivateFileError, windowsPrivateFiles } from './private-files.js';
1
3
  import { acquireRefreshFileLock } from './refresh-file-lock.js';
2
4
  export function lockPathFor(target, name) {
3
5
  return `${target}.${name}.lock`;
@@ -18,20 +20,42 @@ export class LockOwnershipError extends Error {
18
20
  this.details = details;
19
21
  }
20
22
  }
23
+ function isChangingLockRead(error, target, name) {
24
+ if (!windowsPrivateFiles ||
25
+ !(error instanceof PrivateFileError) ||
26
+ error.reason !== 'io-error' ||
27
+ (error.win32Code !== 32 && error.win32Code !== 303) ||
28
+ (error.stage !== 'read' && error.stage !== 'verify'))
29
+ return false;
30
+ const path = resolve(lockPathFor(target, name));
31
+ return error.path === path || error.path === join(path, 'owner.json');
32
+ }
21
33
  export async function withLock(target, options, fn) {
22
34
  const started = performance.now();
23
35
  for (;;) {
24
36
  options.signal?.throwIfAborted();
25
- const lock = await acquireRefreshFileLock({
26
- path: target,
27
- name: options.name,
28
- ttlMs: options.ttlMs,
29
- renew: options.renew ?? false,
30
- ...(options.onStep ? { onStep: options.onStep } : {}),
31
- ...(options.onRenewalTimer
32
- ? { onRenewalTimer: options.onRenewalTimer }
33
- : {}),
34
- });
37
+ let lock;
38
+ let changingReadError;
39
+ try {
40
+ lock = await acquireRefreshFileLock({
41
+ path: target,
42
+ name: options.name,
43
+ ttlMs: options.ttlMs,
44
+ renew: options.renew ?? false,
45
+ ...(options.onStep ? { onStep: options.onStep } : {}),
46
+ ...(options.onRenewalTimer
47
+ ? { onRenewalTimer: options.onRenewalTimer }
48
+ : {}),
49
+ });
50
+ }
51
+ catch (error) {
52
+ if (!isChangingLockRead(error, target, options.name))
53
+ throw error;
54
+ // Sharing violations (32) and delete-pending opens (303) do not reveal
55
+ // ownership or expiry. Retry exclusive creation, never infer a stale lock.
56
+ lock = null;
57
+ changingReadError = error;
58
+ }
35
59
  if (lock) {
36
60
  try {
37
61
  options.signal?.throwIfAborted();
@@ -43,6 +67,9 @@ export async function withLock(target, options, fn) {
43
67
  }
44
68
  const remaining = options.timeoutMs - (performance.now() - started);
45
69
  if (remaining <= 0) {
70
+ // Keep persistent native IO failures distinguishable from a live holder.
71
+ if (changingReadError)
72
+ throw changingReadError;
46
73
  throw new LockContentionError({
47
74
  target,
48
75
  name: options.name,
@@ -30,6 +30,8 @@ export interface QuotaTextOptions {
30
30
  }
31
31
  export interface QuotaWindowPart {
32
32
  kind: 'window' | 'budget';
33
+ /** Unknown windows have no dated percentage; budgets retain their own wording. */
34
+ reading: 'known' | 'unknown';
33
35
  /** The display name of a quota window, or `credits` for the budget. */
34
36
  name: string;
35
37
  /**
@@ -45,7 +47,8 @@ export interface QuotaWindowPart {
45
47
  remainingPercent?: number;
46
48
  /** A bare relative reset span such as `4d`, only when the reset is future. */
47
49
  resetIn?: string;
48
- checkedAt: number;
50
+ /** The reading time, omitted for a window with no reading. */
51
+ checkedAt?: number;
49
52
  /** Whether the reset followed this reading and has arrived by `now`. */
50
53
  resetSinceReading: boolean;
51
54
  }
@@ -64,9 +67,11 @@ export interface QuotaParts {
64
67
  */
65
68
  export declare function quotaWindowName(limit: Pick<ProjectedLimit, 'scope' | 'label' | 'windowMinutes'>): string;
66
69
  /**
67
- * Structured quota wording for layouts that need one row per window. The
68
- * windows use the same filtering and order as `quotaTextParts`; future resets
69
- * and the optional age keep the shared formatter's wording.
70
+ * Structured quota wording for layouts that need one row per window.
71
+ * Windows without a dated percentage remain as explicit unknown parts. Limits
72
+ * marked absent (not reported) or retired (no longer tracked) remain omitted.
73
+ * Reported windows, future resets and the optional age keep the shared
74
+ * formatter's wording and order.
70
75
  */
71
76
  export declare function quotaWindowParts(quota: ProjectedQuota | undefined, options: QuotaTextOptions): QuotaParts;
72
77
  /**
@@ -5,10 +5,10 @@
5
5
  //
6
6
  // The rules: a window is named by its length (`5h`, `7d`), or, for a window
7
7
  // that binds only one model family, by the label the plugin stored it under.
8
- // Every figure is "% left". A window the provider did not report (an absence
9
- // record or a retirement tombstone) is left out rather than written as "not
10
- // reported". A reset is a short time from now (`resets 2h`). A reading older
11
- // than fifteen minutes says how old it is (`checked 3h ago`).
8
+ // Every reported window figure is "% left". A window the provider did not
9
+ // report (an absence record or a retirement tombstone) is left out rather than
10
+ // written as "not reported". A reset is a short time from now (`resets 2h`).
11
+ // A reading older than fifteen minutes says how old it is (`checked 3h ago`).
12
12
  //
13
13
  // Like the rest of this module it reads no clock: `now` is a parameter.
14
14
  import { ALL_SCOPE } from './map.js';
@@ -97,12 +97,14 @@ function displayOrder(left, right) {
97
97
  return left.label < right.label ? -1 : left.label > right.label ? 1 : 0;
98
98
  }
99
99
  /**
100
- * Structured quota wording for layouts that need one row per window. The
101
- * windows use the same filtering and order as `quotaTextParts`; future resets
102
- * and the optional age keep the shared formatter's wording.
100
+ * Structured quota wording for layouts that need one row per window.
101
+ * Windows without a dated percentage remain as explicit unknown parts. Limits
102
+ * marked absent (not reported) or retired (no longer tracked) remain omitted.
103
+ * Reported windows, future resets and the optional age keep the shared
104
+ * formatter's wording and order.
103
105
  */
104
106
  export function quotaWindowParts(quota, options) {
105
- const { windows, age } = scopedWindowParts(quota, options);
107
+ const { windows, age } = scopedWindowParts(quota, options, true);
106
108
  const parts = windows.map(({ part }) => part);
107
109
  return age === undefined ? { windows: parts } : { windows: parts, age };
108
110
  }
@@ -125,34 +127,47 @@ function withShortNames(windows) {
125
127
  }
126
128
  return windows;
127
129
  }
130
+ function hasWindowReading(limit) {
131
+ return (Number.isFinite(limit.remainingPercent) && Number.isFinite(limit.checkedAt));
132
+ }
128
133
  /**
129
134
  * `quotaWindowParts` with each window's scope kept beside it. The narrow form
130
135
  * needs the scope to name and pick model-scoped windows; the public parts do
131
- * not carry it, so their shape stays as it was.
136
+ * not carry the scope.
132
137
  */
133
- function scopedWindowParts(quota, options) {
138
+ function scopedWindowParts(quota, options, includeUnknown = false) {
134
139
  if (!quota)
135
140
  return { windows: [] };
136
141
  const { now } = options;
137
142
  const readings = quota.limits
138
- .filter((limit) => limit.kind === 'reading' && limit.remainingPercent !== undefined)
143
+ .filter((limit) => limit.kind === 'reading' && (includeUnknown || hasWindowReading(limit)))
139
144
  .sort(displayOrder);
140
145
  const windows = readings.map((limit) => {
141
- const remainingPercent = clampPercent(limit.remainingPercent ?? 0);
142
- return {
143
- scope: limit.scope,
144
- ...(limit.windowMinutes === undefined
145
- ? {}
146
- : { windowMinutes: limit.windowMinutes }),
147
- part: {
146
+ const part = hasWindowReading(limit)
147
+ ? {
148
148
  kind: 'window',
149
+ reading: 'known',
149
150
  name: quotaWindowName(limit),
150
151
  short: '',
151
- figure: percentLeft(limit.remainingPercent ?? 0),
152
- remainingPercent,
152
+ figure: percentLeft(limit.remainingPercent),
153
+ remainingPercent: clampPercent(limit.remainingPercent),
153
154
  checkedAt: limit.checkedAt,
154
155
  ...resetDetails(limit.resetsAt, limit.checkedAt, now),
155
- },
156
+ }
157
+ : {
158
+ kind: 'window',
159
+ reading: 'unknown',
160
+ name: quotaWindowName(limit),
161
+ short: '',
162
+ figure: 'no reading',
163
+ resetSinceReading: false,
164
+ };
165
+ return {
166
+ scope: limit.scope,
167
+ ...(limit.windowMinutes === undefined
168
+ ? {}
169
+ : { windowMinutes: limit.windowMinutes }),
170
+ part,
156
171
  };
157
172
  });
158
173
  const budget = quota.budget;
@@ -168,6 +183,7 @@ function scopedWindowParts(quota, options) {
168
183
  spent: budget.reached,
169
184
  part: {
170
185
  kind: 'budget',
186
+ reading: 'known',
171
187
  name: 'credits',
172
188
  short: '',
173
189
  figure,
@@ -180,8 +196,11 @@ function scopedWindowParts(quota, options) {
180
196
  });
181
197
  }
182
198
  withShortNames(windows);
183
- if (windows.length > 0) {
184
- const age = now - Math.min(...windows.map(({ part }) => part.checkedAt));
199
+ const readingTimes = windows.flatMap(({ part }) => part.reading === 'known' && part.checkedAt !== undefined
200
+ ? [part.checkedAt]
201
+ : []);
202
+ if (readingTimes.length > 0) {
203
+ const age = now - Math.min(...readingTimes);
185
204
  if (age > (options.staleAfterMs ?? QUOTA_STALE_AFTER_MS)) {
186
205
  return { windows, age: `checked ${shortSpan(age)} ago` };
187
206
  }
@@ -357,13 +376,13 @@ function narrowTextParts(quota, options) {
357
376
  export function quotaTextParts(quota, options) {
358
377
  if (options.form === 'narrow')
359
378
  return narrowTextParts(quota, options);
360
- const parts = quotaWindowParts(quota, options);
379
+ const { windows, age } = scopedWindowParts(quota, options);
361
380
  const full = (options.form ?? 'full') === 'full';
362
- const text = parts.windows.map((part) => {
381
+ const text = windows.map(({ part }) => {
363
382
  const reset = full && part.resetIn ? `, resets ${part.resetIn}` : '';
364
383
  return `${part.kind === 'budget' ? '' : `${part.name} `}${part.figure}${reset}`;
365
384
  });
366
- return parts.age === undefined ? text : [...text, parts.age];
385
+ return age === undefined ? text : [...text, age];
367
386
  }
368
387
  /**
369
388
  * A row's quota as one line: `5h 58% left · 7d 90% left` in the compact
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "package": "@cortexkit/common-auth",
4
- "version": "0.32.1",
4
+ "version": "0.33.0",
5
5
  "artifactStatus": "published",
6
6
  "publicRoots": {
7
7
  "./store": {
@@ -37,15 +37,15 @@
37
37
  "import": "./dist/pi-slot/index.js"
38
38
  }
39
39
  },
40
- "input": "tools/common-auth-build/inputs/cortexkit-common-auth-0.32.1.tgz",
41
- "tarballSha256": "d25d2eb670bc5ac48e75bf31cd633bb8b56fd807bf4467d69dbf62a00e1e6717",
42
- "sri": "sha512-Y3SmHTnkDjdvo8d6MsQTVr+GlR3q6Ro8HYDqEFuotSUbrv8GwJvjRkYraN8ahEAT8uRHFmfyWGuogfYLzz65DA==",
40
+ "input": "tools/common-auth-build/inputs/cortexkit-common-auth-0.33.0.tgz",
41
+ "tarballSha256": "bba9f56f7fb353c9c712113722c7c9361f0e13e930c73f57314185b13032a24c",
42
+ "sri": "sha512-cbbyqXTK1TYQZNHb2xA/ueZGQsBvAX6H9hTlQcZZxM7SR/u7tsscyBAWw38ViXl1n/gwkqgsvs2Vbs6cZ0hOrw==",
43
43
  "canonicalFiles": 147,
44
- "canonicalBytes": 1000963,
44
+ "canonicalBytes": 1003209,
45
45
  "generator": {
46
46
  "path": "packages/opencode/scripts/embed-common-auth.ts",
47
47
  "version": 4,
48
- "sha256": "d0920ba672e4d2a4ab37cd827d0a8098e9253d23678b88d051750373787af65d"
48
+ "sha256": "9f9ebaeb47c9c18874ed832504422c0791551627c34092885cb481c6fe23ef19"
49
49
  },
50
50
  "files": [
51
51
  {
@@ -603,9 +603,9 @@
603
603
  {
604
604
  "source": "package/dist/fs/refresh-file-lock.js",
605
605
  "output": "fs/refresh-file-lock.js",
606
- "bytes": 37832,
607
- "sourceSha256": "07b261b409c5c6cc9b35cb732df44486d372413ce6ae078be4b211b9b5a5c1c7",
608
- "outputSha256": "07b261b409c5c6cc9b35cb732df44486d372413ce6ae078be4b211b9b5a5c1c7",
606
+ "bytes": 37831,
607
+ "sourceSha256": "e0fdcc7a45548f3c88ea49bbe000f6e4022fb9c219f0510aa934492adb72a505",
608
+ "outputSha256": "e0fdcc7a45548f3c88ea49bbe000f6e4022fb9c219f0510aa934492adb72a505",
609
609
  "transform": "verbatim"
610
610
  },
611
611
  {
@@ -619,9 +619,9 @@
619
619
  {
620
620
  "source": "package/dist/fs/with-lock.js",
621
621
  "output": "fs/with-lock.js",
622
- "bytes": 1803,
623
- "sourceSha256": "f31ff1935e18563e465ab235202de3932255d3b1fc72b5d0baf6d3502931fc8e",
624
- "outputSha256": "f31ff1935e18563e465ab235202de3932255d3b1fc72b5d0baf6d3502931fc8e",
622
+ "bytes": 3003,
623
+ "sourceSha256": "690fb43c6a0eb6a02eac024b8b58caa39775a37d0064118dab879a0e5ac8c996",
624
+ "outputSha256": "690fb43c6a0eb6a02eac024b8b58caa39775a37d0064118dab879a0e5ac8c996",
625
625
  "transform": "verbatim"
626
626
  },
627
627
  {
@@ -763,17 +763,17 @@
763
763
  {
764
764
  "source": "package/dist/quota/format.d.ts",
765
765
  "output": "quota/format.d.ts",
766
- "bytes": 5507,
767
- "sourceSha256": "7df60af3e135f34e157bf41c05b59052047f1a583ff3f1dabb3ca4e525b8089a",
768
- "outputSha256": "7df60af3e135f34e157bf41c05b59052047f1a583ff3f1dabb3ca4e525b8089a",
766
+ "bytes": 5816,
767
+ "sourceSha256": "1865f4a06b8bd06b3dda73b59a7becc3eb2d13036134c1a34b8b6dce12678f21",
768
+ "outputSha256": "1865f4a06b8bd06b3dda73b59a7becc3eb2d13036134c1a34b8b6dce12678f21",
769
769
  "transform": "verbatim"
770
770
  },
771
771
  {
772
772
  "source": "package/dist/quota/format.js",
773
773
  "output": "quota/format.js",
774
- "bytes": 18143,
775
- "sourceSha256": "e00f3bb6d9da8b13522f02f953387e218f9cc6b62b80821ca081aa06f76d27c6",
776
- "outputSha256": "e00f3bb6d9da8b13522f02f953387e218f9cc6b62b80821ca081aa06f76d27c6",
774
+ "bytes": 18881,
775
+ "sourceSha256": "ffd69317391a225fb8e75550c22ea401480626cde762c52e61001955952ee013",
776
+ "outputSha256": "ffd69317391a225fb8e75550c22ea401480626cde762c52e61001955952ee013",
777
777
  "transform": "verbatim"
778
778
  },
779
779
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/antigravity-auth-core",
3
- "version": "3.0.21",
3
+ "version": "3.0.22",
4
4
  "description": "Harness-agnostic core for Google Antigravity OAuth: transport, fingerprint, request transforms, accounts, quota",
5
5
  "type": "module",
6
6
  "license": "MIT",