xaq-cli 0.1.0-nightly.86 → 0.1.0-nightly.87

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/cli.js CHANGED
@@ -59,7 +59,7 @@ const SERVER_GROUP = "Run xaq:";
59
59
  const AGENT_GROUP = "On an agent's machine:";
60
60
  withServerOptions(program.command("start", { isDefault: true }).description("run xaq in this terminal and print the setup link (the default command)").summary("run it in this terminal (the default command)").helpGroup(SERVER_GROUP)).option("--no-open", "don't open a browser on first run").option("--dev", "development: API only, the console runs on Vite (:5173)").allowExcessArguments().action(async (o, cmd) => {
61
61
  if (cmd.args.length) throw new Error(unknownCommand(cmd.args[0]));
62
- const { runForeground } = await import("./server-DzT6WlPa.js");
62
+ const { runForeground } = await import("./server-DrcmvN-U.js");
63
63
  await runForeground({
64
64
  ...configInput(o),
65
65
  dev: !!o.dev,
@@ -8384,7 +8384,7 @@ var GoogleRuntime = class {
8384
8384
  let uncertain = null;
8385
8385
  for (let attempt = 0;; attempt++) {
8386
8386
  if (opts.cost) await this.bucket(accountId).take(opts.cost);
8387
- const token = await this.tokens.accessToken(accountId, { force: forced });
8387
+ const token = opts.token ? await opts.token(forced) : await this.tokens.accessToken(accountId, { force: forced });
8388
8388
  let err;
8389
8389
  try {
8390
8390
  const res = await fetch(url, {
@@ -8417,7 +8417,7 @@ var GoogleRuntime = class {
8417
8417
  }
8418
8418
  if (err.status === 401 && !forced) {
8419
8419
  forced = true;
8420
- this.tokens.invalidate(accountId);
8420
+ if (!opts.token) this.tokens.invalidate(accountId);
8421
8421
  continue;
8422
8422
  }
8423
8423
  if (!(idempotent ? isRetryable(err) : rejectedBeforeProcessing(err)) || attempt >= 4) {
@@ -8596,11 +8596,27 @@ function classifyCalendarError(err) {
8596
8596
  /** The enable link Google put in a SERVICE_DISABLED error, if any. */
8597
8597
  const activationUrl = (err) => err.details.metadata?.activationUrl ?? null;
8598
8598
  //#endregion
8599
+ //#region server/google/scoped.ts
8600
+ /** Whether the account has granted xaq every one of `scopes` (as last recorded). False for an unknown account. */
8601
+ function accountHasScopes(ctx, accountId, scopes) {
8602
+ const row = getRow(ctx, accountId);
8603
+ return !!row && hasScopes(accountScopes(row), scopes);
8604
+ }
8605
+ /**
8606
+ * An access token for the account limited to `scopes` (a subset of what it granted), with its expiry in epoch ms and
8607
+ * the scopes Google says it has. Cached per account and scope set until it nearly expires; one refresh in flight.
8608
+ * Throws ScopesNotGrantedError (not granted: ask for consent), AccountNeedsReauthError (the sign-in is gone) or
8609
+ * DownscopeError (Google's answer didn't prove the limit; never returned).
8610
+ */
8611
+ function googleAccessToken(ctx, accountId, scopes, opts = {}) {
8612
+ return googleRuntime(ctx).tokens.scopedAccessToken(accountId, scopes, opts);
8613
+ }
8614
+ //#endregion
8599
8615
  //#region server/google/drive.ts
8600
- /** Full Drive access also covers reading. */
8601
- const DRIVE_FULL_SCOPE = "https://www.googleapis.com/auth/drive";
8602
- /** Whether these granted scopes let xaq read the user's Drive files. */
8603
- const canReadDrive = (granted) => !!granted && (granted.includes("https://www.googleapis.com/auth/drive.readonly") || granted.includes(DRIVE_FULL_SCOPE));
8616
+ /** The scopes a Drive attachment's token carries: read-only Drive, nothing else. */
8617
+ const DRIVE_ATTACH_SCOPES = [DRIVE_READONLY_SCOPE];
8618
+ /** Whether the account has granted xaq the Drive read access attachments need (as last recorded). */
8619
+ const canReadDrive = (ctx, accountId) => accountHasScopes(ctx, accountId, DRIVE_ATTACH_SCOPES);
8604
8620
  const FIELDS$1 = "id,name,mimeType,size,webViewLink,trashed,shortcutDetails";
8605
8621
  /** Downloads take longer: 30 s plus 1 s per 256 KB they may carry, at most 10 minutes. */
8606
8622
  const downloadTimeout = (maxBytes) => Math.min(6e5, 3e4 + Math.ceil(maxBytes / 262144) * 1e3);
@@ -8608,12 +8624,17 @@ function createDriveClient(ctx, accountId) {
8608
8624
  const rt = googleRuntime(ctx);
8609
8625
  const base = ctx.config.google.apisBase ?? "https://www.googleapis.com";
8610
8626
  const path = (fileId, suffix = "") => `/drive/v3/files/${encodeURIComponent(fileId)}${suffix}`;
8627
+ const token = async (force) => (await googleAccessToken(ctx, accountId, DRIVE_ATTACH_SCOPES, { force })).accessToken;
8611
8628
  return {
8612
- getFile: (fileId) => rt.request(accountId, base, path(fileId), { query: {
8613
- fields: FIELDS$1,
8614
- supportsAllDrives: true
8615
- } }),
8629
+ getFile: (fileId) => rt.request(accountId, base, path(fileId), {
8630
+ query: {
8631
+ fields: FIELDS$1,
8632
+ supportsAllDrives: true
8633
+ },
8634
+ token
8635
+ }),
8616
8636
  download: (fileId, maxBytes) => rt.request(accountId, base, path(fileId), {
8637
+ token,
8617
8638
  query: {
8618
8639
  alt: "media",
8619
8640
  supportsAllDrives: true
@@ -8623,6 +8644,7 @@ function createDriveClient(ctx, accountId) {
8623
8644
  timeoutMs: downloadTimeout(maxBytes)
8624
8645
  }),
8625
8646
  exportFile: (fileId, mimeType, maxBytes) => rt.request(accountId, base, path(fileId, "/export"), {
8647
+ token,
8626
8648
  query: { mimeType },
8627
8649
  binary: true,
8628
8650
  maxResponseBytes: maxBytes,
@@ -8636,15 +8658,10 @@ function createDriveClient(ctx, accountId) {
8636
8658
  const GOOGLE_WORKSPACE_EXTENSION = "google-workspace";
8637
8659
  /**
8638
8660
  * The xaq Google account id (acc_…) the google-workspace connection `connectionId` is bound to, or null when it
8639
- * isn't bound (or the binding can't be read).
8640
- *
8641
- * TODO(C, feat/google-extension-auth): read C's binding here. C stores the account id on the connection or on its
8642
- * credential; that shape wasn't visible when this was written, so until C's branch merges no connection counts as
8643
- * bound and agentCanReadDrive is always false (attaching by drive_file_id is refused for every agent: fail closed).
8644
- * Replace the body with the lookup, e.g. `SELECT google_account_id FROM connections WHERE id = ? AND preset = ?`.
8661
+ * isn't a google-workspace connection or isn't bound (no account picked yet, or the account was removed).
8645
8662
  */
8646
8663
  function workspaceConnectionAccount(db, connectionId) {
8647
- return null;
8664
+ return db.get("SELECT google_account_id FROM connections WHERE id = ? AND preset = ?", connectionId, "google-workspace")?.google_account_id ?? null;
8648
8665
  }
8649
8666
  //#endregion
8650
8667
  //#region server/google/drive-access.ts
@@ -9471,6 +9488,9 @@ function planDriveFile(label, file, exportAs) {
9471
9488
  }
9472
9489
  /** Drive's error for a file id, in words the agent can act on. Anything unrecognised is returned unchanged. */
9473
9490
  function driveError(err, label, fileId, accountEmail, file) {
9491
+ if (err instanceof ScopesNotGrantedError) return driveNotEnabled(accountEmail);
9492
+ if (err instanceof AccountNeedsReauthError) return new AttachmentError("needs_reauth", `The Google account ${accountEmail} needs to be reconnected in the xaq console before its Drive files can be attached.`);
9493
+ if (err instanceof DownscopeError) return new AttachmentError("drive_error", `${label}: Google didn't give xaq a Drive-only token for ${accountEmail}, so nothing was downloaded. Try again later.`);
9474
9494
  if (!(err instanceof GoogleApiError)) return err;
9475
9495
  const name = file?.name ?? fileId;
9476
9496
  const link = file?.webViewLink ? ` Put its link in the body instead: ${file.webViewLink}` : "";
@@ -10663,7 +10683,7 @@ var AgentMail = class {
10663
10683
  */
10664
10684
  driveFor(a) {
10665
10685
  if (!agentCanReadDrive(this.ctx, this.principal.agentId, a.account.id)) return new AttachmentError("drive_not_granted", driveNotGrantedMessage(a.account.email));
10666
- if (!canReadDrive(a.account.grantedScopes)) return driveNotEnabled(a.account.email);
10686
+ if (!canReadDrive(this.ctx, a.account.id)) return driveNotEnabled(a.account.email);
10667
10687
  return createDriveClient(this.ctx, a.account.id);
10668
10688
  }
10669
10689
  /**
@@ -14158,17 +14178,6 @@ function createCalendarSetup(ctx) {
14158
14178
  };
14159
14179
  }
14160
14180
  //#endregion
14161
- //#region server/google/scoped.ts
14162
- /**
14163
- * An access token for the account limited to `scopes` (a subset of what it granted), with its expiry in epoch ms and
14164
- * the scopes Google says it has. Cached per account and scope set until it nearly expires; one refresh in flight.
14165
- * Throws ScopesNotGrantedError (not granted: ask for consent), AccountNeedsReauthError (the sign-in is gone) or
14166
- * DownscopeError (Google's answer didn't prove the limit; never returned).
14167
- */
14168
- function googleAccessToken(ctx, accountId, scopes, opts = {}) {
14169
- return googleRuntime(ctx).tokens.scopedAccessToken(accountId, scopes, opts);
14170
- }
14171
- //#endregion
14172
14181
  //#region server/google/apis.ts
14173
14182
  /** The project ID from the Google step of the setup wizard, when it is one. */
14174
14183
  function googleProjectId(ctx) {
@@ -24642,4 +24651,4 @@ function openBrowser(url) {
24642
24651
  //#endregion
24643
24652
  export { runForeground };
24644
24653
 
24645
- //# sourceMappingURL=server-DzT6WlPa.js.map
24654
+ //# sourceMappingURL=server-DrcmvN-U.js.map