@indigoai-us/hq-cli 5.308.0 → 5.309.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/command-catalog.generated.d.ts +4 -1
  3. package/dist/command-catalog.generated.js +5 -1
  4. package/dist/commands/agents.js +6 -0
  5. package/dist/commands/core-checkpoint-gate-setting.d.ts +0 -10
  6. package/dist/commands/core-checkpoint-gate-setting.js +3 -3
  7. package/dist/commands/core-ontology.d.ts +1 -9
  8. package/dist/commands/core-ontology.js +4 -4
  9. package/dist/commands/core-policy.d.ts +0 -3
  10. package/dist/commands/core-policy.js +3 -3
  11. package/dist/commands/files.js +1 -1
  12. package/dist/commands/index-cmd.js +1 -1
  13. package/dist/commands/integrations-api.d.ts +9 -4
  14. package/dist/commands/integrations-api.js +9 -6
  15. package/dist/commands/integrations-manage.js +11 -3
  16. package/dist/commands/integrations.d.ts +8 -0
  17. package/dist/commands/integrations.js +83 -0
  18. package/dist/commands/lanes-checks.d.ts +6 -2
  19. package/dist/commands/lanes-checks.js +69 -19
  20. package/dist/commands/lanes-report.js +5 -2
  21. package/dist/commands/lanes.d.ts +0 -11
  22. package/dist/commands/lanes.js +6 -6
  23. package/dist/commands/mcp-registration.d.ts +5 -11
  24. package/dist/commands/mcp-registration.js +2 -6
  25. package/dist/lib/core-utils/sentry-report.d.ts +5 -11
  26. package/dist/lib/core-utils/sentry-report.js +2 -2
  27. package/dist/lib/db/local.d.ts +0 -5
  28. package/dist/lib/db/local.js +1 -1
  29. package/dist/lib/db/migrate.d.ts +0 -12
  30. package/dist/lib/db/migrate.js +5 -5
  31. package/dist/lib/db/sql.d.ts +0 -10
  32. package/dist/lib/db/sql.js +4 -8
  33. package/dist/lib/doctor/checks/codex-wiring.d.ts +0 -4
  34. package/dist/lib/doctor/checks/codex-wiring.js +3 -35
  35. package/dist/lib/doctor/checks/grok-wiring.js +1 -34
  36. package/dist/lib/doctor/checks/runtime-health.d.ts +0 -17
  37. package/dist/lib/doctor/checks/runtime-health.js +4 -4
  38. package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +0 -1
  39. package/dist/lib/doctor/checks/scaffold-cli-floor.js +1 -1
  40. package/dist/lib/doctor/compat.d.ts +0 -16
  41. package/dist/lib/doctor/compat.js +3 -3
  42. package/dist/lib/doctor/deep/effects.js +1 -3
  43. package/dist/lib/doctor/deep/sandbox.js +1 -3
  44. package/dist/lib/doctor/fixtures/schema.js +1 -3
  45. package/dist/lib/doctor/platform.d.ts +0 -26
  46. package/dist/lib/doctor/platform.js +5 -5
  47. package/dist/lib/doctor/shared.d.ts +8 -0
  48. package/dist/lib/doctor/shared.js +38 -0
  49. package/dist/lib/lanes/autopilot.js +125 -103
  50. package/dist/lib/monitor.d.ts +2 -6
  51. package/dist/lib/monitor.js +5 -5
  52. package/dist/lib/onboarding/errors.d.ts +2 -1
  53. package/dist/lib/onboarding/errors.js +1 -1
  54. package/dist/lib/onboarding/index.d.ts +2 -6
  55. package/dist/lib/onboarding/index.js +2 -5
  56. package/dist/lib/report-publish.d.ts +1 -10
  57. package/dist/lib/report-publish.js +3 -9
  58. package/dist/lib/search-index/embed-lock.d.ts +0 -7
  59. package/dist/lib/search-index/embed-lock.js +1 -1
  60. package/dist/lib/search-index/index.d.ts +7 -0
  61. package/dist/lib/search-index/index.js +21 -2
  62. package/dist/lib/search-index/load-gate.d.ts +0 -8
  63. package/dist/lib/search-index/load-gate.js +3 -3
  64. package/dist/lib/search-index/max-doc-bytes.d.ts +2 -3
  65. package/dist/lib/search-index/max-doc-bytes.js +1 -1
  66. package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
  67. package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
  68. package/dist/lib/service-manager/launchd.d.ts +0 -7
  69. package/dist/lib/service-manager/launchd.js +2 -2
  70. package/dist/main.js +7 -2
  71. package/dist/startup-registration.js +9 -1
  72. package/dist/utils/client-health.d.ts +0 -4
  73. package/dist/utils/client-health.js +2 -2
  74. package/dist/utils/feedback-diagnostics.d.ts +2 -1
  75. package/dist/utils/feedback-log-bundle.d.ts +0 -25
  76. package/dist/utils/feedback-log-bundle.js +5 -5
  77. package/dist/utils/feedback-logs.d.ts +4 -38
  78. package/dist/utils/feedback-logs.js +7 -7
  79. package/dist/utils/feedback-screenshots.d.ts +0 -1
  80. package/dist/utils/feedback-screenshots.js +1 -1
  81. package/dist/utils/install-tree-torn.d.ts +19 -13
  82. package/dist/utils/install-tree-torn.js +77 -2
  83. package/dist/utils/local-files-overview.d.ts +0 -6
  84. package/dist/utils/local-files-overview.js +1 -1
  85. package/dist/utils/qmd-model-download-error.d.ts +0 -8
  86. package/dist/utils/qmd-model-download-error.js +1 -1
  87. package/dist/utils/qmd-query-document-error.d.ts +0 -6
  88. package/dist/utils/qmd-query-document-error.js +1 -1
  89. package/dist/utils/scaffold-fast-import-guard.d.ts +0 -1
  90. package/dist/utils/scaffold-fast-import-guard.js +1 -1
  91. package/dist/utils/scaffold-sibling-references.d.ts +3 -2
  92. package/dist/utils/sentry-fingerprint.js +17 -0
  93. package/dist/utils/version-gate.d.ts +4 -0
  94. package/dist/utils/version-gate.js +13 -0
  95. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,7 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.309.1] — 2026-10-01
6
+ - Fixed `hq agents rm` to remove external agents through revoke rather than the hosted-agent teardown path.
7
+
8
+ - Wait for asynchronous integration approvals to reach a terminal queue state and report pending work accurately.
9
+
10
+ - Add privacy-safe qmd exit diagnostics to Sentry reports.
11
+
12
+ - `hq files delete` help now explains exact-key and wildcard-prefix matching.
13
+
14
+ - Move directly controlled Linux CI checks to ARM runners where their tools support ARM.
15
+ - Lead autopilot resolves a lead's company through its project when the lane record does not carry a company, and skips project paths registered to a different company.
16
+ - Install-tree recovery errors now report bounded sibling, target, version-gate phase, and updater-liveness enums.
17
+
18
+ ## [5.309.0] — 2026-10-01
19
+ - `hq lanes autopilot run` now evaluates leads based on lane ownership rather than a host label, and reports registered leads assigned to another host as skipped.
20
+ - Allow non-overlapping story-prefix teams to share a project in lane checks and reports.
21
+
5
22
  ## [5.308.0] — 2026-10-01
23
+ - Added native `hq core audit-log`, `git-pack-extension-check`, `pin`, and `refresh-vault-access` commands behind the default-off `core-native-utilities` flag.
24
+ - CI now cancels superseded pull-request benchmarks and reserves the additional Windows and macOS checks for changes that affect those platform paths. Main pushes retain all platform checks.
25
+ - `hq lanes list` now settles legacy failed-dispatch mirrors as undispatched only after a successful strict owner snapshot returns no row; snapshot errors and mirrors with a host report remain unverified.
26
+ - Outpost pickup and remote database requests now time out, and failed pending DM lookups are reported.
27
+
28
+ ## [5.307.0] — 2026-10-01
6
29
  - Add native `hq core resolve-company` on the fast route behind the default-off `core-native-utilities` gate.
7
30
 
8
31
  - Stop immediately when hq-deploy repeats an app-list cursor while resolving apps.
@@ -1734,7 +1734,7 @@ export declare const COMMAND_CATALOG: readonly [{
1734
1734
  readonly subcommands: readonly [];
1735
1735
  }, {
1736
1736
  readonly name: "delete";
1737
- readonly description: "Delete vault objects under a prefix (bounded + scoped). Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.";
1737
+ readonly description: "Delete vault objects by exact key or bounded prefix. A trailing slash expands recursively; use * for an explicit wildcard prefix. Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.";
1738
1738
  readonly aliases: readonly [];
1739
1739
  readonly hidden: false;
1740
1740
  readonly usage: "[options] <prefix>";
@@ -3492,6 +3492,9 @@ export declare const COMMAND_CATALOG: readonly [{
3492
3492
  }, {
3493
3493
  readonly flags: "--json";
3494
3494
  readonly description: "Machine-readable output";
3495
+ }, {
3496
+ readonly flags: "--history";
3497
+ readonly description: "Include recently decided calls";
3495
3498
  }];
3496
3499
  readonly subcommands: readonly [];
3497
3500
  }, {
@@ -2213,7 +2213,7 @@ export const COMMAND_CATALOG = [
2213
2213
  },
2214
2214
  {
2215
2215
  "name": "delete",
2216
- "description": "Delete vault objects under a prefix (bounded + scoped). Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.",
2216
+ "description": "Delete vault objects by exact key or bounded prefix. A trailing slash expands recursively; use * for an explicit wildcard prefix. Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.",
2217
2217
  "aliases": [],
2218
2218
  "hidden": false,
2219
2219
  "usage": "[options] <prefix>",
@@ -4500,6 +4500,10 @@ export const COMMAND_CATALOG = [
4500
4500
  {
4501
4501
  "flags": "--json",
4502
4502
  "description": "Machine-readable output"
4503
+ },
4504
+ {
4505
+ "flags": "--history",
4506
+ "description": "Include recently decided calls"
4503
4507
  }
4504
4508
  ],
4505
4509
  "subcommands": []
@@ -1946,6 +1946,12 @@ export function registerAgentsCommand(program) {
1946
1946
  }
1947
1947
  try {
1948
1948
  const token = (await resolveVaultCredential()).token;
1949
+ const status = await getAgentStatus(token, agentUid);
1950
+ if (recordValue(status.agent)?.runtimeKind === "external") {
1951
+ await revokeAgent(token, agentUid, true);
1952
+ console.log(chalk.green(`Revoked and removed external agent ${agentUid}.`));
1953
+ return;
1954
+ }
1949
1955
  const result = await deprovisionAgent(token, agentUid, opts.confirmDestroyAgentsV2);
1950
1956
  if (result.terminal === false) {
1951
1957
  console.log(chalk.yellow(`Deprovision in progress for agent ${agentUid} (not yet fully torn down — re-run to continue).`));
@@ -18,16 +18,6 @@
18
18
  */
19
19
  import { Command } from "commander";
20
20
  export declare const CHECKPOINT_GATE_SETTING_FLAG = "checkpoint.gate.company-setting";
21
- /** The refresh gives up after this long; the gate keeps the cached value. */
22
- export declare const CHECKPOINT_GATE_REFRESH_TIMEOUT_MS = 3000;
23
- export declare function assertCompanySlug(slug: string): void;
24
- export interface CheckpointGateCacheEntry {
25
- company: string;
26
- checkpointGateEnabled: boolean;
27
- /** Unix seconds. The gate treats an entry older than 600 s as stale. */
28
- fetched_at: number;
29
- }
30
- export declare function checkpointGateCacheDir(home?: string): string;
31
21
  export declare function checkpointGateCachePath(slug: string, home?: string): string;
32
22
  export type CheckpointGateRefreshOutcome = {
33
23
  status: "written";
@@ -25,15 +25,15 @@ import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
25
25
  import { describeCoreCommand } from "./core-command-catalog.js";
26
26
  export const CHECKPOINT_GATE_SETTING_FLAG = "checkpoint.gate.company-setting";
27
27
  /** The refresh gives up after this long; the gate keeps the cached value. */
28
- export const CHECKPOINT_GATE_REFRESH_TIMEOUT_MS = 3_000;
28
+ const CHECKPOINT_GATE_REFRESH_TIMEOUT_MS = 3_000;
29
29
  /** Same character set the gate script accepts; anything else is refused. */
30
30
  const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
31
- export function assertCompanySlug(slug) {
31
+ function assertCompanySlug(slug) {
32
32
  if (!SLUG_RE.test(slug) || slug === "personal") {
33
33
  throw new Error(`checkpoint-gate-setting: '${slug}' is not a company slug`);
34
34
  }
35
35
  }
36
- export function checkpointGateCacheDir(home = os.homedir()) {
36
+ function checkpointGateCacheDir(home = os.homedir()) {
37
37
  return path.join(home, ".hq", "cache", "checkpoint-gate");
38
38
  }
39
39
  export function checkpointGateCachePath(slug, home = os.homedir()) {
@@ -9,11 +9,7 @@
9
9
  */
10
10
  import { Command } from "commander";
11
11
  import { type FlagReader } from "../lib/checkpoint-ontology/plan.js";
12
- import { type AliasDrop } from "../lib/checkpoint-ontology/drop-aliases.js";
13
- import { type DeleteVaultKey, type ListedPair, type MergeCopiesReport } from "../lib/checkpoint-ontology/merge-copies.js";
14
- export declare const ONTOLOGY_PAYLOAD_FILE = "payload.json";
15
- /** The hq-flags read, scoped to the payload company. */
16
- export declare const registryFlagReader: FlagReader;
12
+ import { type DeleteVaultKey, type MergeCopiesReport } from "../lib/checkpoint-ontology/merge-copies.js";
17
13
  export interface OntologyCommandDeps {
18
14
  readFlag?: FlagReader;
19
15
  now?: () => Date;
@@ -41,8 +37,6 @@ export interface MergeCopiesCommandOptions {
41
37
  json?: boolean;
42
38
  }
43
39
  export declare function mergeCopiesSummary(report: MergeCopiesReport): string;
44
- /** Reads a `--only` file: a JSON array of `{ copy, original }` company-relative paths. */
45
- export declare function readListedPairs(file: string): ListedPair[];
46
40
  /**
47
41
  * `hq core ontology merge-copies`: merge `<slug> <n>.md` copies into their
48
42
  * original (`--apply`), then delete merged copies from the vault
@@ -58,8 +52,6 @@ export declare function readListedPairs(file: string): ListedPair[];
58
52
  * which a person reviewed, instead of the pairs a finder produces.
59
53
  */
60
54
  export declare function runOntologyMergeCopies(hqRoot: string, slug: string | undefined, options: MergeCopiesCommandOptions, deps?: OntologyCommandDeps): Promise<string>;
61
- /** Reads a `drop-aliases --only` file: a JSON array of `{ entity, alias }`. */
62
- export declare function readAliasDrops(file: string): AliasDrop[];
63
55
  /**
64
56
  * `hq core ontology drop-aliases`: remove the reviewed `{ entity, alias }`
65
57
  * pairs in `--only` from entity files (`--apply`); without it only reports.
@@ -22,7 +22,7 @@ import { MERGE_COPIES_FLAG, MERGE_LISTED_PAIRS_FLAG, MERGE_REWRITE_REFS_FLAG, ME
22
22
  import { callDeleteEndpoint } from "./files.js";
23
23
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
24
24
  import { registerCoreOntologyNativeCommands } from "./core-ontology-native.js";
25
- export const ONTOLOGY_PAYLOAD_FILE = "payload.json";
25
+ const ONTOLOGY_PAYLOAD_FILE = "payload.json";
26
26
  class OntologyUsageError extends Error {
27
27
  }
28
28
  function readPayload(runDir) {
@@ -49,7 +49,7 @@ function requireRunDir(runDir) {
49
49
  return resolved;
50
50
  }
51
51
  /** The hq-flags read, scoped to the payload company. */
52
- export const registryFlagReader = (flagKey, company) => readLanesRegistryFlag(flagKey, {
52
+ const registryFlagReader = (flagKey, company) => readLanesRegistryFlag(flagKey, {
53
53
  company: company.cloudUid ? { slug: company.slug, uid: company.cloudUid } : null,
54
54
  });
55
55
  function companyBriefSuffix(brief) {
@@ -160,7 +160,7 @@ export function mergeCopiesSummary(report) {
160
160
  return lines.join("\n");
161
161
  }
162
162
  /** Reads a `--only` file: a JSON array of `{ copy, original }` company-relative paths. */
163
- export function readListedPairs(file) {
163
+ function readListedPairs(file) {
164
164
  let parsed;
165
165
  try {
166
166
  parsed = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -270,7 +270,7 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
270
270
  return options.json ? JSON.stringify(report, null, 2) : mergeCopiesSummary(report);
271
271
  }
272
272
  /** Reads a `drop-aliases --only` file: a JSON array of `{ entity, alias }`. */
273
- export function readAliasDrops(file) {
273
+ function readAliasDrops(file) {
274
274
  let parsed;
275
275
  try {
276
276
  parsed = JSON.parse(fs.readFileSync(file, "utf8"));
@@ -1,8 +1,5 @@
1
1
  import type { Command } from 'commander';
2
2
  import { type FrontmatterResult } from './core-policy-frontmatter.js';
3
- export declare const POLICY_AGE_REPORT_ASSET = "core/scripts/policy-age-report.sh";
4
- export declare const POLICY_RETIRE_ASSET = "core/scripts/policy-retire.sh";
5
- export declare const POLICY_FRONTMATTER_ASSET = "core/scripts/read-policy-frontmatter.sh";
6
3
  export type CorePolicyResult = {
7
4
  code: number;
8
5
  stdout: Buffer;
@@ -8,9 +8,9 @@ import { resolveLiveRoot, resolveBundledAsset } from '../utils/hq-roots.js';
8
8
  import { runBundledScript } from '../utils/run-bundled-script.js';
9
9
  import { describeCoreCommand } from './core-command-catalog.js';
10
10
  import { runPolicyFrontmatter as runPolicyFrontmatterImpl } from './core-policy-frontmatter.js';
11
- export const POLICY_AGE_REPORT_ASSET = 'core/scripts/policy-age-report.sh';
12
- export const POLICY_RETIRE_ASSET = 'core/scripts/policy-retire.sh';
13
- export const POLICY_FRONTMATTER_ASSET = 'core/scripts/read-policy-frontmatter.sh';
11
+ const POLICY_AGE_REPORT_ASSET = 'core/scripts/policy-age-report.sh';
12
+ const POLICY_RETIRE_ASSET = 'core/scripts/policy-retire.sh';
13
+ const POLICY_FRONTMATTER_ASSET = 'core/scripts/read-policy-frontmatter.sh';
14
14
  const AGE_HELP = [
15
15
  '# hq-core: public',
16
16
  '# policy-age-report.sh — measure policy usage and flag retirement candidates',
@@ -511,7 +511,7 @@ export function registerFilesCommand(program) {
511
511
  });
512
512
  files
513
513
  .command("delete <prefix>")
514
- .description("Delete vault objects under a prefix (bounded + scoped). Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.")
514
+ .description("Delete vault objects by exact key or bounded prefix. A trailing slash expands recursively; use * for an explicit wildcard prefix. Always previews the exact count first; prompts for confirmation unless --yes. Use --personal to target your own personal vault instead of a company.")
515
515
  .option("--dry-run", "List what WOULD be deleted without deleting anything")
516
516
  .option("--personal", "Target your own personal vault instead of a company vault (mutually exclusive with --company)")
517
517
  .option("-y, --yes", "Skip the confirmation prompt (for scripts)")
@@ -38,7 +38,7 @@ export function syncSearchIndex(hqRoot, embed, dependencies = defaults) {
38
38
  catch {
39
39
  /* capping is an optimisation; indexing is the job */
40
40
  }
41
- dependencies.runQmd(['update'], { cwd: hqRoot });
41
+ dependencies.runQmd(['update'], { cwd: hqRoot, entryPath: 'index-sync' });
42
42
  };
43
43
  const writeStderr = dependencies.writeStderr ?? ((text) => process.stderr.write(text));
44
44
  const singleFlightEnabled = (dependencies.singleFlightEnabled ?? qmdReindexSingleFlightEnabled)();
@@ -288,13 +288,18 @@ export interface PendingApproval {
288
288
  requestedByName?: string | null;
289
289
  createdAt: string;
290
290
  expiresAt: string;
291
+ /** Queue state is present on history rows; older servers may omit it. */
292
+ status?: string;
293
+ /** Owner-facing outcome projected by hq-pro for decided rows. */
294
+ outcome?: string;
291
295
  }
292
296
  /**
293
- * Live open-approval list, read from hq-pro's confirm-queue state (not the
294
- * audit feed). Unlike the audit-derived reconstruction this replaces, a call
295
- * that was approved/rejected/expired no longer appears here.
297
+ * Read open approvals from hq-pro's confirm-queue state (not the audit feed).
298
+ * Pass `includeHistory` to include recently decided rows alongside open ones.
296
299
  */
297
- export declare function fetchPendingApprovals(token: string, companyUid: string): Promise<PendingApproval[]>;
300
+ export declare function fetchPendingApprovals(token: string, companyUid: string, options?: {
301
+ includeHistory?: boolean;
302
+ }): Promise<PendingApproval[]>;
298
303
  export declare function getConnectionAccess(token: string, companyUid: string, connectionId: string): Promise<ConnectionAccess>;
299
304
  export declare function mutateConnectionAccess(token: string, companyUid: string, action: "grant" | "revoke", input: {
300
305
  connectionId: string;
@@ -148,20 +148,23 @@ export async function setReadSafe(token, companyUid, connectionId, toolName, rea
148
148
  await raiseForResponse(res, "Failed to update the tool's read-safe setting");
149
149
  }
150
150
  /**
151
- * Live open-approval list, read from hq-pro's confirm-queue state (not the
152
- * audit feed). Unlike the audit-derived reconstruction this replaces, a call
153
- * that was approved/rejected/expired no longer appears here.
151
+ * Read open approvals from hq-pro's confirm-queue state (not the audit feed).
152
+ * Pass `includeHistory` to include recently decided rows alongside open ones.
154
153
  */
155
- export async function fetchPendingApprovals(token, companyUid) {
154
+ export async function fetchPendingApprovals(token, companyUid, options = {}) {
156
155
  const res = await vaultApiFetch({
157
156
  token,
158
157
  path: "/v1/integrations/factory/pending",
159
- query: { companyUid },
158
+ query: {
159
+ companyUid,
160
+ ...(options.includeHistory ? { include: "history" } : {}),
161
+ },
160
162
  });
161
163
  if (!res.ok)
162
164
  await raiseForResponse(res, "Failed to list pending approvals");
163
165
  const body = (await res.json());
164
- return body.pending ?? [];
166
+ const pending = body.pending ?? [];
167
+ return options.includeHistory ? [...pending, ...(body.history ?? [])] : pending;
165
168
  }
166
169
  export async function getConnectionAccess(token, companyUid, connectionId) {
167
170
  const res = await vaultApiFetch({
@@ -562,6 +562,7 @@ export function registerManageCommands(integrations) {
562
562
  .description("Calls that were queued for an owner's approval")
563
563
  .option("--company <slug>", "Company slug, e.g. indigo")
564
564
  .option("--json", "Machine-readable output")
565
+ .option("--history", "Include recently decided calls")
565
566
  .action(async (opts) => {
566
567
  const token = await ensureCognitoIdToken();
567
568
  const companyUid = await getCompanyUid(token, opts.company);
@@ -570,7 +571,9 @@ export function registerManageCommands(integrations) {
570
571
  // could only show that a call was once queued.
571
572
  let pending;
572
573
  try {
573
- pending = await fetchPendingApprovals(token, companyUid);
574
+ pending = await fetchPendingApprovals(token, companyUid, {
575
+ includeHistory: opts.history,
576
+ });
574
577
  }
575
578
  catch (err) {
576
579
  // This CLI can ship ahead of the hq-pro deploy that adds the pending
@@ -586,14 +589,19 @@ export function registerManageCommands(integrations) {
586
589
  return;
587
590
  }
588
591
  if (pending.length === 0) {
589
- console.log("Nothing is waiting for approval.");
592
+ console.log(opts.history ? "No recent approval history." : "Nothing is waiting for approval.");
590
593
  return;
591
594
  }
592
595
  for (const row of pending) {
593
596
  const who = row.requestedByName ?? row.requestedBy;
594
597
  const label = row.requestedTool ?? row.toolName;
595
598
  console.log(`${chalk.bold(label)} ${chalk.dim(`${who} · ${row.createdAt}`)}`);
596
- console.log(chalk.dim(` hq integrations approve ${row.queueId}${row.provider ? ` --provider ${bareProvider(row.provider)}` : ""}`));
599
+ if (opts.history) {
600
+ console.log(chalk.dim(` ${row.queueId} · ${row.status ?? row.outcome ?? "status unavailable"}`));
601
+ }
602
+ else {
603
+ console.log(chalk.dim(` hq integrations approve ${row.queueId}${row.provider ? ` --provider ${bareProvider(row.provider)}` : ""}`));
604
+ }
597
605
  }
598
606
  });
599
607
  integrations
@@ -22,6 +22,7 @@
22
22
  * hq integrations tools --provider <p> What the connected app can do.
23
23
  * hq integrations call <tool> --provider <p> --args '<json>'
24
24
  * hq integrations pending Calls awaiting approval.
25
+ * hq integrations pending --history Recently decided calls, including status.
25
26
  * hq integrations approve|reject <queueId> Decide a queued call.
26
27
  *
27
28
  * Govern and remove:
@@ -49,7 +50,14 @@
49
50
  * `integrations-manage.ts`.
50
51
  */
51
52
  import { Command } from "commander";
53
+ import { type PendingApproval } from "./integrations-api.js";
52
54
  export { IntegrationsCliError, callGateway, fetchConnections, queuedOutcome, selectConnection, toolPrefixForProvider, unwrapGatewayResult, } from "./integrations-core.js";
53
55
  export type { AdminConnection } from "./integrations-core.js";
56
+ /** Wait for a decided queue item without resending the approve request. */
57
+ export declare function waitForApprovalTerminal(token: string, companyUid: string, queueId: string, options?: {
58
+ timeoutMs?: number;
59
+ now?: () => number;
60
+ sleep?: (milliseconds: number) => Promise<void>;
61
+ }): Promise<PendingApproval | null>;
54
62
  export declare function registerIntegrationsCommand(program: Command): void;
55
63
  //# sourceMappingURL=integrations.d.ts.map
@@ -22,6 +22,7 @@
22
22
  * hq integrations tools --provider <p> What the connected app can do.
23
23
  * hq integrations call <tool> --provider <p> --args '<json>'
24
24
  * hq integrations pending Calls awaiting approval.
25
+ * hq integrations pending --history Recently decided calls, including status.
25
26
  * hq integrations approve|reject <queueId> Decide a queued call.
26
27
  *
27
28
  * Govern and remove:
@@ -57,6 +58,7 @@ import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, isCl
57
58
  import { registerConnectCommands } from "./integrations-connect.js";
58
59
  import { registerImportCommands } from "./integrations-import.js";
59
60
  import { registerManageCommands } from "./integrations-manage.js";
61
+ import { fetchPendingApprovals } from "./integrations-api.js";
60
62
  export { IntegrationsCliError, callGateway, fetchConnections, queuedOutcome, selectConnection, toolPrefixForProvider, unwrapGatewayResult, } from "./integrations-core.js";
61
63
  /**
62
64
  * Gateway metadata is additive and may be absent when talking to an older
@@ -160,6 +162,55 @@ function reportRevokedConnection(connection, opts) {
160
162
  }
161
163
  return true;
162
164
  }
165
+ const APPROVAL_POLL_TIMEOUT_MS = 5 * 60_000;
166
+ const APPROVAL_POLL_INITIAL_DELAY_MS = 1_000;
167
+ const APPROVAL_POLL_MAX_DELAY_MS = 15_000;
168
+ const APPROVAL_TERMINAL_STATUSES = new Set([
169
+ "completed",
170
+ "resumed",
171
+ "failed",
172
+ "write_unknown",
173
+ "rejected",
174
+ "expired",
175
+ ]);
176
+ const APPROVAL_TERMINAL_OUTCOMES = new Set([
177
+ "ran",
178
+ "failed",
179
+ "unknown",
180
+ "turned_down",
181
+ "timed_out",
182
+ ]);
183
+ function isTerminalApproval(row) {
184
+ return APPROVAL_TERMINAL_STATUSES.has(row.status ?? "") ||
185
+ APPROVAL_TERMINAL_OUTCOMES.has(row.outcome ?? "");
186
+ }
187
+ /** Wait for a decided queue item without resending the approve request. */
188
+ export async function waitForApprovalTerminal(token, companyUid, queueId, options = {}) {
189
+ const timeoutMs = options.timeoutMs ?? APPROVAL_POLL_TIMEOUT_MS;
190
+ const now = options.now ?? Date.now;
191
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
192
+ const deadline = now() + timeoutMs;
193
+ let delayMs = APPROVAL_POLL_INITIAL_DELAY_MS;
194
+ while (now() < deadline) {
195
+ const rows = await fetchPendingApprovals(token, companyUid, { includeHistory: true });
196
+ const row = rows.find((candidate) => candidate.queueId === queueId);
197
+ if (row && isTerminalApproval(row))
198
+ return row;
199
+ const remainingMs = deadline - now();
200
+ if (remainingMs <= 0)
201
+ break;
202
+ await sleep(Math.min(delayMs, remainingMs));
203
+ delayMs = Math.min(delayMs * 2, APPROVAL_POLL_MAX_DELAY_MS);
204
+ }
205
+ return null;
206
+ }
207
+ function isPendingApprovalResponse(status) {
208
+ return ["pending", "approved", "approved_pending_run", "in_progress"].includes(status ?? "");
209
+ }
210
+ function approvalOutcomeFailed(row) {
211
+ return ["failed", "write_unknown"].includes(row.status ?? "") ||
212
+ ["failed", "unknown"].includes(row.outcome ?? "");
213
+ }
163
214
  export function registerIntegrationsCommand(program) {
164
215
  const integrations = program
165
216
  .command("integrations")
@@ -347,6 +398,38 @@ export function registerIntegrationsCommand(program) {
347
398
  raiseIfUpstreamUnavailable(res);
348
399
  throw new IntegrationsCliError(body.error ?? `${decision} failed (HTTP ${res.status})`, { expected: isClientError(res.status) });
349
400
  }
401
+ const pendingApprove = decision === "approve" &&
402
+ (res.status === 202 || isPendingApprovalResponse(body.status));
403
+ if (pendingApprove) {
404
+ const terminal = await waitForApprovalTerminal(token, companyUid, queueId);
405
+ const historyCommand = `hq integrations pending --history${opts.company ? ` --company ${opts.company}` : ""}`;
406
+ if (!terminal) {
407
+ if (opts.json) {
408
+ printJson({ ...body, status: "pending", pollTimedOut: true, checkCommand: historyCommand });
409
+ }
410
+ else {
411
+ console.log(chalk.yellow(`Approval is still running after 5 minutes. Check queue ${queueId} later with: ${historyCommand}`));
412
+ }
413
+ return;
414
+ }
415
+ if (opts.json)
416
+ printJson(terminal);
417
+ else if (approvalOutcomeFailed(terminal)) {
418
+ const unknown = terminal.status === "write_unknown" || terminal.outcome === "unknown";
419
+ console.log(chalk.red(unknown
420
+ ? `The provider outcome is unknown for ${queueId}. Check the provider before taking any action; ${historyCommand} shows the latest queue state.`
421
+ : `The approved call failed (${terminal.status ?? terminal.outcome}). See ${historyCommand} for its status.`));
422
+ }
423
+ else if (terminal.status === "resumed" || terminal.status === "completed" || terminal.outcome === "ran") {
424
+ console.log(chalk.green("Approved — the call completed."));
425
+ }
426
+ else {
427
+ console.log(chalk.yellow(`Approval ended with status ${terminal.status ?? terminal.outcome ?? "unknown"}.`));
428
+ }
429
+ if (approvalOutcomeFailed(terminal))
430
+ process.exitCode = 1;
431
+ return;
432
+ }
350
433
  if (opts.json) {
351
434
  printJson(body);
352
435
  return;
@@ -1,6 +1,7 @@
1
1
  import type { Command } from "commander";
2
+ import { type TrackerCheckTeam } from "../lib/lanes/tracker-checks.js";
2
3
  import { type TrackerKind, type TrackerRecord } from "../lib/lanes/tracker-store.js";
3
- import type { LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
4
+ import type { LaneRecord, LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
4
5
  export interface LaneChecksDeps {
5
6
  hqRoot: () => string;
6
7
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
@@ -14,7 +15,10 @@ export interface ProjectMapping {
14
15
  team: string;
15
16
  leadLaneId?: string;
16
17
  statusPath: string;
18
+ storyPrefix?: string;
19
+ excludePrefix?: string;
17
20
  }
21
+ export declare function mappingIncludesStory(mapping: ProjectMapping, storyId: string): boolean;
18
22
  export interface ProjectReadResult {
19
23
  stories: Array<Record<string, unknown>>;
20
24
  mtime: number;
@@ -28,8 +32,8 @@ export interface ProjectReadResult {
28
32
  export declare function loadProjectMappings(root: string, company: string, requestedProject?: string): Map<string, ProjectMapping>;
29
33
  export declare function readProject(root: string, company: string, prdPath: string, tolerantStories?: boolean): ProjectReadResult;
30
34
  export declare function loadCompanyQuestionRecords(root: string, company: LaneCompanyRef, projectId?: string): QuestionRecord[];
35
+ export declare function readTeamInputs(root: string, company: LaneCompanyRef, projects: readonly ProjectMapping[], lanes: readonly LaneRecord[]): TrackerCheckTeam[];
31
36
  export declare function pathForTrackerRecord(projectDirectory: string, kind: TrackerKind, id: string): string;
32
- export declare function listTrackerRecords(projectDirectory: string, kind: TrackerKind): TrackerRecord[];
33
37
  export interface TolerantTrackerReadIssue {
34
38
  path: string;
35
39
  field: string;