@indigoai-us/hq-cli 5.345.169 → 5.345.171

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 (140) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
  3. package/dist/command-catalog.generated.d.ts +181 -9
  4. package/dist/command-catalog.generated.js +224 -10
  5. package/dist/commands/access.d.ts +32 -1
  6. package/dist/commands/access.js +119 -5
  7. package/dist/commands/agent-kit.d.ts +1 -1
  8. package/dist/commands/agent-kit.js +3 -1
  9. package/dist/commands/agents.js +2 -1
  10. package/dist/commands/bot.d.ts +3 -0
  11. package/dist/commands/bot.js +141 -14
  12. package/dist/commands/cloud.d.ts +78 -1
  13. package/dist/commands/cloud.js +274 -9
  14. package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
  15. package/dist/commands/core-company-index-scope-flag.js +9 -2
  16. package/dist/commands/core-delegate-w1b.js +5 -2
  17. package/dist/commands/core-dm-bind-native.js +19 -4
  18. package/dist/commands/core-rebuild-index-fast.js +24 -6
  19. package/dist/commands/core.js +18 -6
  20. package/dist/commands/dm.d.ts +17 -3
  21. package/dist/commands/dm.js +121 -26
  22. package/dist/commands/feedback-triage.d.ts +43 -1
  23. package/dist/commands/feedback-triage.js +123 -23
  24. package/dist/commands/feedback.d.ts +40 -1
  25. package/dist/commands/feedback.js +184 -4
  26. package/dist/commands/files.d.ts +42 -0
  27. package/dist/commands/files.js +223 -30
  28. package/dist/commands/integrations-api.d.ts +4 -0
  29. package/dist/commands/integrations-api.js +18 -2
  30. package/dist/commands/integrations-connect.d.ts +10 -3
  31. package/dist/commands/integrations-connect.js +70 -6
  32. package/dist/commands/integrations-core.d.ts +23 -0
  33. package/dist/commands/integrations-core.js +100 -0
  34. package/dist/commands/integrations-manage.js +8 -1
  35. package/dist/commands/integrations.js +49 -5
  36. package/dist/commands/lanes-loop.d.ts +20 -0
  37. package/dist/commands/lanes-loop.js +69 -11
  38. package/dist/commands/lanes.js +9 -1
  39. package/dist/commands/mesh.js +6 -0
  40. package/dist/commands/rescue.d.ts +9 -0
  41. package/dist/commands/rescue.js +31 -0
  42. package/dist/commands/run.js +115 -40
  43. package/dist/commands/scan.d.ts +5 -1
  44. package/dist/commands/scan.js +16 -4
  45. package/dist/commands/secrets.js +50 -9
  46. package/dist/commands/sync-manifest.d.ts +16 -0
  47. package/dist/commands/sync-manifest.js +17 -1
  48. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
  49. package/dist/hq-cloud-sync-runner/package.json +1 -1
  50. package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
  51. package/dist/lib/agent-kit/inbox-state.js +41 -20
  52. package/dist/lib/agent-kit/kit-config.d.ts +2 -0
  53. package/dist/lib/agent-kit/kit-config.js +6 -0
  54. package/dist/lib/agent-kit/mcp/tools.js +12 -5
  55. package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
  56. package/dist/lib/agent-kit/run/heartbeat.js +6 -3
  57. package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
  58. package/dist/lib/agent-kit/run/inbox.js +42 -10
  59. package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
  60. package/dist/lib/agent-kit/run/request-timeout.js +51 -0
  61. package/dist/lib/bot/api.d.ts +1 -1
  62. package/dist/lib/bot/config.d.ts +10 -0
  63. package/dist/lib/bot/config.js +17 -1
  64. package/dist/lib/bot/decisions.d.ts +62 -0
  65. package/dist/lib/bot/decisions.js +228 -0
  66. package/dist/lib/bot/fleet-local.d.ts +127 -0
  67. package/dist/lib/bot/fleet-local.js +638 -0
  68. package/dist/lib/bot/index.d.ts +2 -0
  69. package/dist/lib/bot/index.js +2 -0
  70. package/dist/lib/bot/local-config.d.ts +3 -2
  71. package/dist/lib/bot/local-config.js +5 -2
  72. package/dist/lib/bot/promote.d.ts +10 -0
  73. package/dist/lib/bot/promote.js +3 -2
  74. package/dist/lib/bot/run.d.ts +7 -0
  75. package/dist/lib/bot/run.js +104 -1
  76. package/dist/lib/bot/runtime/index.js +8 -4
  77. package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
  78. package/dist/lib/bot/runtime/scratch-dir.js +41 -0
  79. package/dist/lib/caller-principal.d.ts +4 -0
  80. package/dist/lib/caller-principal.js +40 -0
  81. package/dist/lib/capacity/collectors/grok.js +8 -2
  82. package/dist/lib/cli-flag-keys.d.ts +2 -0
  83. package/dist/lib/cli-flag-keys.js +2 -0
  84. package/dist/lib/daemon/catalog.d.ts +4 -0
  85. package/dist/lib/daemon/catalog.js +63 -29
  86. package/dist/lib/daemon/config.d.ts +7 -0
  87. package/dist/lib/daemon/config.js +32 -0
  88. package/dist/lib/daemon/run.d.ts +3 -2
  89. package/dist/lib/daemon/run.js +23 -3
  90. package/dist/lib/daemon/supervisor.d.ts +1 -0
  91. package/dist/lib/daemon/supervisor.js +4 -0
  92. package/dist/lib/daemon/sync-core-canary.d.ts +12 -0
  93. package/dist/lib/daemon/sync-core-canary.js +61 -0
  94. package/dist/lib/doctor/checks/release-links.d.ts +62 -1
  95. package/dist/lib/doctor/checks/release-links.js +188 -2
  96. package/dist/lib/doctor/checks/sync-health.d.ts +32 -0
  97. package/dist/lib/doctor/checks/sync-health.js +54 -1
  98. package/dist/lib/doctor/fix/apply.d.ts +26 -4
  99. package/dist/lib/doctor/fix/apply.js +149 -11
  100. package/dist/lib/doctor/fix/remediation.d.ts +8 -1
  101. package/dist/lib/doctor/fix/remediation.js +22 -0
  102. package/dist/lib/doctor/types.d.ts +1 -1
  103. package/dist/lib/doctor/types.js +1 -0
  104. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  105. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  106. package/dist/lib/index-render/index.d.ts +9 -0
  107. package/dist/lib/index-render/index.js +65 -5
  108. package/dist/lib/index-render/session-scope.d.ts +69 -0
  109. package/dist/lib/index-render/session-scope.js +279 -0
  110. package/dist/lib/lanes/admission.js +7 -20
  111. package/dist/lib/lanes/reconcile.js +13 -15
  112. package/dist/lib/lanes/stale-lock.d.ts +50 -0
  113. package/dist/lib/lanes/stale-lock.js +105 -0
  114. package/dist/lib/lanes/store.js +11 -76
  115. package/dist/lib/mesh/live/daemon/work-subscribe.js +31 -0
  116. package/dist/lib/notify-recipient.d.ts +10 -4
  117. package/dist/lib/notify-recipient.js +16 -4
  118. package/dist/lib/secret-input-link.d.ts +42 -0
  119. package/dist/lib/secret-input-link.js +93 -0
  120. package/dist/lib/secret-scan/range.d.ts +57 -0
  121. package/dist/lib/secret-scan/range.js +266 -0
  122. package/dist/lib/secret-scan/repo-config.d.ts +18 -3
  123. package/dist/lib/secret-scan/repo-config.js +135 -2
  124. package/dist/lib/work-context/organize.d.ts +7 -0
  125. package/dist/lib/work-context/organize.js +21 -1
  126. package/dist/run/script-bound-schema.d.ts +27 -0
  127. package/dist/run/script-bound-schema.js +97 -0
  128. package/dist/secrets-env-action.d.ts +2 -0
  129. package/dist/secrets-env-action.js +7 -2
  130. package/dist/sentry-before-send.js +1 -0
  131. package/dist/utils/cognito-session.d.ts +3 -30
  132. package/dist/utils/cognito-session.js +2 -100
  133. package/dist/utils/machine-principal-state.d.ts +38 -0
  134. package/dist/utils/machine-principal-state.js +110 -0
  135. package/dist/utils/safe-link.js +9 -0
  136. package/dist/utils/script-bound-exec.d.ts +158 -0
  137. package/dist/utils/script-bound-exec.js +568 -0
  138. package/dist/utils/sync-watchdog.d.ts +92 -0
  139. package/dist/utils/sync-watchdog.js +250 -0
  140. package/package.json +8 -8
@@ -153,7 +153,7 @@ export function buildKitRuntime(service) {
153
153
  process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
154
154
  process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
155
155
  const log = createKitLogger(paths, service, { echo: process.stdout.isTTY === true });
156
- const getToken = () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
156
+ const getToken = (signal) => ensureCognitoToken({ tokenSource: "machine", interactive: false, ...(signal ? { signal } : {}) });
157
157
  return { paths, creds, config, getToken, log, credsSource };
158
158
  }
159
159
  export async function runKitService(service, rt) {
@@ -177,6 +177,7 @@ export async function runKitService(service, rt) {
177
177
  },
178
178
  apiBaseUrl: rt.creds.apiBaseUrl,
179
179
  intervalMs: rt.config.heartbeatIntervalMs,
180
+ requestTimeoutMs: rt.config.requestTimeoutMs,
180
181
  getToken: rt.getToken,
181
182
  log: rt.log,
182
183
  components: enabledKitComponents(rt.config),
@@ -192,6 +193,7 @@ export async function runKitService(service, rt) {
192
193
  agentUid: rt.creds.entityUid,
193
194
  apiBaseUrl: rt.creds.apiBaseUrl,
194
195
  pollMs: rt.config.inboxPollMs,
196
+ requestTimeoutMs: rt.config.requestTimeoutMs,
195
197
  ack: rt.config.inboxAck,
196
198
  getToken: rt.getToken,
197
199
  log: rt.log,
@@ -43,6 +43,7 @@ import { launchSessionManagerPlugin, SessionManagerPluginLaunchError, TerminalSe
43
43
  import { peekIdToken } from "../utils/id-token.js";
44
44
  import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, } from "../utils/plan-gate-error.js";
45
45
  import { AGENT_UID_PATTERN, defaultAudienceFor, isAgentUid } from "../lib/notify-recipient.js";
46
+ import { callerIsMachinePrincipal } from "../lib/caller-principal.js";
46
47
  import { AGENT_MODEL_PROVIDERS, modelSelectionErrorMessage, readAgentModelCatalog, validateAgentModelSelection, } from "../lib/agents/model-catalog.js";
47
48
  import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyBoxSentence, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
48
49
  import { BUNDLED_USAGE_CARD, fetchUsageCard, } from "../lib/billing/usage-card.js";
@@ -1139,7 +1140,7 @@ export async function sendAgentDm(token, agentUid, message) {
1139
1140
  token,
1140
1141
  path: "/v1/notify/dm",
1141
1142
  method: "POST",
1142
- body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent") },
1143
+ body: { toPersonUid: agentUid, body: message, audience: defaultAudienceFor("agent", callerIsMachinePrincipal()) },
1143
1144
  });
1144
1145
  }
1145
1146
  /** Read the two-way DM conversation with an agent (most recent `limit`). */
@@ -109,6 +109,7 @@ export declare function resolveCreateKind(opts: {
109
109
  };
110
110
  export interface BotCreateOptions {
111
111
  runtime?: string;
112
+ hqFleetRuntime?: boolean;
112
113
  model?: string;
113
114
  effort?: string;
114
115
  autoApprove?: boolean;
@@ -343,6 +344,8 @@ export interface RemoteBotRow {
343
344
  displayName: string | null;
344
345
  /** "assistant" for HQ's setup assistant, else null. */
345
346
  role: BotRole;
347
+ /** The owner's person uid exactly as GET /v1/agents/mine sends it; null when the server omits it. */
348
+ ownerUid: string | null;
346
349
  }
347
350
  export declare function registerBotCommand(program: Command): void;
348
351
  //# sourceMappingURL=bot.d.ts.map
@@ -26,6 +26,7 @@ import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hol
26
26
  * list (online flag), and rm. Secrets are never printed.
27
27
  */
28
28
  import { promoteLocalBot } from "../lib/bot/promote.js";
29
+ import { DecisionsClient } from "../lib/decisions/client.js";
29
30
  import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
30
31
  import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
31
32
  import { followLogFile } from "../lib/log-follow.js";
@@ -51,7 +52,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
51
52
  import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
52
53
  import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
53
54
  import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
54
- import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, } from "../lib/bot/index.js";
55
+ import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, controlFleetLocalBot, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, removeFleetLocalBot, } from "../lib/bot/index.js";
55
56
  function collectRepeated(value, previous) {
56
57
  return [...previous, value];
57
58
  }
@@ -113,6 +114,18 @@ function requireBot(name) {
113
114
  }
114
115
  return { name: valid, dir, config };
115
116
  }
117
+ function isFleetRuntimeBot(config) {
118
+ return config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
119
+ }
120
+ function runFleetRuntimeControl(name, config, action, json) {
121
+ const result = controlFleetLocalBot(config.agentUid, action);
122
+ if (json) {
123
+ console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
124
+ return;
125
+ }
126
+ const summary = typeof result.summary === "string" ? result.summary : `${action} completed`;
127
+ console.log(chalk.green(`${name}: ${summary}`));
128
+ }
116
129
  /** Bot-identity token supplier: per-bot creds + per-bot token cache dir. */
117
130
  export function botTokenSupplier(dir) {
118
131
  process.env.HQ_MACHINE_CREDS_FILE = botCredsPath(dir);
@@ -604,6 +617,10 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
604
617
  console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
605
618
  }
606
619
  async function runBotCreateLocked(name, opts) {
620
+ const useFleetRuntime = opts.hqFleetRuntime === true;
621
+ if (useFleetRuntime && (opts.runtime || opts.model || opts.effort || opts.lean)) {
622
+ throw Object.assign(new Error("--hq-fleet-runtime selects the local HQ runtime. Do not combine it with --runtime, --model, --effort, or --lean."), { expected: true });
623
+ }
607
624
  const runtime = opts.runtime ?? "claude";
608
625
  if (!isBotRuntimeId(runtime)) {
609
626
  throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
@@ -639,6 +656,11 @@ async function runBotCreateLocked(name, opts) {
639
656
  // cost an identity).
640
657
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
641
658
  const { kind, companies } = resolveCreateKind(opts, worker);
659
+ if (useFleetRuntime && kind !== "personal") {
660
+ throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
661
+ expected: true,
662
+ });
663
+ }
642
664
  const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
643
665
  if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
644
666
  throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's assistant; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
@@ -653,6 +675,9 @@ async function runBotCreateLocked(name, opts) {
653
675
  expected: true,
654
676
  });
655
677
  }
678
+ const fleetInstallation = useFleetRuntime
679
+ ? await installPinnedFleetLocalDistribution()
680
+ : null;
656
681
  const token = await ownerToken();
657
682
  const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
658
683
  if (!owner)
@@ -762,6 +787,7 @@ async function runBotCreateLocked(name, opts) {
762
787
  // instead of creating a new one. The machine secret never travels.
763
788
  const localConfig = buildBotLocalConfig({
764
789
  runtime,
790
+ ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
765
791
  kind,
766
792
  ...(kind === "company" ? { companies } : {}),
767
793
  ...(model ? { model } : {}),
@@ -828,6 +854,7 @@ async function runBotCreateLocked(name, opts) {
828
854
  agentUid,
829
855
  ownerUid: created.agent.ownerUid ?? owner,
830
856
  runtime,
857
+ ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
831
858
  kind,
832
859
  // A company bot's list is filled in below, with the invites that went through.
833
860
  ...(kind === "company" ? { companies: [] } : {}),
@@ -884,8 +911,20 @@ async function runBotCreateLocked(name, opts) {
884
911
  const scaffoldOpts = { memoryDir: config.memoryDir };
885
912
  const scaffold = worker
886
913
  ? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
887
- : scaffoldBotWorker(hqRoot, name, agentUid, runtime, undefined, scaffoldOpts);
888
- createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtime}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
914
+ : scaffoldBotWorker(hqRoot, name, agentUid, fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime, undefined, scaffoldOpts);
915
+ const fleetBootstrap = fleetInstallation
916
+ ? bootstrapFleetLocalBot({
917
+ agentUid,
918
+ ownerUid: config.ownerUid,
919
+ hqRoot,
920
+ botDir: dir,
921
+ installation: fleetInstallation,
922
+ machineCredentials: botCredsPath(dir),
923
+ }, { start: opts.start !== false })
924
+ : null;
925
+ const fleetProfile = fleetBootstrap?.profile ?? null;
926
+ const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
927
+ createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtimeLabel}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
889
928
  const created_ = {
890
929
  outcome: "created",
891
930
  role: localBotRole(config),
@@ -894,7 +933,9 @@ async function runBotCreateLocked(name, opts) {
894
933
  agentUid,
895
934
  kind,
896
935
  companies,
897
- runtime,
936
+ runtime: runtimeLabel,
937
+ engine: fleetInstallation?.engine ?? null,
938
+ profile: fleetProfile,
898
939
  dir,
899
940
  worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
900
941
  workerId: worker?.id ?? null,
@@ -905,15 +946,15 @@ async function runBotCreateLocked(name, opts) {
905
946
  intro: intro ?? null,
906
947
  kickoff: kickoff ?? null,
907
948
  model: model ?? null,
908
- effort: effort ?? DEFAULT_BOT_EFFORT,
949
+ effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
909
950
  };
910
951
  let daemonMessage = null;
911
- if (opts.daemon !== false && process.platform === "darwin") {
952
+ if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
912
953
  const r = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot }));
913
954
  daemonMessage = r.message;
914
955
  }
915
956
  if (!opts.json) {
916
- console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtime})`));
957
+ console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtimeLabel})`));
917
958
  console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
918
959
  if (worker)
919
960
  console.log(chalk.dim(` worker: ${worker.id}${worker.company ? ` (${worker.company})` : ""} — ${path.join(hqRoot, worker.path)}`));
@@ -921,9 +962,17 @@ async function runBotCreateLocked(name, opts) {
921
962
  console.log(chalk.dim(` worker: ${scaffold.dir}`));
922
963
  console.log(chalk.dim(` memory: ${scaffold.memoryDir} (${memoryMode})`));
923
964
  console.log(chalk.dim(` state: ${dir}`));
965
+ if (fleetProfile)
966
+ console.log(chalk.dim(` profile: ${fleetProfile}`));
924
967
  if (daemonMessage)
925
968
  console.log(chalk.dim(` ${daemonMessage}`));
926
969
  }
970
+ if (fleetInstallation) {
971
+ if (opts.json) {
972
+ console.log(JSON.stringify({ ok: true, name, ...created_, daemon: null, running: fleetBootstrap?.running ?? false }, null, 2));
973
+ }
974
+ return;
975
+ }
927
976
  if (opts.start !== false) {
928
977
  if (daemonMessage && /loaded\)/.test(daemonMessage)) {
929
978
  // RunAtLoad already started it.
@@ -1063,6 +1112,9 @@ async function runBotResident(nameArg) {
1063
1112
  api,
1064
1113
  runtime: runtimeFor(config.runtime),
1065
1114
  ownerContext: () => owner.get(),
1115
+ // US-005: decision picks in the owner DM, with the bot's own machine token
1116
+ // (never the owner's login).
1117
+ decisions: new DecisionsClient(token),
1066
1118
  // Chat attachments are fetched with the bot's own machine identity against
1067
1119
  // the same API its inbox and posts use (BotApi has no baseUrl override either).
1068
1120
  downloadAttachment: createVaultObjectDownloader({ getToken: token }),
@@ -1123,7 +1175,11 @@ async function runBotList(opts) {
1123
1175
  }
1124
1176
  }
1125
1177
  async function runBotStatus(nameArg, opts) {
1126
- const { name, dir } = requireBot(nameArg);
1178
+ const { name, dir, config } = requireBot(nameArg);
1179
+ if (isFleetRuntimeBot(config)) {
1180
+ runFleetRuntimeControl(name, config, "status", opts.json === true);
1181
+ return;
1182
+ }
1127
1183
  const row = rowFor(name);
1128
1184
  await decorateOnline([row]);
1129
1185
  const status = readBotStatus(dir);
@@ -1322,6 +1378,15 @@ async function runBotRmOrphan(name, opts) {
1322
1378
  else
1323
1379
  console.log(chalk.dim(` HQ may still have a bot named "${name}". Remove that too with: hq bot rm ${name} --remote`));
1324
1380
  }
1381
+ function stopFleetRuntimeBotQuietly(agentUid) {
1382
+ try {
1383
+ controlFleetLocalBot(agentUid, "stop");
1384
+ return true;
1385
+ }
1386
+ catch {
1387
+ return false;
1388
+ }
1389
+ }
1325
1390
  async function runBotRm(nameArg, opts) {
1326
1391
  const validated = validateBotName(nameArg);
1327
1392
  // Under the bot's lock: an rm racing a create, adopt or restore of the same
@@ -1344,9 +1409,13 @@ async function runBotRmLocked(validated, opts) {
1344
1409
  return;
1345
1410
  }
1346
1411
  }
1347
- await stopBot(name, dir, config, "silent").catch(() => { });
1348
- const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
1349
- const daemon = uninstallBotDaemon(paths, daemonDeps());
1412
+ if (isFleetRuntimeBot(config))
1413
+ stopFleetRuntimeBotQuietly(config.agentUid);
1414
+ else
1415
+ await stopBot(name, dir, config, "silent").catch(() => { });
1416
+ const daemon = isFleetRuntimeBot(config)
1417
+ ? { message: "managed by HQ Fleet Runtime" }
1418
+ : uninstallBotDaemon(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
1350
1419
  const token = await ownerToken();
1351
1420
  const api = new BotApi({ token: async () => token });
1352
1421
  let identity;
@@ -1376,8 +1445,11 @@ async function runBotRmLocked(validated, opts) {
1376
1445
  // re-created under the same name. Everything else keeps the folder, so the
1377
1446
  // same command can pick the job back up.
1378
1447
  const localStateRemoved = terminal || incomplete.retryable === false;
1379
- if (localStateRemoved)
1448
+ if (localStateRemoved) {
1449
+ if (isFleetRuntimeBot(config))
1450
+ removeFleetLocalBot(config.agentUid);
1380
1451
  fs.rmSync(dir, { recursive: true, force: true });
1452
+ }
1381
1453
  const message = incomplete
1382
1454
  ? `HQ removed what it could of "${name}", but ${incomplete.detail}.` +
1383
1455
  (incomplete.retryable
@@ -1568,6 +1640,10 @@ export function botExists(name) {
1568
1640
  /** `hq bot restart <name>`: stop, then start (launchd when installed). */
1569
1641
  export async function runBotRestart(nameArg, opts = {}) {
1570
1642
  const b = requireBot(nameArg);
1643
+ if (isFleetRuntimeBot(b.config)) {
1644
+ runFleetRuntimeControl(b.name, b.config, "restart", opts.json === true || opts.output === "json");
1645
+ return;
1646
+ }
1571
1647
  const running = readBotStatus(b.dir);
1572
1648
  if (running && isPidAlive(running.pid) && selfStopProbe(running.pid)) {
1573
1649
  // The stop half would end this turn (and this command) before the
@@ -1577,6 +1653,30 @@ export async function runBotRestart(nameArg, opts = {}) {
1577
1653
  await stopBot(b.name, b.dir, b.config, "silent");
1578
1654
  await startBot(b.name, b.dir, b.config, opts.output ?? (opts.json ? "json" : "text"));
1579
1655
  }
1656
+ async function runBotAuth(nameArg, opts) {
1657
+ const b = requireBot(nameArg);
1658
+ if (!isFleetRuntimeBot(b.config)) {
1659
+ throw Object.assign(new Error(`hq bot auth is for bots created with --hq-fleet-runtime. ${b.name} uses ${b.config.runtime}.`), { expected: true });
1660
+ }
1661
+ const apiKey = opts.apiKey ? fs.readFileSync(0, "utf8").trim() : undefined;
1662
+ if (opts.apiKey && !apiKey) {
1663
+ throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
1664
+ }
1665
+ const result = authenticateFleetLocalBot({
1666
+ agentUid: b.config.agentUid,
1667
+ provider: opts.provider,
1668
+ ...(opts.model ? { model: opts.model } : {}),
1669
+ ...(apiKey ? { apiKey } : {}),
1670
+ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
1671
+ ...(opts.transport ? { transport: opts.transport } : {}),
1672
+ openBrowser: opts.browser !== false,
1673
+ });
1674
+ if (opts.json) {
1675
+ console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
1676
+ return;
1677
+ }
1678
+ console.log(chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`));
1679
+ }
1580
1680
  // ── adopt / restore (bring a bot you own back to this computer) ──────────────
1581
1681
  /**
1582
1682
  * Which computer a re-issue is for. Never a secret and never an identifier the
@@ -2086,6 +2186,7 @@ async function runBotListRemote(opts) {
2086
2186
  reason: runnable.reason,
2087
2187
  displayName: remoteBotDisplayName(bot, name),
2088
2188
  role: remoteBotRole(bot),
2189
+ ownerUid: bot.ownerUid ?? null,
2089
2190
  };
2090
2191
  });
2091
2192
  if (opts.json) {
@@ -2120,7 +2221,13 @@ export function registerBotCommand(program) {
2120
2221
  const { dir, config } = requireBot(name);
2121
2222
  const token = await ownerToken(false);
2122
2223
  const api = new BotApi({ token: async () => token });
2123
- const result = await promoteLocalBot({ dir, config, companyUid: opts.company, api });
2224
+ const result = await promoteLocalBot({
2225
+ dir,
2226
+ config,
2227
+ companyUid: opts.company,
2228
+ api,
2229
+ ...(isFleetRuntimeBot(config) ? { runtime: fleetLocalPromotionRuntime(config.agentUid) } : {}),
2230
+ });
2124
2231
  const details = result.pending ? await api.promotionDetails(config.agentUid, opts.company) : null;
2125
2232
  if (opts.json)
2126
2233
  console.log(JSON.stringify({ ...result, pairing: details?.pairing ?? null, ...(details?.failure ? { failure: details.failure } : {}) }));
@@ -2136,7 +2243,8 @@ export function registerBotCommand(program) {
2136
2243
  .description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
2137
2244
  .option("--kind <kind>", `personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker`)
2138
2245
  .option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
2139
- .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`, "claude")
2246
+ .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
2247
+ .option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
2140
2248
  .option("--model <model>", "Model override passed to the runtime CLI")
2141
2249
  .option("--effort <level>", `Thinking level: claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")} (default ${DEFAULT_BOT_EFFORT})`)
2142
2250
  .option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
@@ -2192,6 +2300,10 @@ export function registerBotCommand(program) {
2192
2300
  .option("--json", "Print machine-readable JSON")
2193
2301
  .action((name, opts) => wrap(async () => {
2194
2302
  const b = requireBot(name);
2303
+ if (isFleetRuntimeBot(b.config)) {
2304
+ runFleetRuntimeControl(b.name, b.config, "start", opts.json === true);
2305
+ return;
2306
+ }
2195
2307
  await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
2196
2308
  })());
2197
2309
  bot
@@ -2200,6 +2312,10 @@ export function registerBotCommand(program) {
2200
2312
  .option("--json", "Print machine-readable JSON")
2201
2313
  .action((name, opts) => wrap(async () => {
2202
2314
  const b = requireBot(name);
2315
+ if (isFleetRuntimeBot(b.config)) {
2316
+ runFleetRuntimeControl(b.name, b.config, "stop", opts.json === true);
2317
+ return;
2318
+ }
2203
2319
  await stopBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
2204
2320
  })());
2205
2321
  bot
@@ -2212,6 +2328,17 @@ export function registerBotCommand(program) {
2212
2328
  .description("Show the bot's process, heartbeat, launchd, and credential state")
2213
2329
  .option("--json", "Print machine-readable JSON")
2214
2330
  .action((name, opts) => wrap(() => runBotStatus(name, opts))());
2331
+ bot
2332
+ .command("auth <name>")
2333
+ .description("Sign a runtime-backed personal bot in to ChatGPT, Grok, Claude, or another supported provider")
2334
+ .requiredOption("--provider <provider>", "Provider: chatgpt, grok, claude, or a runtime provider id")
2335
+ .option("--model <model>", "Model id; required for providers without a built-in personal-bot default")
2336
+ .option("--api-key", "Read an API key from stdin instead of starting device authorization")
2337
+ .option("--base-url <url>", "Provider API base URL; required for custom runtime providers")
2338
+ .option("--transport <transport>", "Provider transport: chat_completions, anthropic_messages, or codex_responses")
2339
+ .option("--no-browser", "Do not open a browser during device authorization")
2340
+ .option("--json", "Print machine-readable JSON")
2341
+ .action((name, opts) => wrap(() => runBotAuth(name, opts))());
2215
2342
  bot
2216
2343
  .command("logs <name>")
2217
2344
  .description("Show the bot's log")
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { Command } from "commander";
16
16
  import { type ConflictStrategy, type EntityContext, type MembershipSyncConfig, type SyncMode, type PullScope, type ExplicitGrant, type ShareOptions, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
17
+ import { withSyncWatchdog, type SyncWatchdogLimits } from "../utils/sync-watchdog.js";
17
18
  import { type BannerLevel } from "../lib/narrow-hint-banner.js";
18
19
  import { type PlanLimitRefusalSummary } from "../lib/billing/plan-limit-refusals.js";
19
20
  /**
@@ -45,7 +46,54 @@ export declare class SyncExitError extends Error {
45
46
  * deliberately duck-typed so hq-cli remains compatible while hq-cloud and the
46
47
  * CLI are released independently.
47
48
  */
48
- export declare function formatSyncFailure(err: unknown): string;
49
+ export declare function formatSyncFailure(err: unknown, ctx?: {
50
+ hqRoot?: string;
51
+ cwd?: string;
52
+ companySlug?: string;
53
+ }): string;
54
+ /**
55
+ * The `.hqinclude` line that admits a named path: its HQ-relative spelling,
56
+ * with a trailing slash for a folder. Resolved the way the push resolver
57
+ * reads a named path (absolute, HQ root, the company folder, then the current
58
+ * directory); a path that resolves nowhere inside the HQ tree is suggested as
59
+ * typed.
60
+ */
61
+ export declare function hqincludeEntryFor(namedPath: string, ctx?: {
62
+ hqRoot?: string;
63
+ cwd?: string;
64
+ companySlug?: string;
65
+ }): string;
66
+ /**
67
+ * Re-render hq-cloud's UnreachablePushPathsError with the real blocker per
68
+ * path (feedback_6afa6ce4). The upstream message always ends with advice
69
+ * about symlinks, so a path left out of `.hqinclude` read as a symlink
70
+ * problem. This names the `.hqinclude` line to add, and keeps the symlink
71
+ * advice only for reasons that are about links or paths outside the company
72
+ * folder. Returns null for any other error.
73
+ */
74
+ export declare function formatUnreachablePushPaths(err: unknown, ctx?: {
75
+ hqRoot?: string;
76
+ cwd?: string;
77
+ companySlug?: string;
78
+ }): string | null;
79
+ /**
80
+ * Lines naming the blocker when a push with user-named paths uploaded nothing
81
+ * and every not-uploaded file fell outside write scope (feedback_5baccb9e).
82
+ * The summary before this gave a count and the granted prefixes; this names
83
+ * each requested path as the thing no write grant covers and points at
84
+ * `hq access`, which reports the ACL row that blocks it.
85
+ */
86
+ export declare function explicitPushBlockedLines(input: {
87
+ userNamedPaths: boolean;
88
+ namedPaths: readonly string[];
89
+ filesUploaded: number;
90
+ filesExcludedByScope: number;
91
+ otherNotUploaded: number;
92
+ prefixSet: readonly string[] | undefined;
93
+ hqRoot: string;
94
+ cwd?: string;
95
+ companySlug?: string;
96
+ }): string[];
49
97
  /**
50
98
  * US-003: run one sync fanout under a client-health attempt/outcome report.
51
99
  * The report is best-effort, bounded, and swallowed — it can only append a
@@ -67,6 +115,26 @@ export declare function withSyncHealthReport<T>(run: () => Promise<T>): Promise<
67
115
  */
68
116
  export declare function scopeExcludedExitCode(count: number): 0 | 1;
69
117
  export declare function scopeExcludedWarning(count: number): string | null;
118
+ /**
119
+ * Counts the files share() held back because this sync scope is fenced after an account
120
+ * switch on this computer (feedback_a69e12ba). share() reports the hold only as
121
+ * an `identity-switch-push-held` event and leaves the files out of every
122
+ * result counter, so without this the push printed "Pushed 0 file(s) (0 B,
123
+ * 0 skipped)" as a success while the vault kept the old copy.
124
+ *
125
+ * Each event carries the size of one fenced hold, so the tracker keeps the
126
+ * largest count it sees instead of a sum: a repeated emit for the same hold
127
+ * cannot inflate the total. A missing, non-numeric or negative count reads as 0.
128
+ */
129
+ export declare function createIdentityHeldTracker(): {
130
+ observe: (event: {
131
+ type: string;
132
+ count?: unknown;
133
+ }) => void;
134
+ count: () => number;
135
+ };
136
+ /** Recovery lines for files held by the account-switch push fence. */
137
+ export declare function identityHeldLines(count: number): string[];
70
138
  /** Actionable recovery for files refused because the vault baseline is stale. */
71
139
  export declare function staleUploadRefusalLines(count: number, paths?: readonly string[]): string[];
72
140
  /**
@@ -108,6 +176,15 @@ export declare function scopeExcludedPrefixDetail(prefixSet: readonly string[] |
108
176
  * folder foo/ — and grant advice would point the reader the wrong way.
109
177
  */
110
178
  export declare function scopeExcludedLines(count: number, prefixSet: readonly string[] | undefined): string[];
179
+ /**
180
+ * feedback_b7c00a62: run a `sync now` runner under the watchdog (see
181
+ * `utils/sync-watchdog.ts` for the limits). A timeout prints a clear message
182
+ * and unwinds as a failed {@link SyncExitError}, so {@link withSyncHealthReport}
183
+ * records the failure and exits 124. The failure report gets at most
184
+ * {@link SYNC_WATCHDOG_REPORT_GRACE_MS}; after that the process exits 124
185
+ * regardless, and the worker backstop stays armed behind both.
186
+ */
187
+ export declare function runUnderSyncWatchdog<T>(limits: SyncWatchdogLimits, label: string, run: () => Promise<T>, watchdog?: typeof withSyncWatchdog, exit?: (code: number) => void): Promise<T>;
111
188
  /**
112
189
  * Bound foreground waits for a watcher/manual sync that currently owns the
113
190
  * per-root operation lock. The cloud engine reads this environment value on