@indigoai-us/hq-cli 5.309.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 (75) hide show
  1. package/CHANGELOG.md +13 -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 +0 -2
  19. package/dist/commands/lanes-checks.js +2 -2
  20. package/dist/commands/lanes.d.ts +0 -11
  21. package/dist/commands/lanes.js +6 -6
  22. package/dist/commands/mcp-registration.d.ts +5 -11
  23. package/dist/commands/mcp-registration.js +2 -6
  24. package/dist/lib/core-utils/sentry-report.d.ts +5 -11
  25. package/dist/lib/core-utils/sentry-report.js +2 -2
  26. package/dist/lib/db/local.d.ts +0 -5
  27. package/dist/lib/db/local.js +1 -1
  28. package/dist/lib/db/migrate.d.ts +0 -12
  29. package/dist/lib/db/migrate.js +5 -5
  30. package/dist/lib/db/sql.d.ts +0 -10
  31. package/dist/lib/db/sql.js +4 -8
  32. package/dist/lib/doctor/checks/codex-wiring.js +1 -33
  33. package/dist/lib/doctor/checks/grok-wiring.js +1 -34
  34. package/dist/lib/doctor/deep/effects.js +1 -3
  35. package/dist/lib/doctor/deep/sandbox.js +1 -3
  36. package/dist/lib/doctor/fixtures/schema.js +1 -3
  37. package/dist/lib/doctor/shared.d.ts +8 -0
  38. package/dist/lib/doctor/shared.js +38 -0
  39. package/dist/lib/lanes/autopilot.js +6 -4
  40. package/dist/lib/monitor.d.ts +2 -6
  41. package/dist/lib/monitor.js +5 -5
  42. package/dist/lib/report-publish.d.ts +1 -10
  43. package/dist/lib/report-publish.js +3 -9
  44. package/dist/lib/search-index/index.d.ts +7 -0
  45. package/dist/lib/search-index/index.js +21 -2
  46. package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
  47. package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
  48. package/dist/lib/service-manager/launchd.d.ts +0 -7
  49. package/dist/lib/service-manager/launchd.js +2 -2
  50. package/dist/main.js +7 -2
  51. package/dist/startup-registration.js +9 -1
  52. package/dist/utils/client-health.d.ts +0 -4
  53. package/dist/utils/client-health.js +2 -2
  54. package/dist/utils/feedback-diagnostics.d.ts +2 -1
  55. package/dist/utils/feedback-log-bundle.d.ts +0 -25
  56. package/dist/utils/feedback-log-bundle.js +5 -5
  57. package/dist/utils/feedback-logs.d.ts +4 -38
  58. package/dist/utils/feedback-logs.js +7 -7
  59. package/dist/utils/feedback-screenshots.d.ts +0 -1
  60. package/dist/utils/feedback-screenshots.js +1 -1
  61. package/dist/utils/install-tree-torn.d.ts +19 -13
  62. package/dist/utils/install-tree-torn.js +77 -2
  63. package/dist/utils/local-files-overview.d.ts +0 -6
  64. package/dist/utils/local-files-overview.js +1 -1
  65. package/dist/utils/qmd-model-download-error.d.ts +0 -8
  66. package/dist/utils/qmd-model-download-error.js +1 -1
  67. package/dist/utils/qmd-query-document-error.d.ts +0 -6
  68. package/dist/utils/qmd-query-document-error.js +1 -1
  69. package/dist/utils/scaffold-fast-import-guard.d.ts +0 -1
  70. package/dist/utils/scaffold-fast-import-guard.js +1 -1
  71. package/dist/utils/scaffold-sibling-references.d.ts +3 -2
  72. package/dist/utils/sentry-fingerprint.js +17 -0
  73. package/dist/utils/version-gate.d.ts +4 -0
  74. package/dist/utils/version-gate.js +13 -0
  75. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
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
+
5
18
  ## [5.309.0] — 2026-10-01
6
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.
7
20
  - Allow non-overlapping story-prefix teams to share a project in lane checks and reports.
@@ -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;
@@ -19,7 +19,6 @@ export interface ProjectMapping {
19
19
  excludePrefix?: string;
20
20
  }
21
21
  export declare function mappingIncludesStory(mapping: ProjectMapping, storyId: string): boolean;
22
- export declare function ambiguousProjectMappings(mappings: readonly ProjectMapping[]): Map<string, string[]>;
23
22
  export interface ProjectReadResult {
24
23
  stories: Array<Record<string, unknown>>;
25
24
  mtime: number;
@@ -35,7 +34,6 @@ export declare function readProject(root: string, company: string, prdPath: stri
35
34
  export declare function loadCompanyQuestionRecords(root: string, company: LaneCompanyRef, projectId?: string): QuestionRecord[];
36
35
  export declare function readTeamInputs(root: string, company: LaneCompanyRef, projects: readonly ProjectMapping[], lanes: readonly LaneRecord[]): TrackerCheckTeam[];
37
36
  export declare function pathForTrackerRecord(projectDirectory: string, kind: TrackerKind, id: string): string;
38
- export declare function listTrackerRecords(projectDirectory: string, kind: TrackerKind): TrackerRecord[];
39
37
  export interface TolerantTrackerReadIssue {
40
38
  path: string;
41
39
  field: string;
@@ -23,7 +23,7 @@ function mappingsOverlap(left, right) {
23
23
  const commonPrefix = includes.sort((a, b) => b.length - a.length)[0] ?? "";
24
24
  return ![left.excludePrefix, right.excludePrefix].some((excluded) => excluded !== undefined && commonPrefix.startsWith(excluded));
25
25
  }
26
- export function ambiguousProjectMappings(mappings) {
26
+ function ambiguousProjectMappings(mappings) {
27
27
  const byProject = new Map();
28
28
  for (const mapping of mappings)
29
29
  byProject.set(mapping.projectId, [...(byProject.get(mapping.projectId) ?? []), mapping]);
@@ -641,7 +641,7 @@ function requiredRecordTimestamp(kind, record, projectDirectory, field, value) {
641
641
  return parseTimestamp(value, field, pathForTrackerRecord(projectDirectory, kind, record.id));
642
642
  return value;
643
643
  }
644
- export function listTrackerRecords(projectDirectory, kind) {
644
+ function listTrackerRecords(projectDirectory, kind) {
645
645
  try {
646
646
  return createLocalTrackerStore(projectDirectory).list(kind);
647
647
  }
@@ -112,8 +112,6 @@ export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
112
112
  */
113
113
  export declare const ASK_BACKOFF_MS: readonly [5000, 15000];
114
114
  export declare const ASK_ATTEMPTS: number;
115
- /** Flip to true in the next release to make --priority mandatory. */
116
- export declare const ASK_PRIORITY_REQUIRED = false;
117
115
  export declare function casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
118
116
  export declare function casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
119
117
  /**
@@ -128,12 +126,7 @@ export declare function composeWorkerBrief(worker: ResolvedLaneWorker, taskBrief
128
126
  * and the foreground-only rule.
129
127
  */
130
128
  export declare function composeGeneralBrief(taskBrief: string): string;
131
- export declare function parseMaxRuntimeSeconds(raw: unknown, workerId: string): number | undefined;
132
- export declare const PICKUP_TERMINAL_REASONS: readonly ["account_not_logged_in", "unknown_account", "account_disabled", "model_unsupported"];
133
129
  export declare const DISPATCH_TERMINAL_REASON: "outpost_dispatch_failed";
134
- export type PickupTerminalReason = (typeof PICKUP_TERMINAL_REASONS)[number];
135
- /** Only explicit non-persistence evidence can prove a failed dispatch has no owner snapshot. */
136
- export declare function isUndispatchedOutpostMirror(lane: LaneRecord): boolean;
137
130
  /**
138
131
  * The CAS updater a pickup failure applies. A lane that is already terminal
139
132
  * for any reason (a cancel that landed after the caller's last read, an
@@ -149,9 +142,5 @@ export declare function pickupFailureUpdater(reasonRaw: string, details: Record<
149
142
  export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
150
143
  export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
151
144
  export declare function stopCommittedState(state: LaneRecord["state"]): "cancelled" | "killed";
152
- /** Stderr notice that `stop` ends a lane for good, naming the resumable path. */
153
- export declare function stopTerminalWarning(laneId: string): string;
154
- /** Stderr notice when an interrupted lane has no session to resume from. */
155
- export declare function interruptNotResumableWarning(laneId: string): string;
156
145
  export declare function registerLanesCommand(parent: Command, deps?: LanesCommandDeps): Command;
157
146
  //# sourceMappingURL=lanes.d.ts.map
@@ -2253,7 +2253,7 @@ function assertKnownOutpost(requested, known) {
2253
2253
  export const ASK_BACKOFF_MS = [5_000, 15_000];
2254
2254
  export const ASK_ATTEMPTS = ASK_BACKOFF_MS.length + 1;
2255
2255
  /** Flip to true in the next release to make --priority mandatory. */
2256
- export const ASK_PRIORITY_REQUIRED = false;
2256
+ const ASK_PRIORITY_REQUIRED = false;
2257
2257
  const ASK_PRIORITY_WARNING = "--priority will be required in the next release.";
2258
2258
  const QUESTION_PRIORITIES = ["P0", "P1", "P2"];
2259
2259
  function withCasRetry(fn, attempts = 5) {
@@ -2390,7 +2390,7 @@ export function composeGeneralBrief(taskBrief) {
2390
2390
  "",
2391
2391
  ].join("\n");
2392
2392
  }
2393
- export function parseMaxRuntimeSeconds(raw, workerId) {
2393
+ function parseMaxRuntimeSeconds(raw, workerId) {
2394
2394
  if (raw === undefined || raw === null || raw === "")
2395
2395
  return undefined;
2396
2396
  if (typeof raw === "number") {
@@ -2540,7 +2540,7 @@ async function storyBoardCheck(ctx, company, projectId, storyIds) {
2540
2540
  throw err;
2541
2541
  }
2542
2542
  }
2543
- export const PICKUP_TERMINAL_REASONS = [
2543
+ const PICKUP_TERMINAL_REASONS = [
2544
2544
  "account_not_logged_in",
2545
2545
  "unknown_account",
2546
2546
  "account_disabled",
@@ -2548,7 +2548,7 @@ export const PICKUP_TERMINAL_REASONS = [
2548
2548
  ];
2549
2549
  export const DISPATCH_TERMINAL_REASON = "outpost_dispatch_failed";
2550
2550
  /** Only explicit non-persistence evidence can prove a failed dispatch has no owner snapshot. */
2551
- export function isUndispatchedOutpostMirror(lane) {
2551
+ function isUndispatchedOutpostMirror(lane) {
2552
2552
  return lane.host.kind === "outpost" &&
2553
2553
  isTerminalLaneState(lane.state) &&
2554
2554
  lane.terminal_reason === DISPATCH_TERMINAL_REASON &&
@@ -7302,7 +7302,7 @@ async function runStop(ctx, laneId, reason, quiet = false) {
7302
7302
  });
7303
7303
  }
7304
7304
  /** Stderr notice that `stop` ends a lane for good, naming the resumable path. */
7305
- export function stopTerminalWarning(laneId) {
7305
+ function stopTerminalWarning(laneId) {
7306
7306
  return `hq lanes: warning: stop kills lane ${laneId} and it cannot be resumed. ` +
7307
7307
  `To pause it and keep it resumable, use hq lanes interrupt ${laneId}.\n`;
7308
7308
  }
@@ -7395,7 +7395,7 @@ function persistInterruptedSession(hqRoot, lane) {
7395
7395
  : { ...current, provider_session_id: sessionId, provider_session_id_absent: undefined });
7396
7396
  }
7397
7397
  /** Stderr notice when an interrupted lane has no session to resume from. */
7398
- export function interruptNotResumableWarning(laneId) {
7398
+ function interruptNotResumableWarning(laneId) {
7399
7399
  return `hq lanes: warning: lane ${laneId} was interrupted before its provider reported a session id, ` +
7400
7400
  `so hq lanes resume cannot continue it. Start a new lane for the remaining work.\n`;
7401
7401
  }
@@ -74,8 +74,8 @@ export declare class ConfigParseError extends McpRegistrationError {
74
74
  export declare class ConfigPermissionError extends McpRegistrationError {
75
75
  readonly code: "ConfigPermissionError";
76
76
  }
77
- export type CodexMcpCollisionOwner = 'hq' | 'foreign' | 'unknown';
78
- export type CodexMcpCollisionField = 'command' | 'args' | 'env' | 'url' | 'other';
77
+ type CodexMcpCollisionOwner = 'hq' | 'foreign' | 'unknown';
78
+ type CodexMcpCollisionField = 'command' | 'args' | 'env' | 'url' | 'other';
79
79
  /** Safe, value-free context for a Codex name collision reported to Sentry. */
80
80
  export interface CodexMcpCollisionDiagnostics {
81
81
  client: 'codex';
@@ -146,7 +146,7 @@ export declare function backupRoot(env: SafeWriteEnv): string;
146
146
  /** `~/.hq/logs/mcp-registry.log` — the append-only JSONL audit log (mirrors backupRoot style). */
147
147
  export declare function mcpRegistryLogPath(env: SafeWriteEnv): string;
148
148
  /** The action recorded on an audit line. */
149
- export type AuditAction = 'register' | 'unregister';
149
+ type AuditAction = 'register' | 'unregister';
150
150
  /**
151
151
  * The result recorded on an audit line:
152
152
  * - `registered` — a register write that changed the file.
@@ -156,7 +156,7 @@ export type AuditAction = 'register' | 'unregister';
156
156
  * foreign/unstamped entry left in place). PARTIAL is auditable.
157
157
  * - `error` — the surface emit threw (the operation failed for this surface).
158
158
  */
159
- export type AuditResult = 'registered' | 'unregistered' | 'noop' | 'skipped' | 'error';
159
+ type AuditResult = 'registered' | 'unregistered' | 'noop' | 'skipped' | 'error';
160
160
  /**
161
161
  * One append-only audit-log entry. EXACT shape (field order is the JSON key order):
162
162
  * `{ ts, action, pack, server, transport, target, file, prevHash, newHash, result }`.
@@ -213,8 +213,6 @@ export declare function codexConfigPath(env: SafeWriteEnv): string;
213
213
  export declare function isCodexInstalled(env: SafeWriteEnv): boolean;
214
214
  /** `~/.claude.json` — the PINNED single Claude surface (top-level mcpServers). */
215
215
  export declare function claudeConfigPath(env: SafeWriteEnv): string;
216
- /** `~/.mcp.json` — the project-pinned Claude surface (a symlink on this host). */
217
- export declare function mcpJsonPath(env: SafeWriteEnv): string;
218
216
  /** Outcome of {@link readConfigDoc}: a parsed doc plus whether the file pre-existed. */
219
217
  export interface ReadConfigResult<T> {
220
218
  /** The parsed doc, or the `emptyDoc` when the file is absent/empty. */
@@ -284,10 +282,6 @@ export declare function readConfigDoc<T>(target: string, format: ConfigFormat<T>
284
282
  * separate snippet block) cannot leak. The result never contains raw file content.
285
283
  */
286
284
  export declare function sanitizeParserMessage(message: string): string;
287
- /** Default stale-lock timeout — a lock older than this whose PID is dead is broken. */
288
- export declare const DEFAULT_LOCK_STALE_MS = 30000;
289
- /** Default total time to wait for a held lock before giving up. */
290
- export declare const DEFAULT_LOCK_WAIT_MS = 10000;
291
285
  /** A held advisory lock; release with {@link releaseLock} (idempotent). */
292
286
  export interface ConfigLock {
293
287
  /** The lockfile path (`~/.<basename>.hqlock`). */
@@ -752,7 +746,7 @@ export declare function mergeCodexServer(name: string, def: TomlTable): (doc: Co
752
746
  */
753
747
  export declare function registerCodexServer(opts: RegisterServerOptions): RegisterSurfaceResult | RegisterSurfaceSkip;
754
748
  /** Discriminates an un-registration outcome on one surface. */
755
- export type UnregisterOutcome =
749
+ type UnregisterOutcome =
756
750
  /** The provenance-stamped entry was found and removed (file changed). */
757
751
  'removed'
758
752
  /** The entry was already absent / never installed — a clean idempotent no-op. */