@indigoai-us/hq-cli 5.342.9 → 5.342.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.342.10] — 2026-10-04
6
+ - Attach bounded status, gateway error-class, and integrations subcommand tags to captured gateway faults.
7
+
5
8
  ## [5.342.9] — 2026-10-04
6
9
  - Child wrappers now release their package-use lease after startup modules are preloaded, so long-running child processes do not block CLI updates.
7
10
  - Keep nested HQ child leases and materialize core-job post-exit helpers before releasing the parent package lease.
@@ -150,6 +150,8 @@ export declare class IntegrationsCliError extends Error {
150
150
  * fingerprint as anything but the finite allowlist in sentry-fingerprint.ts.
151
151
  */
152
152
  readonly rpcCode?: number;
153
+ /** Fixed vocabulary for the integrations subcommand that called the gateway. */
154
+ readonly gatewaySubcommand?: "tools_list" | "tools_call" | "other";
153
155
  /**
154
156
  * HQ's own OAuth callback URL, carried on the errors that say a provider
155
157
  * refused to register HQ automatically. The provider must register the exact
@@ -164,6 +166,7 @@ export declare class IntegrationsCliError extends Error {
164
166
  status?: number;
165
167
  oauthProtected?: boolean;
166
168
  rpcCode?: number;
169
+ gatewaySubcommand?: "tools_list" | "tools_call" | "other";
167
170
  redirectUri?: string;
168
171
  });
169
172
  }
@@ -55,6 +55,8 @@ export class IntegrationsCliError extends Error {
55
55
  * fingerprint as anything but the finite allowlist in sentry-fingerprint.ts.
56
56
  */
57
57
  rpcCode;
58
+ /** Fixed vocabulary for the integrations subcommand that called the gateway. */
59
+ gatewaySubcommand;
58
60
  /**
59
61
  * HQ's own OAuth callback URL, carried on the errors that say a provider
60
62
  * refused to register HQ automatically. The provider must register the exact
@@ -75,6 +77,8 @@ export class IntegrationsCliError extends Error {
75
77
  this.oauthProtected = opts.oauthProtected;
76
78
  if (opts.rpcCode !== undefined)
77
79
  this.rpcCode = opts.rpcCode;
80
+ if (opts.gatewaySubcommand !== undefined)
81
+ this.gatewaySubcommand = opts.gatewaySubcommand;
78
82
  if (opts.redirectUri !== undefined) {
79
83
  Object.defineProperty(this, "redirectUri", {
80
84
  value: opts.redirectUri,
@@ -175,6 +179,18 @@ const EXPECTED_GATEWAY_ERROR_CODES = new Set([-32003, -32602, -32050]);
175
179
  function isExpectedGatewayError(code) {
176
180
  return code != null && EXPECTED_GATEWAY_ERROR_CODES.has(code);
177
181
  }
182
+ function expectedGatewayErrorFallback(code) {
183
+ switch (code) {
184
+ case -32003:
185
+ return "You do not have access to this integration. Ask its owner to share it with you.";
186
+ case -32602:
187
+ return "The integration rejected this request. Check the tool name and arguments.";
188
+ case -32050:
189
+ return "The connected provider could not complete this request. Wait a moment and try again.";
190
+ default:
191
+ return undefined;
192
+ }
193
+ }
178
194
  // A 401 from ANY integration-gateway vault call means the caller's HQ session
179
195
  // is expired or missing — an expected auth state fixed by `hq login`, not an
180
196
  // hq-cli defect. Raise the same typed AuthError the vault company-resolution
@@ -399,7 +415,18 @@ export async function resolveConnection(token, companyUid, app, opts, recoveryOp
399
415
  }
400
416
  return selectConnection(connections, opts);
401
417
  }
418
+ function gatewaySubcommandFor(params) {
419
+ const name = params.name;
420
+ if (typeof name !== "string")
421
+ return "other";
422
+ if (name.endsWith(".mcp.tools.list"))
423
+ return "tools_list";
424
+ if (name.endsWith(".mcp.tools.call"))
425
+ return "tools_call";
426
+ return "other";
427
+ }
402
428
  export async function callGateway(token, params, idempotencyKey) {
429
+ const gatewaySubcommand = gatewaySubcommandFor(params);
403
430
  const deadline = Date.now() + ASYNC_GATEWAY_DEADLINE_MS;
404
431
  const res = await vaultApiFetch({
405
432
  token,
@@ -414,6 +441,7 @@ export async function callGateway(token, params, idempotencyKey) {
414
441
  });
415
442
  const message = (await res.json().catch(() => null));
416
443
  let terminalMessage = message;
444
+ let terminalStatus = res.status;
417
445
  if (res.status === 202) {
418
446
  const accepted = message;
419
447
  const jobId = accepted && typeof accepted.jobId === "string" ? accepted.jobId : "";
@@ -427,7 +455,9 @@ export async function callGateway(token, params, idempotencyKey) {
427
455
  if (!idempotencyKey) {
428
456
  throw new IntegrationsCliError(`Integration gateway accepted async job ${jobId} without an idempotency key.`, { expected: false, status: 202 });
429
457
  }
430
- terminalMessage = await pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline);
458
+ const terminal = await pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, deadline);
459
+ terminalMessage = terminal.message;
460
+ terminalStatus = terminal.status;
431
461
  }
432
462
  if (res.status !== 202 && (!res.ok || !terminalMessage)) {
433
463
  raiseIfUnauthorized(res);
@@ -437,7 +467,7 @@ export async function callGateway(token, params, idempotencyKey) {
437
467
  // reaches the Sentry fingerprint's HTTP bucket instead of colliding under
438
468
  // default grouping. Same reasoning as raiseForResponse, which already sets
439
469
  // `status`. This does not change the `expected` decision above.
440
- throw new IntegrationsCliError(`Integration gateway request failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status });
470
+ throw new IntegrationsCliError(`Integration gateway request failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status, gatewaySubcommand });
441
471
  }
442
472
  if (terminalMessage?.error) {
443
473
  // The gateway's error text is minted UPSTREAM (hq-pro, the integration
@@ -449,8 +479,11 @@ export async function callGateway(token, params, idempotencyKey) {
449
479
  // no-op. This preserves PR #298's user-visible diagnostic; it only makes
450
480
  // it safe on the newly-expected path.
451
481
  throw new IntegrationsCliError(redactErrorText(terminalMessage.error.message ?? "") ||
482
+ expectedGatewayErrorFallback(terminalMessage.error.code) ||
452
483
  "Integration gateway returned an error.", {
453
484
  expected: isExpectedGatewayError(terminalMessage.error.code),
485
+ status: terminalStatus,
486
+ gatewaySubcommand,
454
487
  // Carry the protocol code to the capture site as a bounded grouping
455
488
  // discriminator (HQ-CLI collision, Sentry 7642756130). This does not
456
489
  // alter the `expected` decision above; it only labels the error.
@@ -518,7 +551,7 @@ async function pollAsyncGatewayJob(token, companyUid, jobId, idempotencyKey, dea
518
551
  if (!hasJsonRpcOutcome) {
519
552
  throw new IntegrationsCliError(`Integration job ${jobId} returned an invalid JSON-RPC response.`, { expected: false });
520
553
  }
521
- return response;
554
+ return { message: response, status: res.status };
522
555
  }
523
556
  if (poll.status !== "pending") {
524
557
  throw new IntegrationsCliError(`Integration job ${jobId} returned unknown status '${String(poll.status)}'.`, { expected: true });
package/dist/main.js CHANGED
@@ -335,6 +335,39 @@ function qmdSpawnFailureCaptureContext(err) {
335
335
  return undefined;
336
336
  return { contexts: { qmd_spawn_failure: diagnostics } };
337
337
  }
338
+ const GATEWAY_RPC_CODE_TAGS = new Map([
339
+ [-32603, "internal_error"],
340
+ [-32009, "conflict"],
341
+ [-32003, "unauthorized"],
342
+ [-32602, "invalid_params"],
343
+ [-32050, "provider_error"],
344
+ ]);
345
+ function integrationsGatewayCaptureContext(err) {
346
+ if (err === null || typeof err !== "object")
347
+ return undefined;
348
+ const record = err;
349
+ const subcommand = record.gatewaySubcommand;
350
+ if (subcommand === undefined)
351
+ return undefined;
352
+ const subcommandTag = subcommand === "tools_list" || subcommand === "tools_call"
353
+ ? subcommand
354
+ : "other";
355
+ const status = record.status;
356
+ const statusTag = typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
357
+ ? String(status)
358
+ : status === undefined ? "unknown" : "other";
359
+ const rpcCode = record.rpcCode;
360
+ const codeTag = typeof rpcCode === "number" && Number.isInteger(rpcCode)
361
+ ? GATEWAY_RPC_CODE_TAGS.get(rpcCode) ?? "other"
362
+ : rpcCode === undefined ? "missing" : "other";
363
+ return {
364
+ tags: {
365
+ integration_gateway_status: statusTag,
366
+ integration_gateway_code: codeTag,
367
+ integration_subcommand: subcommandTag,
368
+ },
369
+ };
370
+ }
338
371
  /** Classify a top-level failure without making the CLI process boundary opaque to tests. */
339
372
  export async function handleTopLevelError(err, deps = defaultTopLevelErrorDependencies) {
340
373
  // A typed plan gate, or an hq-cloud vault error carrying a plan-limit
@@ -853,9 +886,11 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
853
886
  const spawnContext = qmdSpawnFailureCaptureContext(err);
854
887
  const installContext = incompleteInstallCaptureContext(err);
855
888
  const qmdContext = qmdExitCaptureContext(err);
856
- if (spawnContext || installContext || qmdContext) {
889
+ const gatewayContext = integrationsGatewayCaptureContext(err);
890
+ if (spawnContext || installContext || qmdContext || gatewayContext) {
857
891
  deps.sentry.captureException(err, {
858
892
  ...qmdContext,
893
+ ...(gatewayContext ? { tags: gatewayContext.tags } : {}),
859
894
  ...((spawnContext || installContext)
860
895
  ? { contexts: { ...spawnContext?.contexts, ...installContext } }
861
896
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.342.9",
3
+ "version": "5.342.10",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {