@indigoai-us/hq-cli 5.345.117 → 5.345.119

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 (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/assets/scaffold/core/scripts/pipeline-conductor.sh +2671 -0
  3. package/assets/scaffold/core/scripts/pipeline-driver.sh +820 -0
  4. package/assets/scaffold/core/scripts/pipeline-envelope.sh +211 -0
  5. package/dist/command-catalog.generated.d.ts +34 -0
  6. package/dist/command-catalog.generated.js +40 -0
  7. package/dist/commands/core-checkpoint-native.d.ts +17 -3
  8. package/dist/commands/core-checkpoint-native.js +48 -8
  9. package/dist/commands/core-derive-trigger-facts-native.js +15 -7
  10. package/dist/commands/core-migrate-policy-triggers-native.js +3 -1
  11. package/dist/commands/core-pipeline-conductor-command.d.ts +5 -0
  12. package/dist/commands/core-pipeline-conductor-command.js +138 -0
  13. package/dist/commands/core-pipeline-conductor-native.d.ts +7 -0
  14. package/dist/commands/core-pipeline-conductor-native.js +2950 -0
  15. package/dist/commands/core-pipeline-driver-native.d.ts +24 -0
  16. package/dist/commands/core-pipeline-driver-native.js +843 -0
  17. package/dist/commands/core-pipeline-driver-process.d.ts +4 -0
  18. package/dist/commands/core-pipeline-driver-process.js +68 -0
  19. package/dist/commands/core-pipeline-envelope-native.d.ts +4 -0
  20. package/dist/commands/core-pipeline-envelope-native.js +87 -0
  21. package/dist/commands/core-policy.js +3 -0
  22. package/dist/commands/core-worker-lint-native.d.ts +0 -1
  23. package/dist/commands/core-worker-lint-native.js +0 -1
  24. package/dist/commands/core-worker-share-native.js +2 -2
  25. package/dist/commands/core-worker-skills-command.d.ts +1 -1
  26. package/dist/commands/core-worker-skills-command.js +3 -20
  27. package/dist/commands/core.d.ts +5 -1
  28. package/dist/commands/core.js +8 -9
  29. package/dist/commands/integrations-core.d.ts +7 -1
  30. package/dist/commands/integrations-core.js +17 -4
  31. package/dist/commands/integrations.js +91 -22
  32. package/dist/commands/storage-gate.d.ts +4 -3
  33. package/dist/commands/storage-gate.js +4 -3
  34. package/dist/fast-scaffold.js +0 -2
  35. package/dist/lib/storage-feature-flag.d.ts +7 -7
  36. package/package.json +1 -1
  37. package/assets/scaffold/core/scripts/lint-shared-worker-skills.sh +0 -197
  38. package/assets/scaffold/core/scripts/share-worker-skill.sh +0 -178
  39. package/dist/commands/core-native-worker-skills-flag.d.ts +0 -4
  40. package/dist/commands/core-native-worker-skills-flag.js +0 -7
@@ -0,0 +1,4 @@
1
+ export declare function extractWindowsDriverIdentityFunction(source: string): string;
2
+ /** Preserve the pinned driver's liveness-then-identity lock check. */
3
+ export declare function isPipelineDriverProcess(pid: number, platform?: NodeJS.Platform): boolean;
4
+ //# sourceMappingURL=core-pipeline-driver-process.d.ts.map
@@ -0,0 +1,68 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { BUNDLED_ASSET_DIR, packageRoot } from "../utils/hq-roots.js";
5
+ import { resolveWindowsBash } from "../utils/run-bundled-script.js";
6
+ const DRIVER_MARKER = "pipeline-driver";
7
+ const WINDOWS_DRIVER_IDENTITY_SIGNATURE = "is_driver() { # pid -> 0 when that pid is alive and is a pipeline-driver";
8
+ function isAlive(pid) {
9
+ try {
10
+ process.kill(pid, 0);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ export function extractWindowsDriverIdentityFunction(source) {
18
+ const escapedSignature = WINDOWS_DRIVER_IDENTITY_SIGNATURE.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
19
+ const functionSource = source.match(new RegExp(`^${escapedSignature}\\n[\\s\\S]*?^\\}`, "mu"))?.[0];
20
+ if (!functionSource)
21
+ throw new Error(`Bundled pipeline-driver.sh must define '${WINDOWS_DRIVER_IDENTITY_SIGNATURE}'`);
22
+ return functionSource;
23
+ }
24
+ function isWindowsPipelineDriverProcess(pid) {
25
+ const bash = resolveWindowsBash(process.env).executable;
26
+ if (!bash)
27
+ return false;
28
+ const scriptPath = path.join(packageRoot(), BUNDLED_ASSET_DIR, "core", "scripts", "pipeline-driver.sh");
29
+ const source = fs.readFileSync(scriptPath, "utf8");
30
+ const functionSource = extractWindowsDriverIdentityFunction(source);
31
+ // Windows intentionally runs the pinned Git Bash implementation because its
32
+ // lock format uses both MSYS and Win32 PIDs. This keeps native checks aligned
33
+ // with the shell's liveness-then-identity order and process-table lookup.
34
+ const result = spawnSync(bash, ["-c", `${functionSource}\nis_driver "$1"`, "pipeline-driver-lock-check", String(pid)], {
35
+ encoding: "utf8",
36
+ stdio: ["ignore", "pipe", "ignore"],
37
+ windowsHide: true,
38
+ });
39
+ if (result.error)
40
+ throw result.error;
41
+ return result.status === 0;
42
+ }
43
+ /** Preserve the pinned driver's liveness-then-identity lock check. */
44
+ export function isPipelineDriverProcess(pid, platform = process.platform) {
45
+ if (!Number.isSafeInteger(pid) || pid <= 0)
46
+ return false;
47
+ if (platform === "win32")
48
+ return isWindowsPipelineDriverProcess(pid);
49
+ if (!isAlive(pid))
50
+ return false;
51
+ if (platform === "linux") {
52
+ try {
53
+ return fs.readFileSync(`/proc/${pid}/cmdline`).toString("utf8").includes(DRIVER_MARKER);
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ const args = platform === "darwin"
60
+ ? ["-ww", "-o", "args=", "-p", String(pid)]
61
+ : ["-p", String(pid), "-o", "args="];
62
+ const result = spawnSync("ps", args, {
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "ignore"],
65
+ });
66
+ return result.status === 0 && String(result.stdout ?? "").includes(DRIVER_MARKER);
67
+ }
68
+ //# sourceMappingURL=core-pipeline-driver-process.js.map
@@ -0,0 +1,4 @@
1
+ type EnvelopeKind = "envelope" | "handoff";
2
+ export declare function validatePipelinePhaseFile(filePath: string, kind: EnvelopeKind): [boolean, string];
3
+ export {};
4
+ //# sourceMappingURL=core-pipeline-envelope-native.d.ts.map
@@ -0,0 +1,87 @@
1
+ import * as fs from "node:fs";
2
+ export function validatePipelinePhaseFile(filePath, kind) {
3
+ let text;
4
+ try {
5
+ text = new TextDecoder("utf-8", { fatal: true }).decode(fs.readFileSync(filePath));
6
+ if (text.charCodeAt(0) === 0xfeff)
7
+ text = text.slice(1);
8
+ text = text.replace(/\r\n?/g, "\n");
9
+ }
10
+ catch (error) {
11
+ const message = error && typeof error === "object" && "message" in error ? String(error.message) : String(error);
12
+ return [false, "pipeline-envelope: cannot read input: " + message];
13
+ }
14
+ if (!text.trim())
15
+ return [false, "invalid: empty input"];
16
+ let doc;
17
+ try {
18
+ doc = JSON.parse(text);
19
+ }
20
+ catch (error) {
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ return [false, "invalid: unparseable JSON (" + message + ")"];
23
+ }
24
+ if (!isObject(doc)) {
25
+ const typeName = doc === null ? "NoneType" : Array.isArray(doc) ? "list" : typeof doc === "string" ? "str"
26
+ : typeof doc === "boolean" ? "bool" : Number.isInteger(doc) ? "int" : "float";
27
+ return [false, "invalid: top level must be a JSON object, got " + typeName];
28
+ }
29
+ const problems = [];
30
+ const schemas = { envelope: "hq-phase-envelope/v1", handoff: "hq-phase-handoff/v1" };
31
+ if (Object.hasOwn(doc, "schema") && doc.schema !== schemas[kind])
32
+ problems.push("wrong type: schema (want " + schemas[kind] + ")");
33
+ else if (!Object.hasOwn(doc, "schema"))
34
+ problems.push("missing field: schema");
35
+ const isString = (value) => typeof value === "string";
36
+ const isNonEmpty = (value) => isString(value) && value !== "";
37
+ const stringList = (value) => Array.isArray(value) && value.every(isString);
38
+ const check = (name, predicate, want, required = true) => {
39
+ if (!Object.hasOwn(doc, name)) {
40
+ if (required)
41
+ problems.push("missing field: " + name);
42
+ return;
43
+ }
44
+ if (!predicate(doc[name]))
45
+ problems.push("wrong type: " + name + " (want " + want + ")");
46
+ };
47
+ if (kind === "envelope") {
48
+ for (const field of ["story_id", "phase", "worker_id"])
49
+ check(field, isNonEmpty, "non-empty string");
50
+ check("worktree", isNonEmpty, "string path");
51
+ check("incoming_handoff", (value) => value === null || isNonEmpty(value), "string path or null");
52
+ check("acceptance_criteria", stringList, "array of strings");
53
+ check("deadline", (value) => isString(value) && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]00:?00)\n?$/.test(value), "ISO8601 UTC string");
54
+ check("fresh_call", (value) => typeof value === "boolean", "boolean");
55
+ for (const field of ["project", "engine", "result_path", "story_title", "story_description", "reopen_note", "repo", "branch", "prior_handoff"])
56
+ check(field, isString, "string", false);
57
+ check("resumed_after_interrupt", (value) => typeof value === "boolean", "boolean", false);
58
+ check("constraints", stringList, "array of strings", false);
59
+ }
60
+ else {
61
+ for (const field of ["story_id", "phase", "worker_id"])
62
+ check(field, isNonEmpty, "non-empty string");
63
+ check("status", (value) => value === "passed" || value === "failed" || value === "blocked", "one of passed|failed|blocked");
64
+ check("summary", isString, "string");
65
+ check("files_changed", stringList, "array of strings");
66
+ check("commits", stringList, "array of strings");
67
+ if (!Object.hasOwn(doc, "back_pressure"))
68
+ problems.push("missing field: back_pressure");
69
+ else if (!isObject(doc.back_pressure))
70
+ problems.push("wrong type: back_pressure (want object)");
71
+ else
72
+ for (const group of ["tests", "lint", "typecheck", "build"]) {
73
+ if (!Object.hasOwn(doc.back_pressure, group))
74
+ problems.push("missing field: back_pressure." + group);
75
+ else if (!["pass", "fail", "skip"].includes(String(doc.back_pressure[group])))
76
+ problems.push("wrong type: back_pressure." + group + " (want one of pass|fail|skip)");
77
+ }
78
+ check("context_for_next", isString, "string");
79
+ for (const field of ["engine", "notes"])
80
+ check(field, isString, "string", false);
81
+ }
82
+ return problems.length ? [false, problems.join("\n")] : [true, "valid " + kind];
83
+ }
84
+ function isObject(value) {
85
+ return value !== null && typeof value === "object" && !Array.isArray(value);
86
+ }
87
+ //# sourceMappingURL=core-pipeline-envelope-native.js.map
@@ -772,6 +772,8 @@ function autoRetire(options, root, cwd, env, now) {
772
772
  reportText += `# Automatic policy retirement — ${today}\n\nMode: human on the loop. Each policy below was retired by \`policy-retire.sh --auto\` with the reason shown.\nReverse any wrong call with \`bash core/scripts/policy-retire.sh <slug> --restore\` and fix the rule that produced it.\n\n`;
773
773
  }
774
774
  reportText += `## Run ${now.toISOString().slice(11, 19)}Z — dirs: ${dirs.join(' ')}\n\n`;
775
+ // Match the shell's preflight: fail to create the report temp before any
776
+ // policy can be retired, then replace it with the complete run section.
775
777
  fs.writeFileSync(tempPath, reportText);
776
778
  let retired = 0;
777
779
  let failed = 0;
@@ -847,6 +849,7 @@ function autoRetire(options, root, cwd, env, now) {
847
849
  stdout += `policy-retire --auto: ${wouldRetire} of ${candidates.length} candidates would be retired (dry run)\n`;
848
850
  }
849
851
  else if (retired > 0 || failed > 0 || needfix > 0) {
852
+ fs.writeFileSync(tempPath, reportText);
850
853
  fs.appendFileSync(outputPath, fs.readFileSync(tempPath));
851
854
  fs.rmSync(tempPath, { force: true });
852
855
  stdout += `policy-retire --auto: ${retired} retired, ${failed} failed, ${needfix} need a fix; report ${displayPolicyPath(outputPath, root)}\n`;
@@ -1,4 +1,3 @@
1
1
  import { type WorkerSkillDependencies, type WorkerSkillResult } from "./core-worker-skills-runtime.js";
2
- export declare const WORKER_SKILL_LINT_ASSET = "core/scripts/lint-shared-worker-skills.sh";
3
2
  export declare function runWorkerSkillLint(args: string[], dependencies: WorkerSkillDependencies): WorkerSkillResult;
4
3
  //# sourceMappingURL=core-worker-lint-native.d.ts.map
@@ -2,7 +2,6 @@
2
2
  import * as crypto from "node:crypto";
3
3
  import * as path from "node:path";
4
4
  import { errorCode, isDirectory, missingOrUnreadable, resolveWorkerCallerRoot, } from "./core-worker-skills-runtime.js";
5
- export const WORKER_SKILL_LINT_ASSET = "core/scripts/lint-shared-worker-skills.sh";
6
5
  const DEFAULT_ROOTS = ["core/workers", "core/packages"];
7
6
  const USAGE = "Usage: lint-shared-worker-skills.sh [root ...]\nDefault roots: core/workers core/packages\n";
8
7
  function joinFindPath(parent, child) {
@@ -1,6 +1,6 @@
1
1
  /** Native port of share-worker-skill.sh. */
2
2
  import * as path from "node:path";
3
- import { resolveBundledAsset } from "../utils/hq-roots.js";
3
+ import { BUNDLED_ASSET_DIR, packageRoot } from "../utils/hq-roots.js";
4
4
  import { existsFollowingLinks, isDirectory, isSymlink, resolveWorkerCallerRoot, runGit, errorCode, } from "./core-worker-skills-runtime.js";
5
5
  export const WORKER_SKILL_SHARE_ASSET = "core/scripts/share-worker-skill.sh";
6
6
  const WORKER_SKILL_SHARE_USAGE = "usage: $0 [--apply] [--force] <scope-dir> <skill-name> <worker-skill-path>...\n";
@@ -88,7 +88,7 @@ function forceSymlink(target, filePath, fileSystem) {
88
88
  fileSystem.symlinkSync(target, filePath);
89
89
  }
90
90
  function runtimeScriptPath(dependencies) {
91
- return dependencies.scriptPath ?? resolveBundledAsset(WORKER_SKILL_SHARE_ASSET);
91
+ return dependencies.scriptPath ?? path.join(packageRoot(), BUNDLED_ASSET_DIR, WORKER_SKILL_SHARE_ASSET);
92
92
  }
93
93
  export function runWorkerSkillShare(argv, dependencies) {
94
94
  let apply = false;
@@ -3,6 +3,6 @@ export type WorkerSkillCommandContext = {
3
3
  cwd: string;
4
4
  hqRoot?: string;
5
5
  };
6
- /** Keep the shell rollback route byte-transparent while the family flag is off. */
6
+ /** Run the native implementation for the worker-skills family. */
7
7
  export declare function runWorkerSkillCommand(name: WorkerSkillCommandName, args: string[], context: WorkerSkillCommandContext): Promise<number>;
8
8
  //# sourceMappingURL=core-worker-skills-command.d.ts.map
@@ -1,24 +1,7 @@
1
- import { runBundledScript } from "../utils/run-bundled-script.js";
2
- import { coreNativeWorkerSkillsEnabled } from "./core-native-worker-skills-flag.js";
3
- import { runWorkerSkillLint, WORKER_SKILL_LINT_ASSET } from "./core-worker-lint-native.js";
4
- import { runWorkerSkillShare, WORKER_SKILL_SHARE_ASSET } from "./core-worker-share-native.js";
5
- const assets = {
6
- lint: WORKER_SKILL_LINT_ASSET,
7
- share: WORKER_SKILL_SHARE_ASSET,
8
- };
9
- /** Keep the shell rollback route byte-transparent while the family flag is off. */
1
+ import { runWorkerSkillLint } from "./core-worker-lint-native.js";
2
+ import { runWorkerSkillShare } from "./core-worker-share-native.js";
3
+ /** Run the native implementation for the worker-skills family. */
10
4
  export async function runWorkerSkillCommand(name, args, context) {
11
- if (!(await coreNativeWorkerSkillsEnabled())) {
12
- const result = runBundledScript({
13
- asset: assets[name],
14
- args,
15
- cwd: context.cwd,
16
- hqRoot: context.hqRoot,
17
- });
18
- if (result.code !== 0)
19
- process.exit(result.code);
20
- return result.code;
21
- }
22
5
  const result = name === "lint"
23
6
  ? runWorkerSkillLint(args, { cwd: context.cwd })
24
7
  : runWorkerSkillShare(args, { cwd: context.cwd });
@@ -75,9 +75,13 @@ export type RebuildIndexTarget = {
75
75
  /** One-line summary, shown in `hq core --help`. */
76
76
  summary: string;
77
77
  };
78
- export type WorkerSubcommand = ScaffoldAsset & {
78
+ export type WorkerSubcommand = {
79
79
  /** Subcommand name under the `worker` group — `hq core worker <name>`. */
80
80
  name: string;
81
+ /** Caller-relative root used by the native command. */
82
+ root: "cwd";
83
+ /** One-line summary, shown in `hq core --help`. */
84
+ summary: string;
81
85
  };
82
86
  /**
83
87
  * Native commands that still claim a scaffold forwarder asset. The forwarder
@@ -51,6 +51,7 @@ import { DERIVE_TRIGGER_FACTS_ASSET } from "./core-derive-trigger-facts-native.j
51
51
  import { registerCoreHookStateHelpers } from "./core-hook-state-helpers-native.js";
52
52
  import { registerCoreHookBatchCommand } from "./core-hook-batch-command.js";
53
53
  import { registerCorePipelineWorkerTableCommand } from "./core-pipeline-workers-command.js";
54
+ import { registerCorePipelineRuntimeCommands } from "./core-pipeline-conductor-command.js";
54
55
  import { resolveLiveRoot } from "../utils/hq-roots.js";
55
56
  import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
56
57
  import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
@@ -132,13 +133,11 @@ export const NATIVE_SCAFFOLD_COMMANDS = [
132
133
  export const WORKER_SUBCOMMANDS = [
133
134
  {
134
135
  name: "lint",
135
- asset: "core/scripts/lint-shared-worker-skills.sh",
136
136
  root: "cwd",
137
137
  summary: "Fail on worker-scoped skills duplicated by copy instead of single-sourced",
138
138
  },
139
139
  {
140
140
  name: "share",
141
- asset: "core/scripts/share-worker-skill.sh",
142
141
  root: "cwd",
143
142
  summary: "Migrate duplicated worker skills onto one canonical file + relative symlinks",
144
143
  },
@@ -307,6 +306,10 @@ export const SCAFFOLD_ONLY_ASSETS = [
307
306
  { asset: "core/scripts/handoff-post.sh", root: "live", summary: "Rollback shell for native handoff post" },
308
307
  { asset: "core/scripts/handoff-open-steps.sh", root: "live", summary: "Rollback shell for native handoff open-steps" },
309
308
  { asset: "core/scripts/qmd-reindex-bg.sh", root: "live", summary: "Rollback handoff reindex forwarder" },
309
+ // Pipeline rollback companions remain bundled until the family removal PR.
310
+ { asset: "core/scripts/pipeline-conductor.sh", root: "live", summary: "Rollback shell for native pipeline conductor" },
311
+ { asset: "core/scripts/pipeline-driver.sh", root: "live", summary: "Rollback shell for native pipeline driver" },
312
+ { asset: "core/scripts/pipeline-envelope.sh", root: "live", summary: "Validation helper for the pipeline shell rollback companions" },
310
313
  { asset: "core/scripts/lib/portable.sh", root: "live", summary: "Shared portable helpers sourced by bundled scripts" },
311
314
  ...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
312
315
  asset,
@@ -324,7 +327,6 @@ export const SCAFFOLD_ASSETS = [
324
327
  ...SCAFFOLD_COMMANDS,
325
328
  ...NATIVE_SCAFFOLD_COMMANDS,
326
329
  ...SCAFFOLD_ONLY_ASSETS,
327
- ...WORKER_SUBCOMMANDS,
328
330
  ];
329
331
  /**
330
332
  * Resolve the tree an entry runs against, and the cwd to run it in.
@@ -404,6 +406,7 @@ export function registerCoreCommands(program) {
404
406
  registerCoreDeriveTriggerFactsCommand(core);
405
407
  registerCoreHookStateHelpers(core);
406
408
  registerCoreHookBatchCommand(core);
409
+ registerCorePipelineRuntimeCommands(core);
407
410
  const sessionHelperCommands = [
408
411
  {
409
412
  name: "session-journal", asset: "core/scripts/session-journal.sh", root: "live",
@@ -555,7 +558,7 @@ export function registerCoreCommands(program) {
555
558
  const command = worker
556
559
  .command(entry.name)
557
560
  .description(entry.summary)
558
- // Pure passthrough: the wrapped script owns its own argument grammar.
561
+ // Pure passthrough: the native handler owns the worker command grammar.
559
562
  .allowUnknownOption()
560
563
  .allowExcessArguments()
561
564
  .helpOption(false)
@@ -565,11 +568,7 @@ export function registerCoreCommands(program) {
565
568
  const operands = cmd.args.length > 0 ? cmd.args : args;
566
569
  await runWorkerSkillCommand(entry.name, operands, resolveEntryRoot(entry, scope));
567
570
  });
568
- describeCoreCommand(command, {
569
- execution: interpreterFor(entry.asset) === "node" ? "bundled-node" : "bundled-shell",
570
- asset: entry.asset,
571
- root: entry.root,
572
- });
571
+ describeCoreCommand(command, { execution: "native", root: entry.root });
573
572
  }
574
573
  const runCommand = (entry, args = [], cmd, target) => {
575
574
  const scope = core.opts();
@@ -257,6 +257,12 @@ export interface UsableAppsResult {
257
257
  viewerAccessKnown: boolean;
258
258
  apps: UsableApp[];
259
259
  }
260
+ /**
261
+ * Built-in providers expose their tools under their own names
262
+ * (`google.drive.picked.list`), not through the factory passthrough
263
+ * `<prefix>.mcp.tools.call`, which the gateway never registers for them.
264
+ */
265
+ export declare function isBuiltInGatewayProvider(provider: string): boolean;
260
266
  /**
261
267
  * The apps the caller may use, from `GET /v1/integrations/admin?view=summary`.
262
268
  * A row counts only when all of these hold, mirroring the gateway's checks:
@@ -296,7 +302,7 @@ export declare function resolveConnection(token: string, companyUid: string, app
296
302
  }, recoveryOpts?: {
297
303
  allowSingleRevoked?: boolean;
298
304
  }): Promise<AdminConnection>;
299
- export declare function callGateway(token: string, params: Record<string, unknown>, idempotencyKey?: string): Promise<GatewayMessage>;
305
+ export declare function callGateway(token: string, params: Record<string, unknown>, idempotencyKey?: string, method?: "tools/call" | "tools/list"): Promise<GatewayMessage>;
300
306
  /**
301
307
  * Gateway results arrive MCP-style: `{ content: [{ type: "text", text }] }`
302
308
  * where `text` is the provider's JSON. Unwrap to the inner payload; fall back
@@ -368,6 +368,14 @@ const GATEWAY_SERVED_STATUSES = new Set(["connected", "needs-attention"]);
368
368
  * gateway as `ProviderUnsupported`.
369
369
  */
370
370
  const STATIC_GATEWAY_PROVIDERS = new Set(["google-workspace", "linear", "slack"]);
371
+ /**
372
+ * Built-in providers expose their tools under their own names
373
+ * (`google.drive.picked.list`), not through the factory passthrough
374
+ * `<prefix>.mcp.tools.call`, which the gateway never registers for them.
375
+ */
376
+ export function isBuiltInGatewayProvider(provider) {
377
+ return STATIC_GATEWAY_PROVIDERS.has(provider);
378
+ }
371
379
  const MAX_APP_NAME_LENGTH = 60;
372
380
  /**
373
381
  * Display names are set by whoever installed the app and end up in agent
@@ -561,7 +569,9 @@ export async function resolveConnection(token, companyUid, app, opts, recoveryOp
561
569
  }
562
570
  return selectConnection(connections, opts);
563
571
  }
564
- function gatewaySubcommandFor(params) {
572
+ function gatewaySubcommandFor(params, method) {
573
+ if (method === "tools/list")
574
+ return "tools_list";
565
575
  const name = params.name;
566
576
  if (typeof name !== "string")
567
577
  return "other";
@@ -569,10 +579,13 @@ function gatewaySubcommandFor(params) {
569
579
  return "tools_list";
570
580
  if (name.endsWith(".mcp.tools.call"))
571
581
  return "tools_call";
582
+ // Built-in providers are called by the tool's own name, with no passthrough.
583
+ if (!name.includes(".mcp."))
584
+ return "tools_call";
572
585
  return "other";
573
586
  }
574
- export async function callGateway(token, params, idempotencyKey) {
575
- const gatewaySubcommand = gatewaySubcommandFor(params);
587
+ export async function callGateway(token, params, idempotencyKey, method = "tools/call") {
588
+ const gatewaySubcommand = gatewaySubcommandFor(params, method);
576
589
  const deadline = Date.now() + ASYNC_GATEWAY_DEADLINE_MS;
577
590
  const res = await vaultApiFetch({
578
591
  token,
@@ -581,7 +594,7 @@ export async function callGateway(token, params, idempotencyKey) {
581
594
  body: {
582
595
  jsonrpc: "2.0",
583
596
  id: `hq-cli-${randomUUID()}`,
584
- method: "tools/call",
597
+ method,
585
598
  params,
586
599
  },
587
600
  });
@@ -54,12 +54,39 @@ import chalk from "chalk";
54
54
  import { ensureCognitoIdToken } from "../utils/cognito-session.js";
55
55
  import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
56
56
  import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
57
- import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, fetchUsableApps, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
57
+ import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, fetchUsableApps, isBuiltInGatewayProvider, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
58
58
  import { registerConnectCommands } from "./integrations-connect.js";
59
59
  import { registerImportCommands } from "./integrations-import.js";
60
60
  import { registerManageCommands } from "./integrations-manage.js";
61
61
  import { registerSlackCommands } from "./integrations-slack.js";
62
62
  import { fetchPendingApprovals } from "./integrations-api.js";
63
+ /** Routing ids hq sets on a built-in tool call; never accepted from --args. */
64
+ const BUILT_IN_ROUTING_ARGS = ["companyUid", "connectionId", "idempotencyKey"];
65
+ /**
66
+ * hq sets the routing ids on every built-in call and refuses them in --args,
67
+ * so discovery must not list them as inputs the caller provides.
68
+ */
69
+ function withoutRoutingArgs(tool) {
70
+ const schema = tool.inputSchema;
71
+ if (!schema || typeof schema !== "object" || Array.isArray(schema))
72
+ return tool;
73
+ const { properties, required, ...rest } = schema;
74
+ const routing = new Set(BUILT_IN_ROUTING_ARGS);
75
+ const next = { ...rest };
76
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
77
+ next.properties = Object.fromEntries(Object.entries(properties).filter(([key]) => !routing.has(key)));
78
+ }
79
+ else if (properties !== undefined) {
80
+ next.properties = properties;
81
+ }
82
+ if (Array.isArray(required)) {
83
+ next.required = required.filter((key) => !(typeof key === "string" && routing.has(key)));
84
+ }
85
+ else if (required !== undefined) {
86
+ next.required = required;
87
+ }
88
+ return { ...tool, inputSchema: next };
89
+ }
63
90
  export { IntegrationsCliError, callGateway, fetchConnections, fetchUsableApps, queuedOutcome, selectConnection, toolPrefixForProvider, unwrapGatewayResult, } from "./integrations-core.js";
64
91
  /**
65
92
  * Gateway metadata is additive and may be absent when talking to an older
@@ -285,16 +312,33 @@ export function registerIntegrationsCommand(program) {
285
312
  .action(async (opts) => {
286
313
  const token = await ensureCognitoIdToken();
287
314
  const companyUid = await getCompanyUid(token, opts.company);
288
- const connection = selectConnection(await fetchConnections(token, companyUid), opts);
315
+ const connections = await fetchConnections(token, companyUid);
316
+ const connection = selectConnection(connections, opts);
289
317
  if (reportRevokedConnection(connection, opts))
290
318
  return;
291
- const prefix = toolPrefixForProvider(connection.provider);
292
- const message = await callGateway(token, {
293
- companyUid,
294
- name: `${prefix}.mcp.tools.list`,
295
- arguments: { companyUid, connectionId: connection.id },
296
- });
297
- const payload = unwrapGatewayResult(message.result);
319
+ let payload;
320
+ const builtIn = isBuiltInGatewayProvider(connection.provider);
321
+ if (builtIn) {
322
+ // Built-in providers answer the gateway's own tools/list, which spans
323
+ // every connection in the company; keep only this connection's tools.
324
+ const message = await callGateway(token, { companyUid }, undefined, "tools/list");
325
+ const listed = unwrapGatewayResult(message.result);
326
+ payload = {
327
+ ...listed,
328
+ tools: (listed?.tools ?? [])
329
+ .filter((tool) => tool.annotations?.connectionId === connection.id)
330
+ .map(withoutRoutingArgs),
331
+ };
332
+ }
333
+ else {
334
+ const prefix = toolPrefixForProvider(connection.provider);
335
+ const message = await callGateway(token, {
336
+ companyUid,
337
+ name: `${prefix}.mcp.tools.list`,
338
+ arguments: { companyUid, connectionId: connection.id },
339
+ });
340
+ payload = unwrapGatewayResult(message.result);
341
+ }
298
342
  const tools = payload?.tools ?? [];
299
343
  if (tools.length === 0) {
300
344
  if (opts.json) {
@@ -331,7 +375,14 @@ export function registerIntegrationsCommand(program) {
331
375
  const badge = formatToolBadge(tool);
332
376
  console.log(`${chalk.bold(tool.name)}${badge ? ` ${badge}` : ""}${label}${required ? ` ${chalk.dim(required)}` : ""}`);
333
377
  }
334
- console.log(chalk.dim(`\n${tools.length} tools. Describe inputs: hq integrations tools --provider ${bareProvider(connection.provider)} --describe <tool>\nCall one with: hq integrations call <tool> --provider ${bareProvider(connection.provider)} --args '<json>'`));
378
+ // `--provider` resolves to the first matching connection, so when
379
+ // several connections share this provider the hint must name this one.
380
+ const sharedProvider = builtIn &&
381
+ connections.some((c) => c.id !== connection.id && c.status !== "revoked" && c.provider === connection.provider);
382
+ const selector = sharedProvider
383
+ ? `--connection ${connection.id}`
384
+ : `--provider ${bareProvider(connection.provider)}`;
385
+ console.log(chalk.dim(`\n${tools.length} tools. Describe inputs: hq integrations tools ${selector} --describe <tool>\nCall one with: hq integrations call <tool> ${selector} --args '<json>'`));
335
386
  });
336
387
  integrations
337
388
  .command("call <tool>")
@@ -359,20 +410,38 @@ export function registerIntegrationsCommand(program) {
359
410
  const connection = selectConnection(await fetchConnections(token, companyUid), opts);
360
411
  if (reportRevokedConnection(connection, { ...opts, fail: true }))
361
412
  return;
362
- const prefix = toolPrefixForProvider(connection.provider);
363
413
  const idempotencyKey = opts.idempotencyKey ?? `hq-cli-${randomUUID()}`;
364
- const message = await callGateway(token, {
365
- companyUid,
366
- acceptAsync: true,
367
- name: `${prefix}.mcp.tools.call`,
368
- arguments: {
414
+ let gatewayParams;
415
+ if (isBuiltInGatewayProvider(connection.provider)) {
416
+ // Built-in tools take their args flat beside the routing ids, so an
417
+ // arg with a routing name would redirect the call; refuse it.
418
+ const reserved = BUILT_IN_ROUTING_ARGS.filter((key) => key in parsedArgs);
419
+ if (reserved.length) {
420
+ throw new IntegrationsCliError(`--args cannot include ${reserved.join(", ")}: it is set by hq from the selected connection.`, { expected: true });
421
+ }
422
+ gatewayParams = {
423
+ companyUid,
424
+ acceptAsync: true,
425
+ name: tool,
426
+ arguments: { ...parsedArgs, companyUid, connectionId: connection.id, idempotencyKey },
427
+ };
428
+ }
429
+ else {
430
+ const prefix = toolPrefixForProvider(connection.provider);
431
+ gatewayParams = {
369
432
  companyUid,
370
- connectionId: connection.id,
371
- toolName: tool,
372
- arguments: parsedArgs,
373
- idempotencyKey,
374
- },
375
- }, idempotencyKey);
433
+ acceptAsync: true,
434
+ name: `${prefix}.mcp.tools.call`,
435
+ arguments: {
436
+ companyUid,
437
+ connectionId: connection.id,
438
+ toolName: tool,
439
+ arguments: parsedArgs,
440
+ idempotencyKey,
441
+ },
442
+ };
443
+ }
444
+ const message = await callGateway(token, gatewayParams, idempotencyKey);
376
445
  const payload = unwrapGatewayResult(message.result);
377
446
  const queued = queuedOutcome(payload);
378
447
  if (queued) {
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Live HQ Storage gate and refusal surface for `hq storage`.
3
3
  *
4
- * Allowlist gate, fails closed (owner decision 2026-10-11, Indigo-only until
5
- * the owner widens it; see the storage-feature-flag module in src/lib). This
6
- * is the deliberate exception to the default-on kill-switch rule: any error,
4
+ * Registry gate, fails closed. The `hq-storage` registry row decides who has
5
+ * Storage; it defaults on for every signed-in account (widened 2026-10-13
6
+ * from the 2026-10-11 Indigo-only start; see the storage-feature-flag module
7
+ * in src/lib). The fail-closed client fallback is the deliberate exception to the default-on kill-switch rule: any error,
7
8
  * a signed-out caller, an API-key caller, a membership read failure, or an
8
9
  * unreadable registry all mean Storage is OFF.
9
10
  *
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Live HQ Storage gate and refusal surface for `hq storage`.
3
3
  *
4
- * Allowlist gate, fails closed (owner decision 2026-10-11, Indigo-only until
5
- * the owner widens it; see the storage-feature-flag module in src/lib). This
6
- * is the deliberate exception to the default-on kill-switch rule: any error,
4
+ * Registry gate, fails closed. The `hq-storage` registry row decides who has
5
+ * Storage; it defaults on for every signed-in account (widened 2026-10-13
6
+ * from the 2026-10-11 Indigo-only start; see the storage-feature-flag module
7
+ * in src/lib). The fail-closed client fallback is the deliberate exception to the default-on kill-switch rule: any error,
7
8
  * a signed-out caller, an API-key caller, a membership read failure, or an
8
9
  * unreadable registry all mean Storage is OFF.
9
10
  *
@@ -278,8 +278,6 @@ export const FAST_SCAFFOLD_SCRIPTS = [
278
278
  modulePath: "./commands/core-worker-lint-fast.js",
279
279
  exportName: "runWorkerSkillLintFast",
280
280
  flagKey: "hq-core-native-fast-route",
281
- familyFlagKey: "core-native-worker-skills",
282
- asset: "core/scripts/lint-shared-worker-skills.sh",
283
281
  root: "cwd",
284
282
  shellCwd: "caller",
285
283
  shellHqRoot: "none",
@@ -1,13 +1,13 @@
1
1
  /**
2
- * HQ Storage allowlist gate (hq-flags key `hq-storage`).
2
+ * HQ Storage gate (hq-flags key `hq-storage`).
3
3
  *
4
4
  * Owner decision (Corey, 2026-10-11): "let's fully ship the storage feature
5
- * but all behind a feature flag for indigo users only." That is an explicit
6
- * hold on the default-on kill-switch rule
7
- * (`indigo-feature-flags-default-on-as-fail-safes`): the registry row is
8
- * `defaultValue: false` with a company override for Indigo, so this gate is
9
- * an ALLOWLIST, not a kill switch. It therefore FAILS CLOSED: a missing,
10
- * stale, discovered, archived or unreadable value means Storage is off.
5
+ * but all behind a feature flag for indigo users only." The registry row
6
+ * started as `defaultValue: false` with a company override for Indigo. It
7
+ * was widened on 2026-10-13: the row now defaults to true, so Storage is on
8
+ * for every signed-in account and the Indigo override is redundant. The
9
+ * client still FAILS CLOSED: a missing, stale, discovered, archived or
10
+ * unreadable value means Storage is off.
11
11
  *
12
12
  * Who it is on for is decided by hq-pro's shared evaluator (person override,
13
13
  * then company override by uid, then slug, then default), resolved
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.117",
3
+ "version": "5.345.119",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {