@indigoai-us/hq-cli 5.345.170 → 5.345.172

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 (142) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
  3. package/dist/command-catalog.generated.d.ts +206 -7
  4. package/dist/command-catalog.generated.js +252 -8
  5. package/dist/command-registration-plan.d.ts +7 -0
  6. package/dist/command-registration-plan.js +1 -0
  7. package/dist/commands/access.d.ts +32 -1
  8. package/dist/commands/access.js +112 -3
  9. package/dist/commands/agent-kit.d.ts +1 -1
  10. package/dist/commands/agent-kit.js +3 -1
  11. package/dist/commands/agents.js +2 -1
  12. package/dist/commands/bot.d.ts +3 -0
  13. package/dist/commands/bot.js +179 -18
  14. package/dist/commands/cloud.d.ts +58 -1
  15. package/dist/commands/cloud.js +228 -10
  16. package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
  17. package/dist/commands/core-company-index-scope-flag.js +9 -2
  18. package/dist/commands/core-delegate-w1b.js +5 -2
  19. package/dist/commands/core-dm-bind-native.js +19 -4
  20. package/dist/commands/core-rebuild-index-fast.js +24 -6
  21. package/dist/commands/core.js +18 -6
  22. package/dist/commands/daemon.js +16 -1
  23. package/dist/commands/dm.d.ts +17 -3
  24. package/dist/commands/dm.js +121 -26
  25. package/dist/commands/files.d.ts +1 -0
  26. package/dist/commands/files.js +22 -4
  27. package/dist/commands/integrations-api.d.ts +4 -0
  28. package/dist/commands/integrations-api.js +18 -2
  29. package/dist/commands/integrations-connect.d.ts +10 -3
  30. package/dist/commands/integrations-connect.js +70 -6
  31. package/dist/commands/integrations-core.d.ts +23 -0
  32. package/dist/commands/integrations-core.js +100 -0
  33. package/dist/commands/integrations-manage.js +8 -1
  34. package/dist/commands/integrations.js +49 -5
  35. package/dist/commands/lanes-loop.d.ts +20 -0
  36. package/dist/commands/lanes-loop.js +69 -11
  37. package/dist/commands/lanes.js +9 -1
  38. package/dist/commands/rescue.d.ts +9 -0
  39. package/dist/commands/rescue.js +31 -0
  40. package/dist/commands/run.js +115 -40
  41. package/dist/commands/scan.d.ts +5 -1
  42. package/dist/commands/scan.js +16 -4
  43. package/dist/commands/secrets.js +50 -9
  44. package/dist/commands/sync-core.d.ts +22 -0
  45. package/dist/commands/sync-core.js +124 -0
  46. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2447 -2135
  47. package/dist/hq-cloud-sync-runner/package.json +1 -1
  48. package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
  49. package/dist/lib/agent-kit/inbox-state.js +41 -20
  50. package/dist/lib/agent-kit/kit-config.d.ts +2 -0
  51. package/dist/lib/agent-kit/kit-config.js +6 -0
  52. package/dist/lib/agent-kit/mcp/tools.js +12 -5
  53. package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
  54. package/dist/lib/agent-kit/run/heartbeat.js +6 -3
  55. package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
  56. package/dist/lib/agent-kit/run/inbox.js +42 -10
  57. package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
  58. package/dist/lib/agent-kit/run/request-timeout.js +51 -0
  59. package/dist/lib/bot/api.d.ts +1 -1
  60. package/dist/lib/bot/config.d.ts +10 -0
  61. package/dist/lib/bot/config.js +17 -1
  62. package/dist/lib/bot/fleet-local.d.ts +142 -0
  63. package/dist/lib/bot/fleet-local.js +689 -0
  64. package/dist/lib/bot/fleet-python.d.ts +47 -0
  65. package/dist/lib/bot/fleet-python.js +218 -0
  66. package/dist/lib/bot/index.d.ts +2 -0
  67. package/dist/lib/bot/index.js +2 -0
  68. package/dist/lib/bot/local-config.d.ts +3 -2
  69. package/dist/lib/bot/local-config.js +5 -2
  70. package/dist/lib/bot/promote.d.ts +10 -0
  71. package/dist/lib/bot/promote.js +3 -2
  72. package/dist/lib/bot/prompt.d.ts +6 -1
  73. package/dist/lib/bot/prompt.js +8 -3
  74. package/dist/lib/bot/runtime/index.js +8 -4
  75. package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
  76. package/dist/lib/bot/runtime/scratch-dir.js +41 -0
  77. package/dist/lib/caller-principal.d.ts +4 -0
  78. package/dist/lib/caller-principal.js +40 -0
  79. package/dist/lib/capacity/collectors/grok.js +8 -2
  80. package/dist/lib/cli-flag-keys.d.ts +2 -0
  81. package/dist/lib/cli-flag-keys.js +2 -0
  82. package/dist/lib/daemon/catalog.d.ts +12 -4
  83. package/dist/lib/daemon/catalog.js +70 -24
  84. package/dist/lib/daemon/config.d.ts +0 -7
  85. package/dist/lib/daemon/config.js +0 -32
  86. package/dist/lib/daemon/run.d.ts +5 -1
  87. package/dist/lib/daemon/run.js +48 -11
  88. package/dist/lib/daemon/supervisor.js +3 -1
  89. package/dist/lib/doctor/checks/release-links.d.ts +62 -1
  90. package/dist/lib/doctor/checks/release-links.js +188 -2
  91. package/dist/lib/doctor/fix/apply.js +54 -0
  92. package/dist/lib/doctor/fix/remediation.d.ts +8 -1
  93. package/dist/lib/doctor/fix/remediation.js +22 -0
  94. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  95. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  96. package/dist/lib/index-render/index.d.ts +9 -0
  97. package/dist/lib/index-render/index.js +65 -5
  98. package/dist/lib/index-render/session-scope.d.ts +69 -0
  99. package/dist/lib/index-render/session-scope.js +279 -0
  100. package/dist/lib/lanes/admission.js +7 -20
  101. package/dist/lib/lanes/reconcile.js +13 -15
  102. package/dist/lib/lanes/stale-lock.d.ts +50 -0
  103. package/dist/lib/lanes/stale-lock.js +105 -0
  104. package/dist/lib/lanes/store.js +11 -76
  105. package/dist/lib/notify-recipient.d.ts +10 -4
  106. package/dist/lib/notify-recipient.js +16 -4
  107. package/dist/lib/secret-input-link.d.ts +42 -0
  108. package/dist/lib/secret-input-link.js +93 -0
  109. package/dist/lib/secret-scan/range.d.ts +57 -0
  110. package/dist/lib/secret-scan/range.js +266 -0
  111. package/dist/lib/secret-scan/repo-config.d.ts +18 -3
  112. package/dist/lib/secret-scan/repo-config.js +135 -2
  113. package/dist/lib/sync-core/engine-home.d.ts +44 -0
  114. package/dist/lib/sync-core/engine-home.js +193 -0
  115. package/dist/lib/sync-core/flag.d.ts +17 -0
  116. package/dist/lib/sync-core/flag.js +47 -0
  117. package/dist/lib/sync-core/reconcile.d.ts +31 -0
  118. package/dist/lib/sync-core/reconcile.js +196 -0
  119. package/dist/lib/sync-core/release.d.ts +32 -0
  120. package/dist/lib/sync-core/release.js +140 -0
  121. package/dist/lib/sync-core/rollout-state.d.ts +24 -0
  122. package/dist/lib/sync-core/rollout-state.js +94 -0
  123. package/dist/lib/vault-sync-switches.d.ts +5 -0
  124. package/dist/lib/vault-sync-switches.js +43 -0
  125. package/dist/run/script-bound-schema.d.ts +27 -0
  126. package/dist/run/script-bound-schema.js +97 -0
  127. package/dist/secrets-env-action.d.ts +2 -0
  128. package/dist/secrets-env-action.js +7 -2
  129. package/dist/utils/cognito-session.d.ts +3 -30
  130. package/dist/utils/cognito-session.js +2 -100
  131. package/dist/utils/machine-principal-state.d.ts +38 -0
  132. package/dist/utils/machine-principal-state.js +110 -0
  133. package/dist/utils/remote-list-not-converging.d.ts +21 -0
  134. package/dist/utils/remote-list-not-converging.js +40 -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
  141. package/dist/lib/daemon/sync-core-canary.d.ts +0 -9
  142. package/dist/lib/daemon/sync-core-canary.js +0 -56
@@ -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
@@ -52,7 +52,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
52
52
  import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
53
53
  import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
54
54
  import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.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, } 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, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
56
56
  function collectRepeated(value, previous) {
57
57
  return [...previous, value];
58
58
  }
@@ -114,6 +114,48 @@ function requireBot(name) {
114
114
  }
115
115
  return { name: valid, dir, config };
116
116
  }
117
+ function isFleetRuntimeBot(config) {
118
+ return config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
119
+ }
120
+ /**
121
+ * The standing HQ instructions an HQ Fleet Runtime bot runs under: the same
122
+ * text a legacy local bot gets, read fresh from its worker folder, with the
123
+ * runtime's own way of labelling senders.
124
+ */
125
+ function fleetBotSystemPrompt(name, config, overrides = {}) {
126
+ return buildSystemPrompt({
127
+ botName: name,
128
+ ...(config.displayName ? { displayName: config.displayName } : {}),
129
+ agentUid: config.agentUid,
130
+ ownerUid: config.ownerUid,
131
+ hqRoot: config.hqRoot,
132
+ workerDir: config.workerDir,
133
+ workerSource: config.workerSource,
134
+ workerId: config.workerId,
135
+ companySlug: config.companySlug,
136
+ kind: overrides.kind ?? effectiveBotKind(config),
137
+ companies: overrides.companies ?? effectiveBotCompanies(config),
138
+ memoryDir: config.memoryDir,
139
+ fleetRuntime: true,
140
+ sources: readPromptSources(config.hqRoot, config.workerDir, undefined, {
141
+ memoryDir: config.memoryDir,
142
+ workerSource: config.workerSource,
143
+ companySlug: config.companySlug,
144
+ }),
145
+ });
146
+ }
147
+ function runFleetRuntimeControl(name, config, action, json) {
148
+ if (action === "start" || action === "restart") {
149
+ refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
150
+ }
151
+ const result = controlFleetLocalBot(config.agentUid, action);
152
+ if (json) {
153
+ console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
154
+ return;
155
+ }
156
+ const summary = typeof result.summary === "string" ? result.summary : `${action} completed`;
157
+ console.log(chalk.green(`${name}: ${summary}`));
158
+ }
117
159
  /** Bot-identity token supplier: per-bot creds + per-bot token cache dir. */
118
160
  export function botTokenSupplier(dir) {
119
161
  process.env.HQ_MACHINE_CREDS_FILE = botCredsPath(dir);
@@ -137,15 +179,16 @@ async function ownerToken(interactive = process.stdin.isTTY === true) {
137
179
  function rowFor(name, opts = {}) {
138
180
  const dir = botDir(name);
139
181
  const config = readBotConfig(dir);
140
- const status = readBotStatus(dir);
141
- const pid = status?.pid ?? null;
142
- const processAlive = pid !== null && isPidAlive(pid) && status?.state === "running";
182
+ const fleet = isFleetRuntimeBot(config);
183
+ const status = fleet ? null : readBotStatus(dir);
184
+ const pid = fleet ? fleetLocalGatewayPid(config.agentUid) : (status?.pid ?? null);
185
+ const processAlive = pid !== null && isPidAlive(pid) && (fleet || status?.state === "running");
143
186
  // Turns this bot has taken and not finished: what "thinking" means.
144
187
  const inflightTurns = readInflightTurns(dir);
145
188
  let daemonInstalled = false;
146
189
  let daemonLoaded = false;
147
190
  let daemonNodeMissing = null;
148
- if (opts.withDaemon !== false) {
191
+ if (opts.withDaemon !== false && !fleet) {
149
192
  try {
150
193
  const d = botDaemonStatus(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
151
194
  daemonInstalled = d.installed;
@@ -605,6 +648,10 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
605
648
  console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
606
649
  }
607
650
  async function runBotCreateLocked(name, opts) {
651
+ const useFleetRuntime = opts.hqFleetRuntime === true;
652
+ if (useFleetRuntime && (opts.runtime || opts.model || opts.effort || opts.lean)) {
653
+ 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 });
654
+ }
608
655
  const runtime = opts.runtime ?? "claude";
609
656
  if (!isBotRuntimeId(runtime)) {
610
657
  throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
@@ -640,6 +687,11 @@ async function runBotCreateLocked(name, opts) {
640
687
  // cost an identity).
641
688
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
642
689
  const { kind, companies } = resolveCreateKind(opts, worker);
690
+ if (useFleetRuntime && kind !== "personal") {
691
+ throw Object.assign(new Error("--hq-fleet-runtime creates a personal bot. Drop --kind company and --company."), {
692
+ expected: true,
693
+ });
694
+ }
643
695
  const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
644
696
  if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
645
697
  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 });
@@ -654,6 +706,10 @@ async function runBotCreateLocked(name, opts) {
654
706
  expected: true,
655
707
  });
656
708
  }
709
+ const fleetInstallation = useFleetRuntime
710
+ ? await installPinnedFleetLocalDistribution()
711
+ : null;
712
+ const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
657
713
  const token = await ownerToken();
658
714
  const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
659
715
  if (!owner)
@@ -763,6 +819,7 @@ async function runBotCreateLocked(name, opts) {
763
819
  // instead of creating a new one. The machine secret never travels.
764
820
  const localConfig = buildBotLocalConfig({
765
821
  runtime,
822
+ ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
766
823
  kind,
767
824
  ...(kind === "company" ? { companies } : {}),
768
825
  ...(model ? { model } : {}),
@@ -829,6 +886,7 @@ async function runBotCreateLocked(name, opts) {
829
886
  agentUid,
830
887
  ownerUid: created.agent.ownerUid ?? owner,
831
888
  runtime,
889
+ ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
832
890
  kind,
833
891
  // A company bot's list is filled in below, with the invites that went through.
834
892
  ...(kind === "company" ? { companies: [] } : {}),
@@ -885,8 +943,23 @@ async function runBotCreateLocked(name, opts) {
885
943
  const scaffoldOpts = { memoryDir: config.memoryDir };
886
944
  const scaffold = worker
887
945
  ? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
888
- : scaffoldBotWorker(hqRoot, name, agentUid, runtime, undefined, scaffoldOpts);
889
- createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtime}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
946
+ : scaffoldBotWorker(hqRoot, name, agentUid, fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime, undefined, scaffoldOpts);
947
+ if (fleetInstallation)
948
+ fs.rmSync(path.join(dir, "status.json"), { force: true });
949
+ const fleetBootstrap = fleetInstallation && fleetPython
950
+ ? await bootstrapFleetLocalBot({
951
+ agentUid,
952
+ ownerUid: config.ownerUid,
953
+ hqRoot,
954
+ botDir: dir,
955
+ installation: fleetInstallation,
956
+ machineCredentials: botCredsPath(dir),
957
+ systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
958
+ }, { start: opts.start !== false, python: fleetPython })
959
+ : null;
960
+ const fleetProfile = fleetBootstrap?.profile ?? null;
961
+ const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
962
+ createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtimeLabel}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
890
963
  const created_ = {
891
964
  outcome: "created",
892
965
  role: localBotRole(config),
@@ -895,7 +968,9 @@ async function runBotCreateLocked(name, opts) {
895
968
  agentUid,
896
969
  kind,
897
970
  companies,
898
- runtime,
971
+ runtime: runtimeLabel,
972
+ engine: fleetInstallation?.engine ?? null,
973
+ profile: fleetProfile,
899
974
  dir,
900
975
  worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
901
976
  workerId: worker?.id ?? null,
@@ -906,15 +981,15 @@ async function runBotCreateLocked(name, opts) {
906
981
  intro: intro ?? null,
907
982
  kickoff: kickoff ?? null,
908
983
  model: model ?? null,
909
- effort: effort ?? DEFAULT_BOT_EFFORT,
984
+ effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
910
985
  };
911
986
  let daemonMessage = null;
912
- if (opts.daemon !== false && process.platform === "darwin") {
987
+ if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
913
988
  const r = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot }));
914
989
  daemonMessage = r.message;
915
990
  }
916
991
  if (!opts.json) {
917
- console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtime})`));
992
+ console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtimeLabel})`));
918
993
  console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
919
994
  if (worker)
920
995
  console.log(chalk.dim(` worker: ${worker.id}${worker.company ? ` (${worker.company})` : ""} — ${path.join(hqRoot, worker.path)}`));
@@ -922,9 +997,17 @@ async function runBotCreateLocked(name, opts) {
922
997
  console.log(chalk.dim(` worker: ${scaffold.dir}`));
923
998
  console.log(chalk.dim(` memory: ${scaffold.memoryDir} (${memoryMode})`));
924
999
  console.log(chalk.dim(` state: ${dir}`));
1000
+ if (fleetProfile)
1001
+ console.log(chalk.dim(` profile: ${fleetProfile}`));
925
1002
  if (daemonMessage)
926
1003
  console.log(chalk.dim(` ${daemonMessage}`));
927
1004
  }
1005
+ if (fleetInstallation) {
1006
+ if (opts.json) {
1007
+ console.log(JSON.stringify({ ok: true, name, ...created_, daemon: null, running: fleetBootstrap?.running ?? false }, null, 2));
1008
+ }
1009
+ return;
1010
+ }
928
1011
  if (opts.start !== false) {
929
1012
  if (daemonMessage && /loaded\)/.test(daemonMessage)) {
930
1013
  // RunAtLoad already started it.
@@ -1028,6 +1111,9 @@ async function runBotResident(nameArg) {
1028
1111
  const name = validateBotName(nameArg);
1029
1112
  const dir = botDir(name);
1030
1113
  const config = readBotConfig(dir);
1114
+ if (config && isFleetRuntimeBot(config)) {
1115
+ throw Object.assign(new Error(`${name} runs on the HQ Fleet Runtime under its own background service. Use hq bot start ${name}, hq bot status ${name} or hq bot logs ${name}.`), { expected: true });
1116
+ }
1031
1117
  if (!config) {
1032
1118
  // A LaunchAgent that outlived its bot: exiting non-zero here made launchd
1033
1119
  // relaunch this every five seconds forever. Say so once, take the stale
@@ -1127,7 +1213,11 @@ async function runBotList(opts) {
1127
1213
  }
1128
1214
  }
1129
1215
  async function runBotStatus(nameArg, opts) {
1130
- const { name, dir } = requireBot(nameArg);
1216
+ const { name, dir, config } = requireBot(nameArg);
1217
+ if (isFleetRuntimeBot(config)) {
1218
+ runFleetRuntimeControl(name, config, "status", opts.json === true);
1219
+ return;
1220
+ }
1131
1221
  const row = rowFor(name);
1132
1222
  await decorateOnline([row]);
1133
1223
  const status = readBotStatus(dir);
@@ -1326,6 +1416,15 @@ async function runBotRmOrphan(name, opts) {
1326
1416
  else
1327
1417
  console.log(chalk.dim(` HQ may still have a bot named "${name}". Remove that too with: hq bot rm ${name} --remote`));
1328
1418
  }
1419
+ function stopFleetRuntimeBotQuietly(agentUid) {
1420
+ try {
1421
+ controlFleetLocalBot(agentUid, "stop");
1422
+ return true;
1423
+ }
1424
+ catch {
1425
+ return false;
1426
+ }
1427
+ }
1329
1428
  async function runBotRm(nameArg, opts) {
1330
1429
  const validated = validateBotName(nameArg);
1331
1430
  // Under the bot's lock: an rm racing a create, adopt or restore of the same
@@ -1348,9 +1447,13 @@ async function runBotRmLocked(validated, opts) {
1348
1447
  return;
1349
1448
  }
1350
1449
  }
1351
- await stopBot(name, dir, config, "silent").catch(() => { });
1352
- const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
1353
- const daemon = uninstallBotDaemon(paths, daemonDeps());
1450
+ if (isFleetRuntimeBot(config))
1451
+ stopFleetRuntimeBotQuietly(config.agentUid);
1452
+ else
1453
+ await stopBot(name, dir, config, "silent").catch(() => { });
1454
+ const daemon = isFleetRuntimeBot(config)
1455
+ ? { message: "managed by HQ Fleet Runtime" }
1456
+ : uninstallBotDaemon(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
1354
1457
  const token = await ownerToken();
1355
1458
  const api = new BotApi({ token: async () => token });
1356
1459
  let identity;
@@ -1380,8 +1483,11 @@ async function runBotRmLocked(validated, opts) {
1380
1483
  // re-created under the same name. Everything else keeps the folder, so the
1381
1484
  // same command can pick the job back up.
1382
1485
  const localStateRemoved = terminal || incomplete.retryable === false;
1383
- if (localStateRemoved)
1486
+ if (localStateRemoved) {
1487
+ if (isFleetRuntimeBot(config))
1488
+ removeFleetLocalBot(config.agentUid);
1384
1489
  fs.rmSync(dir, { recursive: true, force: true });
1490
+ }
1385
1491
  const message = incomplete
1386
1492
  ? `HQ removed what it could of "${name}", but ${incomplete.detail}.` +
1387
1493
  (incomplete.retryable
@@ -1572,6 +1678,10 @@ export function botExists(name) {
1572
1678
  /** `hq bot restart <name>`: stop, then start (launchd when installed). */
1573
1679
  export async function runBotRestart(nameArg, opts = {}) {
1574
1680
  const b = requireBot(nameArg);
1681
+ if (isFleetRuntimeBot(b.config)) {
1682
+ runFleetRuntimeControl(b.name, b.config, "restart", opts.json === true || opts.output === "json");
1683
+ return;
1684
+ }
1575
1685
  const running = readBotStatus(b.dir);
1576
1686
  if (running && isPidAlive(running.pid) && selfStopProbe(running.pid)) {
1577
1687
  // The stop half would end this turn (and this command) before the
@@ -1581,6 +1691,30 @@ export async function runBotRestart(nameArg, opts = {}) {
1581
1691
  await stopBot(b.name, b.dir, b.config, "silent");
1582
1692
  await startBot(b.name, b.dir, b.config, opts.output ?? (opts.json ? "json" : "text"));
1583
1693
  }
1694
+ async function runBotAuth(nameArg, opts) {
1695
+ const b = requireBot(nameArg);
1696
+ if (!isFleetRuntimeBot(b.config)) {
1697
+ throw Object.assign(new Error(`hq bot auth is for bots created with --hq-fleet-runtime. ${b.name} uses ${b.config.runtime}.`), { expected: true });
1698
+ }
1699
+ const apiKey = opts.apiKey ? fs.readFileSync(0, "utf8").trim() : undefined;
1700
+ if (opts.apiKey && !apiKey) {
1701
+ throw Object.assign(new Error("No API key was received on stdin."), { expected: true });
1702
+ }
1703
+ const result = authenticateFleetLocalBot({
1704
+ agentUid: b.config.agentUid,
1705
+ provider: opts.provider,
1706
+ ...(opts.model ? { model: opts.model } : {}),
1707
+ ...(apiKey ? { apiKey } : {}),
1708
+ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
1709
+ ...(opts.transport ? { transport: opts.transport } : {}),
1710
+ openBrowser: opts.browser !== false,
1711
+ });
1712
+ if (opts.json) {
1713
+ console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
1714
+ return;
1715
+ }
1716
+ console.log(chalk.green(`${b.name} is signed in to ${result.provider} and running ${result.model}.`));
1717
+ }
1584
1718
  // ── adopt / restore (bring a bot you own back to this computer) ──────────────
1585
1719
  /**
1586
1720
  * Which computer a re-issue is for. Never a secret and never an identifier the
@@ -2090,6 +2224,7 @@ async function runBotListRemote(opts) {
2090
2224
  reason: runnable.reason,
2091
2225
  displayName: remoteBotDisplayName(bot, name),
2092
2226
  role: remoteBotRole(bot),
2227
+ ownerUid: bot.ownerUid ?? null,
2093
2228
  };
2094
2229
  });
2095
2230
  if (opts.json) {
@@ -2124,7 +2259,13 @@ export function registerBotCommand(program) {
2124
2259
  const { dir, config } = requireBot(name);
2125
2260
  const token = await ownerToken(false);
2126
2261
  const api = new BotApi({ token: async () => token });
2127
- const result = await promoteLocalBot({ dir, config, companyUid: opts.company, api });
2262
+ const result = await promoteLocalBot({
2263
+ dir,
2264
+ config,
2265
+ companyUid: opts.company,
2266
+ api,
2267
+ ...(isFleetRuntimeBot(config) ? { runtime: fleetLocalPromotionRuntime(config.agentUid) } : {}),
2268
+ });
2128
2269
  const details = result.pending ? await api.promotionDetails(config.agentUid, opts.company) : null;
2129
2270
  if (opts.json)
2130
2271
  console.log(JSON.stringify({ ...result, pairing: details?.pairing ?? null, ...(details?.failure ? { failure: details.failure } : {}) }));
@@ -2140,7 +2281,8 @@ export function registerBotCommand(program) {
2140
2281
  .description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
2141
2282
  .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`)
2142
2283
  .option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
2143
- .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`, "claude")
2284
+ .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
2285
+ .option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
2144
2286
  .option("--model <model>", "Model override passed to the runtime CLI")
2145
2287
  .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})`)
2146
2288
  .option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
@@ -2196,6 +2338,10 @@ export function registerBotCommand(program) {
2196
2338
  .option("--json", "Print machine-readable JSON")
2197
2339
  .action((name, opts) => wrap(async () => {
2198
2340
  const b = requireBot(name);
2341
+ if (isFleetRuntimeBot(b.config)) {
2342
+ runFleetRuntimeControl(b.name, b.config, "start", opts.json === true);
2343
+ return;
2344
+ }
2199
2345
  await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
2200
2346
  })());
2201
2347
  bot
@@ -2204,6 +2350,10 @@ export function registerBotCommand(program) {
2204
2350
  .option("--json", "Print machine-readable JSON")
2205
2351
  .action((name, opts) => wrap(async () => {
2206
2352
  const b = requireBot(name);
2353
+ if (isFleetRuntimeBot(b.config)) {
2354
+ runFleetRuntimeControl(b.name, b.config, "stop", opts.json === true);
2355
+ return;
2356
+ }
2207
2357
  await stopBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
2208
2358
  })());
2209
2359
  bot
@@ -2216,6 +2366,17 @@ export function registerBotCommand(program) {
2216
2366
  .description("Show the bot's process, heartbeat, launchd, and credential state")
2217
2367
  .option("--json", "Print machine-readable JSON")
2218
2368
  .action((name, opts) => wrap(() => runBotStatus(name, opts))());
2369
+ bot
2370
+ .command("auth <name>")
2371
+ .description("Sign a runtime-backed personal bot in to ChatGPT, Grok, Claude, or another supported provider")
2372
+ .requiredOption("--provider <provider>", "Provider: chatgpt, grok, claude, or a runtime provider id")
2373
+ .option("--model <model>", "Model id; required for providers without a built-in personal-bot default")
2374
+ .option("--api-key", "Read an API key from stdin instead of starting device authorization")
2375
+ .option("--base-url <url>", "Provider API base URL; required for custom runtime providers")
2376
+ .option("--transport <transport>", "Provider transport: chat_completions, anthropic_messages, or codex_responses")
2377
+ .option("--no-browser", "Do not open a browser during device authorization")
2378
+ .option("--json", "Print machine-readable JSON")
2379
+ .action((name, opts) => wrap(() => runBotAuth(name, opts))());
2219
2380
  bot
2220
2381
  .command("logs <name>")
2221
2382
  .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
@@ -128,6 +176,15 @@ export declare function scopeExcludedPrefixDetail(prefixSet: readonly string[] |
128
176
  * folder foo/ — and grant advice would point the reader the wrong way.
129
177
  */
130
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>;
131
188
  /**
132
189
  * Bound foreground waits for a watcher/manual sync that currently owns the
133
190
  * per-root operation lock. The cloud engine reads this environment value on