@cortexkit/pi-antigravity-auth 3.0.5 → 3.0.7

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { join as join2 } from "node:path";
2
+ import { join } from "node:path";
3
3
  import {
4
4
  ANTIGRAVITY_PROVIDER_ID as ANTIGRAVITY_PROVIDER_ID2,
5
5
  authorizeAntigravity,
@@ -26,7 +26,13 @@ import { createHash, randomBytes } from "node:crypto";
26
26
  import { readFileSync } from "node:fs";
27
27
  import {
28
28
  ANTIGRAVITY_PROVIDER_ID,
29
- enrollmentAuthority
29
+ emptyVaultRosterCause,
30
+ enrollmentAuthority,
31
+ sharedVaultTokenFolder,
32
+ sharedVaultTokenFolderOutcome,
33
+ sharedVaultTokenFolderText,
34
+ VAULT_RECLASSIFY_COMMAND,
35
+ VAULT_STATUS_COMMAND
30
36
  } from "@cortexkit/antigravity-auth-core";
31
37
  import { writeJsonAtomic } from "@cortexkit/antigravity-auth-core/atomic-write";
32
38
  var PI_ANTIGRAVITY_PROVIDER_ID = ANTIGRAVITY_PROVIDER_ID;
@@ -62,16 +68,19 @@ function readPiVaultMode(path) {
62
68
  } catch (error) {
63
69
  if (error?.code === "ENOENT")
64
70
  return { ok: true, record: LOCAL_MODE };
65
- return { ok: false, reason: "the vault mode file could not be read" };
71
+ return { ok: false, reason: "its vault settings file could not be read" };
66
72
  }
67
73
  let raw;
68
74
  try {
69
75
  raw = JSON.parse(text);
70
76
  } catch {
71
- return { ok: false, reason: "the vault mode file is not valid JSON" };
77
+ return { ok: false, reason: "its vault settings file is damaged" };
72
78
  }
73
79
  const record = parseModeRecord(raw);
74
- return record ? { ok: true, record } : { ok: false, reason: "the vault mode file has an unknown shape" };
80
+ return record ? { ok: true, record } : {
81
+ ok: false,
82
+ reason: "its vault settings file holds settings it does not understand"
83
+ };
75
84
  }
76
85
  async function writePiVaultMode(path, record) {
77
86
  const value = record.routeId === void 0 ? { version: 1, mode: record.mode } : { version: 1, mode: record.mode, routeId: record.routeId };
@@ -177,7 +186,7 @@ var PiVaultSetup = class {
177
186
  );
178
187
  if (enrollment.state !== "approved")
179
188
  throw new PiVaultSetupError(
180
- "Pi is not enrolled with the vault yet. Request access and wait for the operator to approve it first."
189
+ "Pi doesn't have vault access yet. Choose Request vault access and wait for the vault operator to approve it."
181
190
  );
182
191
  if (routeId !== void 0 && routeId.trim() === "")
183
192
  throw new PiVaultSetupError("Choose a vault account to use.");
@@ -194,7 +203,7 @@ var PiVaultSetup = class {
194
203
  this.#options.claustrum
195
204
  ) !== "local") {
196
205
  throw new PiVaultSetupError(
197
- "Disconnect vault enrollment before using local accounts."
206
+ "Disconnect from the vault before switching to Pi's own login."
198
207
  );
199
208
  }
200
209
  await writePiVaultMode(this.#options.modeFile, LOCAL_MODE);
@@ -213,7 +222,7 @@ var PiVaultSetup = class {
213
222
  );
214
223
  if (result === "busy")
215
224
  throw new PiVaultSetupError(
216
- "Vault enrollment is busy; disconnect did not change it."
225
+ "Another vault setup step is running, so Pi is still connected to the vault. Try again in a moment."
217
226
  );
218
227
  await writePiVaultMode(this.#options.modeFile, LOCAL_MODE);
219
228
  await this.#options.onModeChanged?.(LOCAL_MODE);
@@ -222,43 +231,69 @@ var PiVaultSetup = class {
222
231
  function describeEnrollment(status) {
223
232
  switch (status.state) {
224
233
  case "idle":
225
- return "not requested";
234
+ return "Vault access: not requested yet.";
226
235
  case "pending":
227
- return status.retryCode ? `waiting for operator approval (retrying: ${status.retryCode})` : "waiting for operator approval";
236
+ return status.retryCode ? `Vault access: the vault can't take Pi's request right now; Pi asks again the next time it checks (reference: ${status.retryCode}).` : "Vault access: waiting for the vault operator to approve this request.";
228
237
  case "approved":
229
- return `approved as ${status.approvedName ?? status.proposedName}`;
238
+ return "Vault access: approved by the vault operator.";
230
239
  case "denied":
231
- return "denied by the operator";
240
+ return "Vault access: the vault operator denied the request. Choose Request vault access to ask again.";
232
241
  case "blocked":
233
- return `blocked (${status.code})`;
242
+ if (status.code === "pending_exists")
243
+ return "Vault access: the vault turned down a new request because an earlier request from Pi is still waiting.";
244
+ if (status.code === "missing_token")
245
+ return "Vault access: approved, but Pi's vault key is missing from this computer. Disconnect from the vault, then request vault access again.";
246
+ if (status.code === "unreadable-state")
247
+ return "Vault access: Pi can't read its record of the vault request. Disconnect from the vault, then request vault access again.";
248
+ return `Vault access: the vault ended this request (reference: ${status.code}). Choose Request vault access to start a new one.`;
234
249
  case "unavailable":
235
- return `vault unavailable (${status.code})`;
250
+ return `Vault access: Pi can't reach the vault right now. Try again later (reference: ${status.code}).`;
236
251
  case "busy":
237
- return "another setup step is running";
252
+ return "Vault access: another vault setup step is running. Try again in a moment.";
238
253
  }
239
254
  }
240
255
  var PI_VAULT_GRANT_COMMAND = `ck auth grant --principal enrolled:${PI_ANTIGRAVITY_ENROLLMENT_NAME} --selector-kind category --selector antigravity-native --operation read`;
241
256
  function piPendingEnrollmentGuidance(requestId) {
242
257
  return [
243
258
  "Next, the vault operator runs these outside Pi:",
244
- requestId === void 0 ? "1. Find the request ID with ck auth enroll list, then approve it: ck auth enroll approve --request-id <id>" : `1. Approve the request: ck auth enroll approve --request-id ${requestId}`,
259
+ requestId === void 0 ? `1. Find the request from ${PI_ANTIGRAVITY_ENROLLMENT_NAME} with ck auth enroll list, then approve it: ck auth enroll approve --request-id <id>` : `1. Approve the request: ck auth enroll approve --request-id ${requestId}`,
245
260
  `2. Grant read access: ${PI_VAULT_GRANT_COMMAND}`,
246
- "Both are needed: an approval without the grant serves Pi no accounts.",
247
- "3. Pi switches to the vault once it sees the approval; Request vault access checks again."
261
+ "Both are needed: without the grant, Pi can see no accounts in the vault.",
262
+ "3. Pi switches to the vault once it sees the approval. Choose Request vault access to check again."
248
263
  ];
249
264
  }
250
265
  function piPendingExistsGuidance() {
251
266
  return [
252
- `An earlier request for ${PI_ANTIGRAVITY_ENROLLMENT_NAME} is still pending in the vault, possibly from an enrollment forgotten here.`,
267
+ "The earlier request may come from a vault setup that was disconnected here before the operator answered it.",
253
268
  "It expires 15 minutes after it was made; request vault access again after that.",
254
- "Or the vault operator clears it now: find its ID with ck auth enroll list, run ck auth enroll deny --request-id <id>, then request vault access again here."
269
+ `Or the vault operator clears it now: find the request from ${PI_ANTIGRAVITY_ENROLLMENT_NAME} with ck auth enroll list, run ck auth enroll deny --request-id <id>, then request vault access again here.`
255
270
  ];
256
271
  }
257
- function piNoVaultAccountsGuidance() {
258
- return [
259
- `Enrollment is approved, but the vault serves ${PI_ANTIGRAVITY_ENROLLMENT_NAME} no Antigravity accounts.`,
260
- `The vault operator grants read access with: ${PI_VAULT_GRANT_COMMAND}`
261
- ];
272
+ function piNoVaultAccountsGuidance(reach) {
273
+ return piEmptyRosterGuidance(emptyVaultRosterCause(reach));
274
+ }
275
+ function piEmptyRosterGuidance(cause) {
276
+ switch (cause) {
277
+ case "no-grant":
278
+ return [
279
+ "The vault operator approved Pi. The vault has not granted Pi read access.",
280
+ `The vault operator grants it with: ${PI_VAULT_GRANT_COMMAND}`
281
+ ];
282
+ case "grant-reaches-nothing":
283
+ return [
284
+ "The vault operator approved Pi. Pi has read access, but it reaches no Antigravity login in the vault.",
285
+ `The vault operator updates the vault's logins with: ${VAULT_RECLASSIFY_COMMAND}`,
286
+ `Then they check them with: ${VAULT_STATUS_COMMAND}`
287
+ ];
288
+ case "unknown":
289
+ return [
290
+ "The vault operator approved Pi, but Pi can't see any Antigravity accounts in the vault yet.",
291
+ `If the operator has not granted Pi read access yet, they run: ${PI_VAULT_GRANT_COMMAND}`
292
+ ];
293
+ }
294
+ }
295
+ function piEmptyVaultRosterRefusal(cause) {
296
+ return cause === "unknown" ? void 0 : piEmptyRosterGuidance(cause).join("\n");
262
297
  }
263
298
  function enrollmentGuidance(status) {
264
299
  if (status.state === "pending")
@@ -267,28 +302,20 @@ function enrollmentGuidance(status) {
267
302
  return piPendingExistsGuidance();
268
303
  return [];
269
304
  }
270
- function describeHostSlot(slot) {
271
- switch (slot) {
272
- case "login":
273
- return "signed in with a Pi login";
274
- case "placeholder":
275
- return "holds the vault placeholder";
276
- case "empty":
277
- return "empty";
305
+ function describeSource(status) {
306
+ switch (status.mode) {
307
+ case "custody":
308
+ return "Using the vault for Antigravity accounts; Pi's own login is set aside.";
309
+ case "local":
310
+ return status.hostSlot === "login" ? "Using Pi's own login (the account you signed in with /login), not the vault." : status.hostSlot === "dormant" ? "Using Pi's own login, not the vault." : "Using Pi's own login, not the vault, but you are not signed in. Run /login to sign in.";
311
+ case "unreadable":
312
+ return status.modeProblem === void 0 ? "Pi can't tell whether to use the vault or its own login, so it sends no Antigravity requests." : `Pi can't tell whether to use the vault or its own login (${status.modeProblem}), so it sends no Antigravity requests.`;
278
313
  }
279
314
  }
280
315
  function formatPiVaultSetupStatus(status) {
281
- const lines = [
282
- `Mode: ${status.mode === "custody" ? "vault (custody)" : status.mode === "local" ? "local Pi login" : `unreadable (${status.modeProblem ?? "unknown problem"})`}`,
283
- `Enrollment (${PI_ANTIGRAVITY_ENROLLMENT_NAME}): ${describeEnrollment(status.enrollment)}`,
284
- status.hostSlot === "dormant" ? "Pi auth slot: dormant (not read)" : `Pi auth slot: ${describeHostSlot(status.hostSlot)}`
285
- ];
286
- if (status.selectedRouteId !== void 0)
287
- lines.push("Vault account: a specific account is chosen (marked below)");
288
- if (status.mode === "local" && status.hostSlot === "placeholder")
289
- lines.push(
290
- "Conflict: local mode has no login to serve. Run /login or switch to the vault."
291
- );
316
+ const lines = [describeSource(status), describeEnrollment(status.enrollment)];
317
+ if (status.mode === "custody" && status.selectedRouteId !== void 0)
318
+ lines.push('Pi uses the vault account marked "in use by Pi" below.');
292
319
  lines.push(...enrollmentGuidance(status.enrollment));
293
320
  return lines;
294
321
  }
@@ -296,6 +323,9 @@ var ITEM_ID_SALT = randomBytes(16);
296
323
  function vaultAccountName(listed, account) {
297
324
  return `Vault account ${listed.indexOf(account) + 1}`;
298
325
  }
326
+ var VAULT_ACCOUNT_IN_USE = "in use by Pi";
327
+ var VAULT_ACCOUNT_AVAILABLE = "available";
328
+ var VAULT_ACCOUNT_TURNED_OFF = "turned off for Pi on this computer";
299
329
  function opaqueItemId(routeId) {
300
330
  return `vault-${createHash("sha256").update(ITEM_ID_SALT).update(routeId).digest("hex").slice(0, 16)}`;
301
331
  }
@@ -307,6 +337,11 @@ function refused(error) {
307
337
  return { ok: false, text: error.message, code: "vault-refused" };
308
338
  throw error;
309
339
  }
340
+ function sharedFolderRefusal(error) {
341
+ const outcome = sharedVaultTokenFolderOutcome(error);
342
+ if (outcome) return outcome;
343
+ throw error;
344
+ }
310
345
  async function waitForApproval(setup, wait = {}) {
311
346
  const sleep = wait.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
312
347
  const interval = wait.pollIntervalMs ?? 2e3;
@@ -327,13 +362,13 @@ function createPiVaultSection(options) {
327
362
  const port = options.accounts?.();
328
363
  if (!port) return [];
329
364
  try {
330
- return (await port.list()).length === 0 ? piNoVaultAccountsGuidance() : [];
365
+ return (await port.list()).length === 0 ? piNoVaultAccountsGuidance(port.grantReach()) : [];
331
366
  } catch {
332
367
  return [];
333
368
  }
334
369
  };
335
370
  const stepText = async (step, suffix = "") => [
336
- `Enrollment: ${describeEnrollment(step)}${suffix}`,
371
+ `${describeEnrollment(step)}${suffix}`,
337
372
  ...enrollmentGuidance(step),
338
373
  ...step.state === "approved" ? await approvedRosterGuidance() : []
339
374
  ].join("\n");
@@ -356,48 +391,51 @@ function createPiVaultSection(options) {
356
391
  items.push({
357
392
  id: opaqueItemId(account.routeId),
358
393
  label: name,
359
- detail: account.declined ? "declined on this host" : serving ? "serving Pi" : "available",
394
+ detail: account.declined ? VAULT_ACCOUNT_TURNED_OFF : serving ? VAULT_ACCOUNT_IN_USE : VAULT_ACCOUNT_AVAILABLE,
360
395
  actions: account.declined ? [
361
396
  {
362
397
  id: "accept",
363
- label: "Accept on this host",
398
+ label: "Turn back on for Pi",
364
399
  run: async () => {
365
400
  await port.accept(account.routeId);
366
- return `${name} is accepted on this host.`;
401
+ return `Pi can use ${name} again.`;
367
402
  }
368
403
  }
369
404
  ] : [
370
405
  {
371
406
  id: "use",
372
- label: "Serve Pi from this account",
407
+ label: "Use this account",
373
408
  run: async () => {
374
409
  try {
375
410
  await setup.useVault(account.routeId);
376
411
  } catch (error) {
377
412
  return refused(error);
378
413
  }
379
- return `Pi now serves Antigravity from ${name}.`;
414
+ return `Pi now uses ${name} for Antigravity.`;
380
415
  }
381
416
  },
382
417
  {
383
418
  id: "decline",
384
- label: "Decline on this host",
385
- description: "It stays in the vault and in this list, and is never sent from this host until accepted again.",
419
+ label: "Turn off for Pi",
420
+ description: "The account stays in the vault and in this list, but Pi on this computer never uses it until you turn it back on.",
386
421
  run: async () => {
387
422
  await port.decline(account.routeId);
388
- return `${name} is declined on this host.`;
423
+ return `Pi on this computer won't use ${name} until you turn it back on.`;
389
424
  }
390
425
  }
391
426
  ]
392
427
  });
393
428
  }
394
- if (listed.length === 0) lines.push(...piNoVaultAccountsGuidance());
429
+ if (listed.length === 0)
430
+ lines.push(...piNoVaultAccountsGuidance(port.grantReach()));
395
431
  else if (status.mode !== "custody")
396
432
  lines.push(
397
- "Next: choose Serve Pi from the vault to serve Pi from these accounts."
433
+ "Next: choose Use the vault to have Pi use these accounts."
398
434
  );
399
435
  } catch (error) {
400
- lines.push(`Vault accounts unavailable: ${errorText(error)}`);
436
+ lines.push(
437
+ `Pi can't load the vault accounts right now: ${errorText(error)}`
438
+ );
401
439
  }
402
440
  }
403
441
  const actions = [];
@@ -405,9 +443,14 @@ function createPiVaultSection(options) {
405
443
  actions.push({
406
444
  id: "request-access",
407
445
  label: "Request vault access",
408
- description: `Asks the vault to enroll Pi as ${PI_ANTIGRAVITY_ENROLLMENT_NAME}; the operator approves it in the vault.`,
446
+ description: "Asks the vault operator to let Pi use the vault's Antigravity accounts.",
409
447
  run: async ({ invocation }) => {
410
- const step = await setup.connectStep();
448
+ let step;
449
+ try {
450
+ step = await setup.connectStep();
451
+ } catch (error) {
452
+ return sharedFolderRefusal(error);
453
+ }
411
454
  if (step.state !== "pending" && step.state !== "busy")
412
455
  return {
413
456
  ok: step.state === "approved",
@@ -422,8 +465,9 @@ function createPiVaultSection(options) {
422
465
  final.state === "approved" ? "info" : "warning"
423
466
  );
424
467
  }).catch((error) => {
468
+ const folder = sharedVaultTokenFolder(error);
425
469
  invocation.notify(
426
- `Requesting vault access failed: ${errorText(error)}`,
470
+ folder === void 0 ? `Requesting vault access failed: ${errorText(error)}` : sharedVaultTokenFolderText(folder),
427
471
  "error"
428
472
  );
429
473
  }).finally(() => {
@@ -434,7 +478,7 @@ function createPiVaultSection(options) {
434
478
  ok: true,
435
479
  text: await stepText(
436
480
  step,
437
- ". You are told here when the operator decides."
481
+ " Pi tells you here when the operator decides."
438
482
  )
439
483
  };
440
484
  }
@@ -442,39 +486,39 @@ function createPiVaultSection(options) {
442
486
  if (approved && status.mode !== "custody")
443
487
  actions.push({
444
488
  id: "use-vault",
445
- label: "Serve Pi from the vault",
489
+ label: "Use the vault",
446
490
  run: async () => {
447
491
  try {
448
492
  await setup.useVault(status.selectedRouteId);
449
493
  } catch (error) {
450
494
  return refused(error);
451
495
  }
452
- return "Pi now serves Antigravity from the vault.";
496
+ return "Pi now uses the vault for Antigravity.";
453
497
  }
454
498
  });
455
499
  if (status.mode !== "local" && (status.enrollment.state === "idle" || status.enrollment.state === "pending" || status.enrollment.state === "denied"))
456
500
  actions.push({
457
501
  id: "use-local",
458
- label: "Serve Pi from its own login",
502
+ label: "Use Pi's own login",
459
503
  run: async () => {
460
504
  await setup.useLocal();
461
- return "Pi now serves Antigravity from its own login. Run /login if it has none.";
505
+ return "Pi now uses its own login for Antigravity. Run /login if you haven't signed in.";
462
506
  }
463
507
  });
464
508
  if (status.enrollment.state === "pending")
465
509
  lines.push(
466
- `Forget vault enrollment is offered again once Pi sees the request approved or denied. To drop it, the operator runs ck auth enroll deny --request-id ${status.enrollment.requestId ?? "<id>"}; then choose Request vault access here.`
510
+ `You can't disconnect while this request waits for the vault operator. To withdraw it, ask the vault operator to run ck auth enroll deny --request-id ${status.enrollment.requestId ?? "<id>"}, then choose Request vault access here so Pi sees the answer.`
467
511
  );
468
512
  else if (status.enrollment.state !== "idle")
469
513
  actions.push({
470
514
  id: "forget",
471
- label: "Forget vault enrollment",
472
- description: "Deletes Pi's enrollment token and returns to the local login. The operator can still revoke the enrollment in the vault.",
515
+ label: "Disconnect from the vault",
516
+ description: "Removes Pi's vault key from this computer and goes back to Pi's own login. Using the vault here again needs a new request and a new approval. The earlier approval stays in the vault until the vault operator revokes it.",
473
517
  irreversible: true,
474
- confirm: `Forget the ${PI_ANTIGRAVITY_ENROLLMENT_NAME} enrollment? Using the vault again needs a new approval.`,
518
+ confirm: "Disconnect this computer from the vault? Pi goes back to its own login, and using the vault here again needs a new approval from the vault operator.",
475
519
  run: async () => {
476
520
  await setup.disconnect();
477
- return "Vault enrollment forgotten; Pi serves Antigravity from its own login.";
521
+ return "Disconnected from the vault. Pi now uses its own login for Antigravity.";
478
522
  }
479
523
  });
480
524
  return { lines, items, actions };
@@ -513,22 +557,25 @@ async function custodyAccounts(status, options) {
513
557
  if (status.mode !== "custody" || status.enrollment.state !== "approved")
514
558
  return { list: [] };
515
559
  try {
516
- return { list: await options.accounts().list() };
560
+ const port = options.accounts();
561
+ const list = await port.list();
562
+ const reach = port.grantReach();
563
+ return { list, ...reach && { reach } };
517
564
  } catch (error) {
518
565
  return {
519
566
  list: [],
520
- problem: `Vault accounts unavailable: ${error instanceof Error ? error.message : String(error)}`
567
+ problem: `Pi can't load the vault accounts right now: ${error instanceof Error ? error.message : String(error)}`
521
568
  };
522
569
  }
523
570
  }
524
571
  function localLoginLine(status) {
525
572
  switch (status.hostSlot) {
526
573
  case "dormant":
527
- return "The native Pi login is dormant while vault enrollment controls serving.";
574
+ return "Pi's own login is set aside while Pi uses the vault.";
528
575
  case "login":
529
576
  return "Signed in with Pi's own login. Pi's /login replaces it and /logout removes it.";
530
577
  case "placeholder":
531
- return "Pi holds the vault placeholder, not a login. Run /login, or serve from the vault in the Vault section.";
578
+ return "Not signed in with Pi's own login. Run /login, or choose Use the vault in the Vault section.";
532
579
  case "empty":
533
580
  return "Not signed in. Run Pi's /login to sign in.";
534
581
  }
@@ -542,7 +589,7 @@ function createPiAntigravityAccountSections(options) {
542
589
  if (status.mode === "unreadable")
543
590
  return {
544
591
  lines: [
545
- `The vault mode file is unreadable (${status.modeProblem ?? "unknown problem"}); Pi serves nothing until a mode is chosen in the Vault section.`
592
+ status.modeProblem === void 0 ? "Pi can't tell whether to use the vault or its own login, so it sends no Antigravity requests. See the Vault section." : `Pi can't tell whether to use the vault or its own login (${status.modeProblem}), so it sends no Antigravity requests. Choose one in the Vault section.`
546
593
  ]
547
594
  };
548
595
  if (status.mode === "local")
@@ -552,23 +599,23 @@ function createPiAntigravityAccountSections(options) {
552
599
  localLoginLine(status)
553
600
  ]
554
601
  };
555
- const { list, problem } = await custodyAccounts(status, options);
602
+ const { list, reach, problem } = await custodyAccounts(status, options);
556
603
  const serving = servingAccount(status, list);
557
604
  const items = list.map((account) => ({
558
605
  id: opaqueItemId(account.routeId),
559
606
  label: vaultAccountName(list, account),
560
- detail: account.declined ? "declined on this host" : account === serving ? "serving Pi" : "available"
607
+ detail: account.declined ? VAULT_ACCOUNT_TURNED_OFF : account === serving ? VAULT_ACCOUNT_IN_USE : VAULT_ACCOUNT_AVAILABLE
561
608
  }));
562
609
  return {
563
610
  lines: [
564
- "Pi serves Antigravity from the vault, one account at a time. Choose, decline or accept accounts in the Vault section.",
611
+ "Pi uses the vault for Antigravity, one account at a time. Choose an account, or turn accounts off and on, in the Vault section.",
565
612
  ...status.hostSlot === "login" ? [
566
- "Conflict: Pi also holds its own login. Requests are refused until /logout or a switch back to the local login."
613
+ "Pi is also signed in with its own login, so requests are refused. Run /logout, or switch back to Pi's own login in the Vault section."
567
614
  ] : [],
568
615
  ...problem ? [problem] : [],
569
616
  // An approved enrollment without a read grant lists nothing;
570
617
  // say so rather than show an empty account list.
571
- ...!problem && list.length === 0 && status.enrollment.state === "approved" ? piNoVaultAccountsGuidance() : []
618
+ ...!problem && list.length === 0 && status.enrollment.state === "approved" ? piNoVaultAccountsGuidance(reach) : []
572
619
  ],
573
620
  items
574
621
  };
@@ -589,7 +636,7 @@ function createPiAntigravityAccountSections(options) {
589
636
  if (status.mode === "unreadable")
590
637
  return {
591
638
  lines: [
592
- "Vault enrollment authority is unreadable; requests are refused. Recover or Disconnect in the Vault section."
639
+ "Pi can't read its vault setup, so requests are refused. See the Vault section."
593
640
  ]
594
641
  };
595
642
  if (status.mode !== "custody")
@@ -598,13 +645,13 @@ function createPiAntigravityAccountSections(options) {
598
645
  "Every request uses Pi's own login. There is no account rotation."
599
646
  ]
600
647
  };
601
- const { list, problem } = await custodyAccounts(status, options);
648
+ const { list, reach, problem } = await custodyAccounts(status, options);
602
649
  const serving = servingAccount(status, list);
603
650
  if (!problem && list.length === 0 && status.enrollment.state === "approved")
604
651
  return {
605
652
  lines: [
606
- "The vault serves Pi no accounts, so requests are refused. There is no account rotation.",
607
- ...piNoVaultAccountsGuidance()
653
+ "The vault gives Pi no accounts, so requests are refused. There is no account rotation.",
654
+ ...piNoVaultAccountsGuidance(reach)
608
655
  ]
609
656
  };
610
657
  return {
@@ -643,23 +690,13 @@ function getPackedRefresh(accessToken) {
643
690
  }
644
691
 
645
692
  // src/paths.ts
646
- import { homedir } from "node:os";
647
- import { dirname, join } from "node:path";
648
- function getPiConfigDir() {
649
- return process.env.PI_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent");
650
- }
651
- function getPiAntigravityAuthFile() {
652
- return process.env.PI_ANTIGRAVITY_AUTH_FILE?.trim() || join(getPiConfigDir(), "antigravity-accounts.json");
653
- }
654
- function getPiAntigravityVaultDir() {
655
- return join(dirname(getPiAntigravityAuthFile()), "antigravity-auth-vault");
656
- }
657
- function getPiAntigravityVaultModeFile() {
658
- return join(getPiAntigravityVaultDir(), "pi-mode.json");
659
- }
660
- function getPiAntigravityVaultRosterFile() {
661
- return join(getPiAntigravityVaultDir(), "pi-roster.json");
662
- }
693
+ import {
694
+ getPiAntigravityAuthFile,
695
+ getPiAntigravityVaultDir,
696
+ getPiAntigravityVaultModeFile,
697
+ getPiAntigravityVaultRosterFile,
698
+ getPiConfigDir
699
+ } from "@cortexkit/antigravity-auth-core";
663
700
 
664
701
  // src/stream.ts
665
702
  import {
@@ -674,10 +711,12 @@ import {
674
711
  isGeminiArgonModel,
675
712
  isRetiredAgyModel,
676
713
  orderAgyRequestPayloadInPlace,
714
+ PreResponseDeadline,
715
+ parseRefreshParts,
677
716
  RETIRED_AGY_MODEL_MESSAGE,
678
717
  redactCredentialError,
679
718
  redactCredentialText,
680
- resolveModelForHeaderStyle
719
+ resolveAntigravityModel
681
720
  } from "@cortexkit/antigravity-auth-core";
682
721
  import {
683
722
  calculateCost,
@@ -1021,11 +1060,11 @@ function resolvePiAntigravityModel(model, reasoning) {
1021
1060
  const lower = model.id.toLowerCase();
1022
1061
  const supportsAgyTiers = model.reasoning && (isGeminiArgonModel(lower) || lower.includes("gemini-3") || lower.includes("claude") && (lower.includes("thinking") || /claude-(opus|sonnet)-5-5/.test(lower)));
1023
1062
  if (!supportsAgyTiers || !reasoning) {
1024
- return resolveModelForHeaderStyle(model.id, "antigravity");
1063
+ return resolveAntigravityModel(model.id);
1025
1064
  }
1026
1065
  const tier = reasoning === "minimal" ? "low" : reasoning === "xhigh" ? "high" : reasoning;
1027
1066
  const baseModel = model.id.replace(/-(minimal|low|medium|high|xhigh)$/i, "");
1028
- return resolveModelForHeaderStyle(`${baseModel}-${tier}`, "antigravity");
1067
+ return resolveAntigravityModel(`${baseModel}-${tier}`);
1029
1068
  }
1030
1069
  function prepareAntigravityRequest(options) {
1031
1070
  const resolved = resolvePiAntigravityModel(
@@ -1100,14 +1139,25 @@ async function sendAntigravityRequest(options) {
1100
1139
  throw new Error(RETIRED_AGY_MODEL_MESSAGE);
1101
1140
  }
1102
1141
  const packedRefresh = getPackedRefresh(options.accessToken) ?? "";
1103
- const projectContext = await ensureProjectContext({
1104
- type: "oauth",
1105
- refresh: packedRefresh,
1106
- access: options.accessToken,
1107
- expires: Date.now() + 6e4
1108
- });
1142
+ const packedProject = parseRefreshParts(packedRefresh);
1143
+ for (const projectId of [
1144
+ packedProject.projectId,
1145
+ packedProject.managedProjectId
1146
+ ]) {
1147
+ if (projectId) options.selectedProjectIds.add(projectId);
1148
+ }
1149
+ const projectContext = await options.deadline.wait(
1150
+ () => ensureProjectContext({
1151
+ type: "oauth",
1152
+ refresh: packedRefresh,
1153
+ access: options.accessToken,
1154
+ expires: Date.now() + 6e4
1155
+ })
1156
+ );
1157
+ if (projectContext.effectiveProjectId)
1158
+ options.selectedProjectIds.add(projectContext.effectiveProjectId);
1109
1159
  const prepared = prepareAntigravityRequest(options);
1110
- await options.assertAdmission?.();
1160
+ await options.deadline.wait(() => options.assertAdmission?.());
1111
1161
  return dispatchAntigravityRequest(
1112
1162
  prepared,
1113
1163
  {
@@ -1117,13 +1167,13 @@ async function sendAntigravityRequest(options) {
1117
1167
  options.signal ?? options.streamOptions?.signal ?? null
1118
1168
  );
1119
1169
  }
1120
- function streamCortexKitAntigravity(model, context, options, assertAdmission) {
1170
+ function streamCortexKitAntigravity(model, context, options, assertAdmission, preResponseClock) {
1121
1171
  return runAntigravityStream(
1122
1172
  model,
1123
1173
  context,
1124
1174
  options,
1125
- async (sessionKey, signal) => {
1126
- await assertAdmission?.();
1175
+ async (sessionKey, signal, _selectedCredentials, selectedProjectIds, deadline) => {
1176
+ await deadline.wait(() => assertAdmission?.());
1127
1177
  const accessToken = options?.apiKey ?? "";
1128
1178
  if (!accessToken)
1129
1179
  throw new Error("Missing Antigravity OAuth access token");
@@ -1134,17 +1184,20 @@ function streamCortexKitAntigravity(model, context, options, assertAdmission) {
1134
1184
  accessToken,
1135
1185
  sessionKey,
1136
1186
  signal,
1137
- assertAdmission
1187
+ assertAdmission,
1188
+ selectedProjectIds,
1189
+ deadline
1138
1190
  });
1139
- }
1191
+ },
1192
+ preResponseClock
1140
1193
  );
1141
1194
  }
1142
- function createCortexKitAntigravityVaultStream(send, placeholderKey) {
1195
+ function createCortexKitAntigravityVaultStream(send, placeholderKey, preResponseClock) {
1143
1196
  return (model, context, options) => runAntigravityStream(
1144
1197
  model,
1145
1198
  context,
1146
1199
  options,
1147
- (sessionKey, signal, selectedCredentials) => {
1200
+ (sessionKey, signal, selectedCredentials, selectedProjectIds, deadline) => {
1148
1201
  if (options?.apiKey !== placeholderKey)
1149
1202
  throw new Error(
1150
1203
  "The native Google credential slot is not synchronized for vault mode. No request was sent."
@@ -1155,28 +1208,37 @@ function createCortexKitAntigravityVaultStream(send, placeholderKey) {
1155
1208
  streamOptions: options,
1156
1209
  sessionKey
1157
1210
  });
1158
- return send(async (credential, sendSignal) => {
1159
- selectedCredentials.add(credential.accessToken);
1160
- try {
1161
- return await dispatchAntigravityRequest(
1162
- prepared,
1163
- credential,
1164
- sendSignal ?? signal
1165
- );
1166
- } catch (error) {
1167
- throw redactCredentialError(error, [...selectedCredentials]);
1168
- }
1169
- }, signal);
1170
- }
1211
+ return deadline.open(
1212
+ () => send(async (credential, sendSignal) => {
1213
+ selectedCredentials.add(credential.accessToken);
1214
+ selectedProjectIds.add(credential.projectId);
1215
+ try {
1216
+ deadline.follow(sendSignal);
1217
+ return await deadline.open(
1218
+ () => dispatchAntigravityRequest(prepared, credential, signal)
1219
+ );
1220
+ } catch (error) {
1221
+ throw redactCredentialError(
1222
+ error,
1223
+ [...selectedCredentials],
1224
+ [...selectedProjectIds]
1225
+ );
1226
+ }
1227
+ }, signal)
1228
+ );
1229
+ },
1230
+ preResponseClock
1171
1231
  );
1172
1232
  }
1173
- function runAntigravityStream(model, context, options, open) {
1233
+ function runAntigravityStream(model, context, options, open, preResponseClock) {
1174
1234
  const stream = createAssistantMessageEventStream();
1235
+ const deadline = new PreResponseDeadline(options?.signal, preResponseClock);
1175
1236
  void (async () => {
1176
1237
  const output = createOutput(model);
1177
1238
  const selectedCredentials = new Set(
1178
1239
  options?.apiKey ? [options.apiKey] : []
1179
1240
  );
1241
+ const selectedProjectIds = /* @__PURE__ */ new Set();
1180
1242
  stream.push({ type: "start", partial: output });
1181
1243
  let response;
1182
1244
  let requestAbort;
@@ -1185,16 +1247,22 @@ function runAntigravityStream(model, context, options, open) {
1185
1247
  };
1186
1248
  try {
1187
1249
  const sessionKey = getRequestSessionKey(context, options);
1188
- requestAbort = new AbortController();
1189
- detachCaller = forwardAbort(options?.signal, requestAbort);
1190
- response = await open(
1191
- sessionKey,
1192
- requestAbort.signal,
1193
- selectedCredentials
1250
+ const openingAbort = new AbortController();
1251
+ requestAbort = openingAbort;
1252
+ detachCaller = forwardAbort(deadline.signal, openingAbort);
1253
+ response = await deadline.open(
1254
+ () => open(
1255
+ sessionKey,
1256
+ openingAbort.signal,
1257
+ selectedCredentials,
1258
+ selectedProjectIds,
1259
+ deadline
1260
+ )
1194
1261
  );
1262
+ if (response.ok) deadline.acceptHeaders();
1195
1263
  if (!response.ok) {
1196
1264
  throw new Error(
1197
- `Antigravity request failed: HTTP ${response.status} ${await response.text()}`
1265
+ `Antigravity request failed: HTTP ${response.status} ${await deadline.wait(() => response.text())}`
1198
1266
  );
1199
1267
  }
1200
1268
  const content = output.content;
@@ -1390,11 +1458,13 @@ function runAntigravityStream(model, context, options, open) {
1390
1458
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
1391
1459
  output.errorMessage = redactCredentialText(
1392
1460
  error instanceof Error ? error.message : String(error),
1393
- [...selectedCredentials]
1461
+ [...selectedCredentials],
1462
+ [...selectedProjectIds]
1394
1463
  );
1395
1464
  stream.push({ type: "error", reason: output.stopReason, error: output });
1396
1465
  stream.end();
1397
1466
  } finally {
1467
+ deadline.dispose();
1398
1468
  detachCaller();
1399
1469
  }
1400
1470
  })();
@@ -1425,7 +1495,7 @@ function requireCustody(read) {
1425
1495
  );
1426
1496
  return read.record;
1427
1497
  }
1428
- function selectRoute(routes, routeId) {
1498
+ function selectRoute(routes, routeId, emptyRosterCause) {
1429
1499
  if (routeId !== void 0) {
1430
1500
  const selected = routes.find((route) => route.routeId === routeId);
1431
1501
  if (!selected)
@@ -1437,7 +1507,7 @@ function selectRoute(routes, routeId) {
1437
1507
  const [only, ...rest] = routes;
1438
1508
  if (!only)
1439
1509
  throw new PiVaultSendError(
1440
- "The vault serves Pi no Antigravity accounts yet."
1510
+ piEmptyVaultRosterRefusal(emptyRosterCause()) ?? "The vault serves Pi no Antigravity accounts yet."
1441
1511
  );
1442
1512
  if (rest.length > 0)
1443
1513
  throw new PiVaultSendError(
@@ -1460,12 +1530,17 @@ function createPiVaultSend(options) {
1460
1530
  signal.throwIfAborted();
1461
1531
  routes = source.routes();
1462
1532
  }
1463
- const ref = selectRoute(routes, record.routeId);
1533
+ const ref = selectRoute(
1534
+ routes,
1535
+ record.routeId,
1536
+ () => source.emptyRosterCause()
1537
+ );
1464
1538
  return source.send(ref, dispatch, { site: "model", signal });
1465
1539
  };
1466
1540
  }
1467
1541
  function createPiVaultAccountsPort(options) {
1468
1542
  let lastRows = /* @__PURE__ */ new Map();
1543
+ let lastReach;
1469
1544
  const refFor = (routeId) => {
1470
1545
  const ref = lastRows.get(routeId);
1471
1546
  if (!ref)
@@ -1477,7 +1552,9 @@ function createPiVaultAccountsPort(options) {
1477
1552
  return {
1478
1553
  list: async () => {
1479
1554
  const { source } = await openCustody(options);
1555
+ lastReach = void 0;
1480
1556
  const roster = await source.refresh();
1557
+ lastReach = source.grantReach();
1481
1558
  const rows = /* @__PURE__ */ new Map();
1482
1559
  const choices = [];
1483
1560
  for (const row of roster?.rows ?? []) {
@@ -1503,6 +1580,7 @@ function createPiVaultAccountsPort(options) {
1503
1580
  lastRows = rows;
1504
1581
  return choices;
1505
1582
  },
1583
+ grantReach: () => lastReach,
1506
1584
  decline: async (routeId) => {
1507
1585
  const { source } = await openCustody(options);
1508
1586
  await source.decline(refFor(routeId));
@@ -1783,8 +1861,8 @@ async function registerCortexKitPiAntigravity(pi, seams = {}) {
1783
1861
  const mode = await currentMode();
1784
1862
  const { openPiSlot } = await (seams.loadPiSlot ?? loadCommonAuthPiSlot)();
1785
1863
  const slot = openPiSlot({
1786
- authPath: seams.nativeAuthPath ?? join2(getAgentDir(), "auth.json"),
1787
- stashPath: seams.nativeStashPath ?? join2(
1864
+ authPath: seams.nativeAuthPath ?? join(getAgentDir(), "auth.json"),
1865
+ stashPath: seams.nativeStashPath ?? join(
1788
1866
  seams.vaultDir ?? getPiAntigravityVaultDir(),
1789
1867
  "google-login-stash.json"
1790
1868
  ),