@indigoai-us/hq-cli 5.345.190 → 5.345.193

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 (122) hide show
  1. package/CHANGELOG.md +420 -0
  2. package/assets/bot-workers/setup/worker.yaml +91 -34
  3. package/dist/command-catalog.generated.d.ts +96 -19
  4. package/dist/command-catalog.generated.js +124 -21
  5. package/dist/commands/bot-jobs.d.ts +21 -18
  6. package/dist/commands/bot-jobs.js +127 -39
  7. package/dist/commands/bot.d.ts +7 -4
  8. package/dist/commands/bot.js +289 -140
  9. package/dist/commands/core-jobs-schedule.js +10 -0
  10. package/dist/commands/daemon.js +37 -0
  11. package/dist/commands/files.d.ts +41 -6
  12. package/dist/commands/files.js +108 -27
  13. package/dist/commands/pack-start-bot.d.ts +4 -6
  14. package/dist/commands/pack-start-bot.js +4 -8
  15. package/dist/commands/signals-run.d.ts +45 -0
  16. package/dist/commands/signals-run.js +175 -0
  17. package/dist/commands/signals.d.ts +1 -0
  18. package/dist/commands/signals.js +3 -0
  19. package/dist/commands/sources.d.ts +11 -0
  20. package/dist/commands/sources.js +137 -25
  21. package/dist/commands/staff/reset-test-account.d.ts +10 -0
  22. package/dist/commands/staff/reset-test-account.js +18 -2
  23. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
  24. package/dist/hq-cloud-sync-runner/package.json +1 -1
  25. package/dist/lib/bot/api.d.ts +3 -2
  26. package/dist/lib/bot/config.d.ts +2 -0
  27. package/dist/lib/bot/fleet-cron.d.ts +121 -0
  28. package/dist/lib/bot/fleet-cron.js +319 -0
  29. package/dist/lib/bot/fleet-local.d.ts +113 -2
  30. package/dist/lib/bot/fleet-local.js +597 -35
  31. package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
  32. package/dist/lib/bot/fleet-owner-companies.js +30 -0
  33. package/dist/lib/bot/fleet-watch.d.ts +25 -0
  34. package/dist/lib/bot/fleet-watch.js +51 -0
  35. package/dist/lib/bot/index.d.ts +1 -1
  36. package/dist/lib/bot/index.js +1 -1
  37. package/dist/lib/bot/job-run.d.ts +5 -4
  38. package/dist/lib/bot/job-run.js +5 -4
  39. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
  40. package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
  41. package/dist/lib/bot/local-jobs.d.ts +13 -2
  42. package/dist/lib/bot/local-jobs.js +17 -4
  43. package/dist/lib/bot/progress.d.ts +14 -0
  44. package/dist/lib/bot/progress.js +47 -1
  45. package/dist/lib/bot/run.d.ts +55 -2
  46. package/dist/lib/bot/run.js +192 -10
  47. package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
  48. package/dist/lib/bot/setup-tool-offer.js +16 -0
  49. package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
  50. package/dist/lib/bot/signals-engine-flag.js +30 -0
  51. package/dist/lib/daemon/catalog.d.ts +4 -0
  52. package/dist/lib/daemon/catalog.js +29 -9
  53. package/dist/lib/daemon/legacy.d.ts +6 -0
  54. package/dist/lib/daemon/legacy.js +16 -10
  55. package/dist/lib/daemon/run.d.ts +1 -0
  56. package/dist/lib/daemon/run.js +10 -0
  57. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  58. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  59. package/dist/lib/flag-registry.d.ts +13 -1
  60. package/dist/lib/flag-registry.js +46 -0
  61. package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
  62. package/dist/lib/hot-channels/adapters/attio.js +367 -29
  63. package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
  64. package/dist/lib/hot-channels/adapters/gmail.js +59 -2
  65. package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
  66. package/dist/lib/hot-channels/adapters/linear.js +71 -1
  67. package/dist/lib/hot-channels/adapters/meetings.js +4 -1
  68. package/dist/lib/hot-channels/adapters/slack.js +7 -3
  69. package/dist/lib/hot-channels/alert.js +1 -1
  70. package/dist/lib/hot-channels/contract.d.ts +11 -0
  71. package/dist/lib/hot-channels/contract.js +1 -0
  72. package/dist/lib/hot-channels/cursors.js +1 -1
  73. package/dist/lib/hot-channels/landing.d.ts +6 -2
  74. package/dist/lib/hot-channels/landing.js +13 -6
  75. package/dist/lib/hot-channels/ledger.js +2 -0
  76. package/dist/lib/hot-channels/loop.js +18 -4
  77. package/dist/lib/hot-channels/registry.js +9 -1
  78. package/dist/lib/hot-channels/run.d.ts +18 -0
  79. package/dist/lib/hot-channels/run.js +15 -4
  80. package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
  81. package/dist/lib/hot-channels/signals-pass.js +7 -5
  82. package/dist/lib/hot-channels/sync-lock.js +22 -6
  83. package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
  84. package/dist/lib/mesh/live/daemon/install.js +117 -30
  85. package/dist/lib/signals/adapters-catalog.d.ts +77 -0
  86. package/dist/lib/signals/adapters-catalog.js +194 -0
  87. package/dist/lib/signals/engine.d.ts +196 -0
  88. package/dist/lib/signals/engine.js +495 -0
  89. package/dist/lib/signals/failure-alert.d.ts +89 -0
  90. package/dist/lib/signals/failure-alert.js +175 -0
  91. package/dist/lib/signals/local-date.d.ts +3 -0
  92. package/dist/lib/signals/local-date.js +6 -0
  93. package/dist/lib/signals/projection.d.ts +42 -0
  94. package/dist/lib/signals/projection.js +67 -0
  95. package/dist/lib/signals/reply.d.ts +112 -0
  96. package/dist/lib/signals/reply.js +368 -0
  97. package/dist/lib/signals/run-lock.d.ts +36 -0
  98. package/dist/lib/signals/run-lock.js +148 -0
  99. package/dist/lib/signals/run-record.d.ts +24 -0
  100. package/dist/lib/signals/run-record.js +82 -0
  101. package/dist/lib/signals/sanitize.d.ts +105 -0
  102. package/dist/lib/signals/sanitize.js +439 -0
  103. package/dist/lib/signals/settings.d.ts +10 -0
  104. package/dist/lib/signals/settings.js +48 -0
  105. package/dist/lib/signals/steps/adapters.d.ts +14 -0
  106. package/dist/lib/signals/steps/adapters.js +156 -0
  107. package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
  108. package/dist/lib/signals/steps/entity-similarity.js +47 -0
  109. package/dist/lib/signals/steps/extract.d.ts +113 -0
  110. package/dist/lib/signals/steps/extract.js +765 -0
  111. package/dist/lib/signals/steps/garden.d.ts +24 -0
  112. package/dist/lib/signals/steps/garden.js +250 -0
  113. package/dist/lib/signals/steps/types.d.ts +112 -0
  114. package/dist/lib/signals/steps/types.js +4 -0
  115. package/dist/lib/signals/steps/upload.d.ts +43 -0
  116. package/dist/lib/signals/steps/upload.js +324 -0
  117. package/dist/lib/signals/write-guard.d.ts +63 -0
  118. package/dist/lib/signals/write-guard.js +287 -0
  119. package/dist/utils/resident-package-preload.js +16 -0
  120. package/package.json +9 -9
  121. package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
  122. package/dist/lib/bot/fleet-default-flag.js +0 -127
@@ -1,4 +1,7 @@
1
1
  import { withLocalJobs } from "../lib/bot/local-jobs.js";
2
+ import { runSignals } from "../lib/signals/engine.js";
3
+ import { BOT_SIGNALS_ENGINE_JOB_FLAG, signalsEngineForJob } from "../lib/bot/signals-engine-flag.js";
4
+ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
2
5
  import { currentHotSignalSyncCompany, readActiveCompanySlug, registerHotSignalSyncOnStart, runHotSignalSyncJob } from "../lib/bot/jobs/hot-signal-sync.js";
3
6
  import { createClaudeSignalsPass } from "../lib/hot-channels/signals-pass.js";
4
7
  import { machineTimeZone } from "../lib/bot/job-run.js";
@@ -10,7 +13,7 @@ import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hol
10
13
  /**
11
14
  * `hq bot` — personal local bots (local-bots US-003/US-004/US-005).
12
15
  *
13
- * hq bot create <name> [--kind personal|company] [--company slug]... [--runtime claude|codex|grok] [--model m] [--effort level] [--worker id] [--intro text] [--kickoff prompt] [--memory synced|local] [--no-daemon]
16
+ * hq bot create <name> [--kind personal|company] [--company slug]... [--runtime claude|codex|grok] [--model m] [--worker id] [--intro text] [--kickoff prompt] [--memory synced|local] [--no-daemon]
14
17
  * hq bot companies <name> [--add slug]... [--remove slug]... (a company bot's memberships)
15
18
  * hq bot workers [--json] (company + core workers a bot can be created from)
16
19
  * hq bot list [--remote] [--json]
@@ -39,6 +42,12 @@ import { createVaultObjectDownloader } from "../lib/agent-kit/attachment-downloa
39
42
  import { addBotToCompanies, describeCompanyFailures, removeBotFromCompanies } from "./bot-companies.js";
40
43
  import { registerBotContinuityCommand } from "./bot-continuity.js";
41
44
  import { registerBotJobsCommand } from "./bot-jobs.js";
45
+ import { ensureFleetHotSignalSync, fleetCronOps, migrateLegacyJobsToFleet } from "../lib/bot/fleet-cron.js";
46
+ import { resolvePackSetupPrompt } from "../lib/bot/pack-setup.js";
47
+ import { detectCodingToolActivity } from "../lib/bot/coding-tool-activity.js";
48
+ import { setupToolOfferPendingSection, setupToolOfferText, setupWelcomeOpening } from "../lib/bot/setup-tool-offer.js";
49
+ import { patchSetupIntroState, readSetupIntroState, readSetupProgressIntro } from "../lib/bot/setup-explain.js";
50
+ import { resolveHqBinary, resolveNodeBinary } from "../lib/mesh/live/daemon/install.js";
42
51
  import chalk from "chalk";
43
52
  import { execFileSync, spawn } from "node:child_process";
44
53
  import * as fs from "node:fs";
@@ -48,7 +57,6 @@ import * as readline from "node:readline";
48
57
  import { DEFAULT_COGNITO, ensureCognitoToken, resolveDefaultHqRoot, } from "../utils/cognito-session.js";
49
58
  import { peekHqApiKey } from "../utils/resolve-vault-credential.js";
50
59
  import { getCompanyUid, resolveCallerPersonUid } from "../utils/vault-api.js";
51
- import { resolveHqBinary } from "../lib/mesh/live/daemon/install.js";
52
60
  import { askDaemonToStart, askDaemonToStop, daemonOwnsProcess, daemonHoldReason, daemonSupervises, daemonWillSupervise, handOffInstallHere, } from "../lib/daemon/handoff.js";
53
61
  import { daemonPaths } from "../lib/daemon/paths.js";
54
62
  import { launchdLabel } from "../lib/bot/paths.js";
@@ -57,7 +65,7 @@ import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js
57
65
  import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
58
66
  import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
59
67
  import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
60
- 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, runtimeCrashSinceStart, 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, fleetLocalProfileNeedsAdoption, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetDefaultCompanies, fleetOwnerCompanies, fleetRuntimeIsDefault, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
68
+ 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, runtimeCrashSinceStart, 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, HQ_FLEET_LOCAL_RELEASE, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetLocalProfileNeedsAdoption, fleetLocalServiceInstalled, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetOwnerCompanies, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, installTargetFleetLocalDistribution, resolveFleetLocalPointerTarget, resolveFleetLocalTargetRelease, fleetLocalProfileDir, fleetProfileOwnerCompanies, fleetLocalChannels, readFleetLocalGatewayState, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
61
69
  function collectRepeated(value, previous) {
62
70
  return [...previous, value];
63
71
  }
@@ -128,7 +136,15 @@ function isFleetRuntimeBot(config) {
128
136
  * runtime's own way of labelling senders.
129
137
  */
130
138
  function fleetBotSystemPrompt(name, config, overrides = {}) {
131
- return buildSystemPrompt({
139
+ let packSetup;
140
+ try {
141
+ packSetup = resolvePackSetupPrompt({ hqRoot: config.hqRoot, workerDir: config.workerDir, workerSource: config.workerSource });
142
+ }
143
+ catch {
144
+ packSetup = null;
145
+ }
146
+ const prompt = buildSystemPrompt({
147
+ packSetup,
132
148
  botName: name,
133
149
  ...(config.displayName ? { displayName: config.displayName } : {}),
134
150
  agentUid: config.agentUid,
@@ -148,10 +164,139 @@ function fleetBotSystemPrompt(name, config, overrides = {}) {
148
164
  companySlug: config.companySlug,
149
165
  }),
150
166
  });
167
+ const offer = pendingFleetSetupToolOffer(name, config);
168
+ return offer ? `${prompt.trimEnd()}\n\n${offer}` : prompt;
169
+ }
170
+ function isFleetSetupBot(name, config) {
171
+ return name === RESERVED_SETUP_BOT_NAME && effectiveBotKind(config) === "personal" && config.workerId === SETUP_BOT_WORKER_ID;
172
+ }
173
+ function pendingFleetSetupToolOffer(name, config) {
174
+ const kickoff = (config.kickoff ?? "").trim();
175
+ if (!kickoff || !isFleetSetupBot(name, config))
176
+ return null;
177
+ try {
178
+ const state = readSetupIntroState(botDir(name));
179
+ if (!state.kickoffDeferred || !state.toolOffer)
180
+ return null;
181
+ if (readSetupProgressIntro(resolveBotMemoryDir(config)) !== null)
182
+ return null;
183
+ return setupToolOfferPendingSection(state.toolOffer.tool, kickoff);
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ function fleetSetupToolOffer(dir, config) {
190
+ try {
191
+ if (readSetupIntroState(dir).toolOffer)
192
+ return null;
193
+ if (readSetupProgressIntro(resolveBotMemoryDir(config)) !== null)
194
+ return null;
195
+ const tool = detectCodingToolActivity({ now: new Date(), extraHqRoots: [config.hqRoot] }).pick;
196
+ if (!tool)
197
+ return null;
198
+ patchSetupIntroState(dir, { toolOffer: { tool, offeredAt: new Date().toISOString() }, kickoffDeferred: true });
199
+ createBotLogger(dir)("info", `offered to continue setup in the ${tool} app; the kickoff waits for the answer`);
200
+ return tool;
201
+ }
202
+ catch (err) {
203
+ createBotLogger(dir)("warn", `coding tool check failed (${err instanceof Error ? err.message : String(err)}); running the kickoff`);
204
+ return null;
205
+ }
206
+ }
207
+ /** The daily Fleet signals job is gated by the same default-off flag as the in-process engine job. */
208
+ function fleetSignalsJobEnabled() {
209
+ try {
210
+ return resolveProcessFlagGate(BOT_SIGNALS_ENGINE_JOB_FLAG, { fallback: false }, () => false);
211
+ }
212
+ catch {
213
+ return false;
214
+ }
215
+ }
216
+ function syncFleetRuntimeJobs(name, dir, config) {
217
+ const log = createBotLogger(dir);
218
+ let ops;
219
+ try {
220
+ ops = fleetCronOps(config.agentUid);
221
+ }
222
+ catch (err) {
223
+ log("warn", `could not reach ${name}'s HQ Fleet Runtime schedule: ${err instanceof Error ? err.message : String(err)}`);
224
+ return;
225
+ }
226
+ try {
227
+ const migrated = migrateLegacyJobsToFleet(dir, config, ops);
228
+ if (migrated) {
229
+ const movedCount = migrated.imported;
230
+ const moved = `moved ${movedCount} scheduled job${movedCount === 1 ? "" : "s"} into the HQ Fleet Runtime${migrated.alreadyPresent ? ` (${migrated.alreadyPresent} already there)` : ""}`;
231
+ if (migrated.skipped.length === 0) {
232
+ log("info", `${moved}; the old list is kept at ${migrated.renamedTo}`);
233
+ }
234
+ else {
235
+ const message = `${moved}, but could not move ${migrated.skipped.length} scheduled job(s) (${migrated.skipped.join(", ")}): the HQ Fleet Runtime cannot run their schedule. They are still in ${path.join(dir, "jobs.json")}, which was not renamed, and they will not run until you add them again with hq bot jobs --add using a schedule the runtime supports.`;
236
+ log("warn", message);
237
+ console.warn(`Warning: ${name}: ${message}`);
238
+ }
239
+ }
240
+ }
241
+ catch (err) {
242
+ log("warn", `could not move ${name}'s scheduled jobs into the HQ Fleet Runtime: ${err instanceof Error ? err.message : String(err)}`);
243
+ }
244
+ try {
245
+ let otherBotDirs = [];
246
+ try {
247
+ otherBotDirs = listBotNames(botsRoot()).map((n) => botDir(n));
248
+ }
249
+ catch {
250
+ otherBotDirs = [];
251
+ }
252
+ const result = ensureFleetHotSignalSync({
253
+ dir,
254
+ config,
255
+ ops,
256
+ profileDir: fleetLocalProfileDir(config.agentUid),
257
+ activeCompany: readActiveCompanySlug(config.hqRoot) ?? null,
258
+ ownerCompanies: fleetProfileOwnerCompanies(config.agentUid),
259
+ otherBotDirs,
260
+ nodeBinary: resolveNodeBinary(),
261
+ hqBinary: resolveHqBinary(),
262
+ signalsJobEnabled: fleetSignalsJobEnabled(),
263
+ });
264
+ for (const id of result.removed)
265
+ log("info", `removed the hot-signal-sync job ${id}: this bot no longer syncs that company`);
266
+ for (const entry of result.companies) {
267
+ if (entry.created)
268
+ log("info", `registered the daily hot-signal-sync job for ${entry.company} at 06:30 in the HQ Fleet Runtime`);
269
+ if (entry.heldBy)
270
+ log("info", `not adding a hot-signal-sync job for ${entry.company}: the bot in ${entry.heldBy} already runs it`);
271
+ }
272
+ }
273
+ catch (err) {
274
+ log("warn", `could not set up ${name}'s hot-signal-sync job: ${err instanceof Error ? err.message : String(err)}`);
275
+ }
276
+ }
277
+ function refuseHeldFleetBot(dir) {
278
+ if (hasPromotionHold(dir))
279
+ throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
151
280
  }
152
281
  function fleetAuthCommand(name, config) {
153
282
  return `hq bot auth ${name} --provider ${config.runtime === "grok" ? "grok" : config.runtime === "codex" ? "chatgpt" : "claude"}`;
154
283
  }
284
+ /** How long a just-(re)started HQ Fleet gateway gets to write its pid before it counts as not started. */
285
+ function fleetStartWaitMs() {
286
+ const raw = Number(process.env.HQ_BOT_FLEET_START_WAIT_MS);
287
+ return Number.isFinite(raw) && raw >= 0 ? raw : 15_000;
288
+ }
289
+ async function fleetGatewayComesUp(agentUid, waitMs, stepMs = 500) {
290
+ const deadline = Date.now() + waitMs;
291
+ for (;;) {
292
+ const pid = fleetLocalGatewayPid(agentUid);
293
+ if (pid !== null && isPidAlive(pid))
294
+ return true;
295
+ if (Date.now() >= deadline)
296
+ return false;
297
+ await new Promise((r) => setTimeout(r, stepMs));
298
+ }
299
+ }
155
300
  function runFleetRuntimeControl(name, config, action, json) {
156
301
  if ((action === "install" || action === "start" || action === "restart") && config.fleetAuthPending === true) {
157
302
  const authCommand = fleetAuthCommand(name, config);
@@ -163,6 +308,7 @@ function runFleetRuntimeControl(name, config, action, json) {
163
308
  }
164
309
  if (action === "install" || action === "start" || action === "restart") {
165
310
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
311
+ syncFleetRuntimeJobs(name, botDir(name), config);
166
312
  }
167
313
  const installService = action === "install" || (config.fleetServiceInstallPending === true && (action === "start" || action === "restart"));
168
314
  const result = controlFleetLocalBot(config.agentUid, installService ? "install" : action);
@@ -236,6 +382,7 @@ function rowFor(name, opts = {}) {
236
382
  agentUid: config.agentUid,
237
383
  ownerUid: config.ownerUid,
238
384
  runtime: config.runtime,
385
+ ...(fleet ? { engine: HQ_FLEET_RUNTIME_ENGINE_ID } : {}),
239
386
  model: config.model,
240
387
  effort: effectiveBotEffort(config),
241
388
  effortIsDefault: !config.effort,
@@ -436,6 +583,7 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
436
583
  }
437
584
  const adopted = await adoptFleetRuntimeProfile(name, dir, config);
438
585
  refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
586
+ syncFleetRuntimeJobs(name, dir, config);
439
587
  const before = readBotStatus(dir);
440
588
  const alreadyRunning = !!before && before.state === "running" && isPidAlive(before.pid);
441
589
  const installService = adopted || config.fleetServiceInstallPending === true;
@@ -460,10 +608,28 @@ async function startFleetRuntimeBot(name, dir, config, output, extra) {
460
608
  console.log(chalk.yellow(`${name} did not report running within 45s`) + chalk.dim(` logs: ~/.hq-fleet/profiles/${config.agentUid}/logs`));
461
609
  return { running, how };
462
610
  }
463
- async function adoptFleetRuntimeProfile(name, dir, config) {
464
- if (!fleetLocalProfileNeedsAdoption(config.agentUid))
611
+ async function adoptFleetRuntimeProfile(name, dir, config, opts = {}) {
612
+ const target = opts.pointerOnly ? await resolveFleetLocalPointerTarget() : await resolveFleetLocalTargetRelease();
613
+ if (!target)
614
+ return false;
615
+ if (!fleetLocalProfileNeedsAdoption(config.agentUid, { release: target.release }))
465
616
  return false;
466
- const installation = await installPinnedFleetLocalDistribution();
617
+ let installation;
618
+ try {
619
+ installation = await installTargetFleetLocalDistribution({ target });
620
+ }
621
+ catch (err) {
622
+ if (target.source !== "pointer")
623
+ throw err;
624
+ if (opts.pointerOnly) {
625
+ createBotLogger(dir)("warn", `could not install HQ Fleet Local ${target.release.version} (${err instanceof Error ? err.message : String(err)}); the bot stays on its current release`);
626
+ return false;
627
+ }
628
+ createBotLogger(dir)("warn", `could not install HQ Fleet Local ${target.release.version} (${err instanceof Error ? err.message : String(err)}); staying on ${HQ_FLEET_LOCAL_RELEASE.version}`);
629
+ if (!fleetLocalProfileNeedsAdoption(config.agentUid, { release: HQ_FLEET_LOCAL_RELEASE }))
630
+ return false;
631
+ installation = await installPinnedFleetLocalDistribution();
632
+ }
467
633
  const python = await ensureFleetPython();
468
634
  const companyScope = effectiveBotKind(config) === "company"
469
635
  ? effectiveBotCompanies(config)
@@ -477,6 +643,7 @@ async function adoptFleetRuntimeProfile(name, dir, config) {
477
643
  systemPrompt: fleetBotSystemPrompt(name, config),
478
644
  provider: fleetProviderForRuntime(config.runtime, config.model),
479
645
  ownerCompanies: companyScope,
646
+ ...(config.autoApprove === true ? { autoApprove: true } : {}),
480
647
  }, { python, start: false });
481
648
  patchBotConfig(dir, { engine: installation.engine });
482
649
  const credentials = botCredsPath(dir);
@@ -637,28 +804,6 @@ export function resolveCreateKind(opts, worker) {
637
804
  }
638
805
  return { kind, companies };
639
806
  }
640
- async function promptFleetProvider() {
641
- if (process.stdin.isTTY !== true || process.stderr.isTTY !== true) {
642
- throw Object.assign(new Error("--provider is required with --hq-fleet-runtime in a non-interactive command (claude, codex, or grok)."), { expected: true });
643
- }
644
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
645
- try {
646
- const answer = await new Promise((resolve) => {
647
- rl.question("Provider: 1) Codex 2) Claude 3) Grok [1]: ", resolve);
648
- });
649
- const selected = answer.trim().toLowerCase();
650
- if (selected === "" || selected === "1" || selected === "codex")
651
- return "codex";
652
- if (selected === "2" || selected === "claude")
653
- return "claude";
654
- if (selected === "3" || selected === "grok")
655
- return "grok";
656
- throw Object.assign(new Error("Provider must be claude, codex, or grok."), { expected: true });
657
- }
658
- finally {
659
- rl.close();
660
- }
661
- }
662
807
  /**
663
808
  * `hq bot create`. Runs under the bot's lock, so a create racing an adopt,
664
809
  * restore, rm or set of the same bot waits for it instead of tripping over
@@ -789,12 +934,8 @@ async function finishExistingSetupBot(name, dir, config, displayName, opts) {
789
934
  console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` logs: hq bot logs ${name}`));
790
935
  }
791
936
  async function runBotCreateLocked(name, opts) {
792
- const requestedFleetRuntime = opts.hqFleetRuntime === true;
793
- if (requestedFleetRuntime && opts.legacyRuntime) {
794
- throw Object.assign(new Error("Pick one of --hq-fleet-runtime and --legacy-runtime."), { expected: true });
795
- }
796
- if (requestedFleetRuntime && (opts.effort || opts.lean)) {
797
- throw Object.assign(new Error("--hq-fleet-runtime sets its own thinking level. Do not combine it with --effort or --lean."), { expected: true });
937
+ if (opts.legacyRuntime) {
938
+ throw Object.assign(new Error("--legacy-runtime is no longer supported: new bots run on the HQ Fleet Runtime. Drop --legacy-runtime."), { expected: true });
798
939
  }
799
940
  if (opts.provider && !isBotRuntimeId(opts.provider)) {
800
941
  throw Object.assign(new Error(`--provider must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
@@ -802,8 +943,7 @@ async function runBotCreateLocked(name, opts) {
802
943
  if (opts.provider && opts.runtime && opts.provider !== opts.runtime) {
803
944
  throw Object.assign(new Error("--provider and --runtime must name the same provider when both are used."), { expected: true });
804
945
  }
805
- const selectedProvider = opts.provider ?? opts.runtime ?? (requestedFleetRuntime ? await promptFleetProvider() : undefined);
806
- const runtime = selectedProvider ?? "claude";
946
+ const runtime = opts.provider ?? opts.runtime ?? "claude";
807
947
  if (!isBotRuntimeId(runtime)) {
808
948
  throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
809
949
  }
@@ -815,7 +955,6 @@ async function runBotCreateLocked(name, opts) {
815
955
  const displayName = opts.displayName !== undefined ? validateBotDisplayName(opts.displayName) : undefined;
816
956
  const kickoff = opts.kickoff !== undefined ? validateBotKickoff(opts.kickoff) : undefined;
817
957
  const model = opts.model !== undefined ? validateBotModel(opts.model) : undefined;
818
- const effort = opts.effort !== undefined ? validateBotEffort(runtime, opts.effort) : undefined;
819
958
  const dir = botDir(name);
820
959
  const existingBeforeRoot = readBotConfig(dir);
821
960
  // Only HQ's setup assistant is idempotent; any other bot keeps failing fast.
@@ -838,24 +977,11 @@ async function runBotCreateLocked(name, opts) {
838
977
  // cost an identity).
839
978
  const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
840
979
  const { kind, companies } = resolveCreateKind(opts, worker);
841
- if (requestedFleetRuntime && kind === "company" && companies.length !== 1) {
980
+ if (kind === "company" && companies.length !== 1) {
842
981
  throw Object.assign(new Error("An HQ Fleet company bot must belong to exactly one company."), {
843
982
  expected: true,
844
983
  });
845
984
  }
846
- // Companies with the fleet-default flag get their personal bots on
847
- // the HQ Fleet Runtime; --legacy-runtime keeps the model-CLI runner.
848
- const fleetCandidate = requestedFleetRuntime ||
849
- (!opts.legacyRuntime && kind === "personal" && !opts.effort && !opts.lean);
850
- // Owner turns on the bot may use only these companies: the owner's own,
851
- // and only where the company turned the flag on.
852
- const flaggedCompanies = fleetCandidate && kind === "personal" ? await fleetDefaultCompanies(hqRoot) : [];
853
- const useFleetRuntime = requestedFleetRuntime || (fleetCandidate && fleetRuntimeIsDefault(flaggedCompanies));
854
- const ownerCompanies = useFleetRuntime
855
- ? kind === "personal"
856
- ? await fleetOwnerCompanies()
857
- : companies
858
- : [];
859
985
  const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
860
986
  if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
861
987
  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 });
@@ -870,12 +996,8 @@ async function runBotCreateLocked(name, opts) {
870
996
  expected: true,
871
997
  });
872
998
  }
873
- const fleetInstallation = useFleetRuntime
874
- ? await installPinnedFleetLocalDistribution()
875
- : null;
876
- const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
877
- const fleetProvider = fleetInstallation ? fleetProviderForRuntime(runtime, model) : null;
878
- const fleetAuthPending = fleetProvider !== null && fleetProviderNeedsSignIn(fleetProvider);
999
+ const fleetProvider = fleetProviderForRuntime(runtime, model);
1000
+ const fleetAuthPending = fleetProviderNeedsSignIn(fleetProvider);
879
1001
  const token = await ownerToken();
880
1002
  const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
881
1003
  if (!owner)
@@ -968,6 +1090,9 @@ async function runBotCreateLocked(name, opts) {
968
1090
  // of sending another create that the entity store will reject.
969
1091
  if (isSetupBot && (await adoptOwnedSetupBot(true)))
970
1092
  return;
1093
+ const fleetInstallation = await installTargetFleetLocalDistribution();
1094
+ const fleetPython = await ensureFleetPython();
1095
+ const ownerCompanies = kind === "personal" ? await fleetOwnerCompanies() : companies;
971
1096
  // A company bot's companies are resolved first (a typo must not cost an
972
1097
  // identity) and sent with the create: the server makes the bot a member of
973
1098
  // each and refuses the whole create if the owner cannot add agents there.
@@ -985,12 +1110,11 @@ async function runBotCreateLocked(name, opts) {
985
1110
  // instead of creating a new one. The machine secret never travels.
986
1111
  const localConfig = buildBotLocalConfig({
987
1112
  runtime,
988
- ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
1113
+ engine: fleetInstallation.engine,
989
1114
  kind,
990
1115
  ...(kind === "company" ? { companies } : {}),
991
1116
  ...(model ? { model } : {}),
992
- ...(effort ? { effort } : {}),
993
- ...(opts.autoApprove === false ? { autoApprove: false } : {}),
1117
+ ...(opts.autoApprove === true ? { autoApprove: true } : {}),
994
1118
  ...(worker
995
1119
  ? { workerSource: "worker", workerId: worker.id, ...(worker.company ? { companySlug: worker.company } : {}) }
996
1120
  : { workerSource: "scaffold" }),
@@ -1052,13 +1176,12 @@ async function runBotCreateLocked(name, opts) {
1052
1176
  agentUid,
1053
1177
  ownerUid: created.agent.ownerUid ?? owner,
1054
1178
  runtime,
1055
- ...(fleetInstallation ? { engine: fleetInstallation.engine } : {}),
1179
+ engine: fleetInstallation.engine,
1056
1180
  kind,
1057
1181
  // A company bot's list is filled in below, with the invites that went through.
1058
1182
  ...(kind === "company" ? { companies: [] } : {}),
1059
1183
  ...(model ? { model } : {}),
1060
- ...(effort ? { effort } : {}),
1061
- ...(opts.autoApprove === false ? { autoApprove: false } : {}),
1184
+ ...(opts.autoApprove === true ? { autoApprove: true } : {}),
1062
1185
  hqRoot,
1063
1186
  ...(worker
1064
1187
  ? {
@@ -1080,7 +1203,6 @@ async function runBotCreateLocked(name, opts) {
1080
1203
  ...(displayName ? { displayName } : {}),
1081
1204
  ...(intro ? { intro } : {}),
1082
1205
  ...(kickoff ? { kickoff } : {}),
1083
- ...(opts.lean && runtime === "claude" ? { lean: true } : {}),
1084
1206
  createdAt: new Date().toISOString(),
1085
1207
  enabled: !fleetAuthPending,
1086
1208
  ...(fleetAuthPending ? { fleetAuthPending: true } : {}),
@@ -1110,28 +1232,28 @@ async function runBotCreateLocked(name, opts) {
1110
1232
  const scaffoldOpts = { memoryDir: config.memoryDir };
1111
1233
  const scaffold = worker
1112
1234
  ? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
1113
- : scaffoldBotWorker(hqRoot, name, agentUid, fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime, undefined, scaffoldOpts);
1114
- if (fleetInstallation)
1115
- fs.rmSync(path.join(dir, "status.json"), { force: true });
1116
- const fleetBootstrap = fleetInstallation && fleetPython
1117
- ? await bootstrapFleetLocalBot({
1118
- agentUid,
1119
- ownerUid: config.ownerUid,
1120
- hqRoot,
1121
- botDir: dir,
1122
- installation: fleetInstallation,
1123
- machineCredentials: botCredsPath(dir),
1124
- systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
1125
- provider: fleetProvider,
1126
- ownerCompanies,
1127
- firstStart: {
1128
- intro: introDmText(name, runtime, intro, { kind, companies }),
1129
- ...(kickoff ? { kickoff } : {}),
1130
- },
1131
- }, { start: opts.start !== false && !fleetAuthPending, python: fleetPython })
1132
- : null;
1133
- const fleetProfile = fleetBootstrap?.profile ?? null;
1134
- const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
1235
+ : scaffoldBotWorker(hqRoot, name, agentUid, HQ_FLEET_RUNTIME_ENGINE_ID, undefined, scaffoldOpts);
1236
+ fs.rmSync(path.join(dir, "status.json"), { force: true });
1237
+ const introText = introDmText(name, runtime, intro, { kind, companies });
1238
+ const toolOffer = isSetupBot && kickoff ? fleetSetupToolOffer(dir, config) : null;
1239
+ const fleetBootstrap = await bootstrapFleetLocalBot({
1240
+ agentUid,
1241
+ ownerUid: config.ownerUid,
1242
+ hqRoot,
1243
+ botDir: dir,
1244
+ installation: fleetInstallation,
1245
+ machineCredentials: botCredsPath(dir),
1246
+ systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
1247
+ provider: fleetProvider,
1248
+ ownerCompanies,
1249
+ ...(opts.autoApprove === true ? { autoApprove: true } : {}),
1250
+ firstStart: toolOffer
1251
+ ? { intro: setupToolOfferText(toolOffer, setupWelcomeOpening(intro)) }
1252
+ : { intro: introText, ...(kickoff ? { kickoff } : {}) },
1253
+ }, { start: opts.start !== false && !fleetAuthPending, python: fleetPython });
1254
+ syncFleetRuntimeJobs(name, dir, config);
1255
+ const fleetProfile = fleetBootstrap.profile;
1256
+ const runtimeLabel = HQ_FLEET_RUNTIME_ENGINE_ID;
1135
1257
  createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtimeLabel}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
1136
1258
  const created_ = {
1137
1259
  outcome: "created",
@@ -1142,7 +1264,7 @@ async function runBotCreateLocked(name, opts) {
1142
1264
  kind,
1143
1265
  companies,
1144
1266
  runtime: runtimeLabel,
1145
- engine: fleetInstallation?.engine ?? null,
1267
+ engine: fleetInstallation.engine,
1146
1268
  profile: fleetProfile,
1147
1269
  dir,
1148
1270
  worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
@@ -1154,14 +1276,11 @@ async function runBotCreateLocked(name, opts) {
1154
1276
  intro: intro ?? null,
1155
1277
  kickoff: kickoff ?? null,
1156
1278
  model: model ?? null,
1157
- effort: fleetInstallation ? null : effort ?? DEFAULT_BOT_EFFORT,
1158
- ...(fleetProvider ? { provider: fleetProvider.id, needsSignIn: fleetAuthPending, ...(fleetAuthPending ? { authCommand: fleetAuthCommand(name, config) } : {}) } : {}),
1279
+ effort: null,
1280
+ provider: fleetProvider.id,
1281
+ needsSignIn: fleetAuthPending,
1282
+ ...(fleetAuthPending ? { authCommand: fleetAuthCommand(name, config) } : {}),
1159
1283
  };
1160
- let daemonMessage = null;
1161
- if (!fleetInstallation && opts.daemon !== false && process.platform === "darwin") {
1162
- const r = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot }));
1163
- daemonMessage = r.message;
1164
- }
1165
1284
  if (!opts.json) {
1166
1285
  console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtimeLabel})`));
1167
1286
  console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
@@ -1171,38 +1290,24 @@ async function runBotCreateLocked(name, opts) {
1171
1290
  console.log(chalk.dim(` worker: ${scaffold.dir}`));
1172
1291
  console.log(chalk.dim(` memory: ${scaffold.memoryDir} (${memoryMode})`));
1173
1292
  console.log(chalk.dim(` state: ${dir}`));
1174
- if (fleetProfile)
1175
- console.log(chalk.dim(` profile: ${fleetProfile}`));
1176
- if (fleetProvider && fleetProviderNeedsSignIn(fleetProvider)) {
1293
+ console.log(chalk.dim(` profile: ${fleetProfile}`));
1294
+ if (fleetAuthPending) {
1177
1295
  console.log(chalk.yellow(` sign it in before it can answer: hq bot auth ${name} --provider ${runtime === "grok" ? "grok" : "chatgpt"}`));
1178
1296
  }
1179
- if (daemonMessage)
1180
- console.log(chalk.dim(` ${daemonMessage}`));
1181
- }
1182
- if (fleetInstallation) {
1183
- if (opts.json) {
1184
- console.log(JSON.stringify({ ok: true, name, ...created_, daemon: null, running: fleetBootstrap?.running ?? false }, null, 2));
1185
- }
1186
- return;
1187
- }
1188
- if (opts.start !== false) {
1189
- if (daemonMessage && /loaded\)/.test(daemonMessage)) {
1190
- // RunAtLoad already started it.
1191
- const running = await waitFor(() => readBotStatus(dir)?.state === "running", 20_000);
1192
- if (opts.json) {
1193
- console.log(JSON.stringify({ ok: true, name, ...created_, daemon: daemonMessage, running }, null, 2));
1194
- }
1195
- else if (running)
1196
- console.log(chalk.green(`${name} is online — it will DM you an introduction shortly.`));
1197
- else
1198
- console.log(chalk.yellow(`${name} has not reported running yet; check hq bot logs ${name}`));
1199
- return;
1200
- }
1201
- await startBot(name, dir, config, opts.json ? "json" : "text", { ...(isSetupBot ? { ok: true } : {}), ...created_, daemon: daemonMessage });
1202
- return;
1203
1297
  }
1204
1298
  if (opts.json) {
1205
- console.log(JSON.stringify({ ok: true, name, ...created_, daemon: daemonMessage, running: false }, null, 2));
1299
+ const status = readBotStatus(dir);
1300
+ console.log(JSON.stringify({
1301
+ ok: true,
1302
+ name,
1303
+ ...created_,
1304
+ daemon: null,
1305
+ running: fleetBootstrap.running,
1306
+ how: fleetBootstrap.running ? "started by the HQ Fleet Runtime" : null,
1307
+ state: status?.state ?? "unknown",
1308
+ pid: status?.pid ?? null,
1309
+ lastError: status?.lastError ?? null,
1310
+ }, null, 2));
1206
1311
  }
1207
1312
  }
1208
1313
  // ── companies ────────────────────────────────────────────────────────────────
@@ -1326,12 +1431,20 @@ async function runBotResident(nameArg) {
1326
1431
  // A bot with no company (the personal bot first-run setup creates) syncs the
1327
1432
  // HQ's active company. Only one local bot registers the job per company, and
1328
1433
  // the first sync starts in the background right after the job is created.
1329
- const runHotSignalSync = (input) => runHotSignalSyncJob({
1434
+ // US-006: a fired job runs the signals engine (garden -> adapters ->
1435
+ // extract -> garden -> upload) and replies in the storyboarded format.
1436
+ const runHotSignalSync = (input) =>
1437
+ // The engine is gated default-off (cli.bot-signals-engine-job); with the
1438
+ // flag off the job runs the hot-channel sync alone, as before US-006.
1439
+ signalsEngineForJob(runSignals).then((signals) => runHotSignalSyncJob({
1330
1440
  ...input,
1441
+ hqRoot: config.hqRoot,
1442
+ timeZone: input.timeZone ?? machineTimeZone(),
1443
+ ...(signals ? { signals } : {}),
1331
1444
  sync: (syncInput) => runCompanyHotChannelSync({ hqRoot: config.hqRoot, ...syncInput }),
1332
1445
  // A new pass per run: the login preflight and the 20-document cap are per run.
1333
1446
  signalsPass: createClaudeSignalsPass({ hqRoot: config.hqRoot, company: input.company }),
1334
- });
1447
+ }));
1335
1448
  let otherBotDirs = [];
1336
1449
  try {
1337
1450
  otherBotDirs = listBotNames(botsRoot()).map((n) => botDir(n));
@@ -1355,7 +1468,7 @@ async function runBotResident(nameArg) {
1355
1468
  api,
1356
1469
  runtime: runtimeFor(config.runtime),
1357
1470
  ownerContext: () => owner.get(),
1358
- hotSignalSync: ({ company, catchUp, now, sendAlert }) => runHotSignalSync({ company, catchUp, now, sendAlert }),
1471
+ hotSignalSync: (input) => runHotSignalSync(input),
1359
1472
  // US-005: decision picks in the owner DM, with the bot's own machine token
1360
1473
  // (never the owner's login).
1361
1474
  decisions: new DecisionsClient(token),
@@ -1427,8 +1540,9 @@ async function runBotStatus(nameArg, opts) {
1427
1540
  await decorateOnline([row]);
1428
1541
  const status = readBotStatus(dir);
1429
1542
  const creds = readBotCredsIdentity(dir);
1543
+ const channels = fleet ? fleetLocalChannels(readFleetLocalGatewayState(config.agentUid), row.processAlive) : null;
1430
1544
  if (opts.json) {
1431
- console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
1545
+ console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine, channels } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
1432
1546
  return;
1433
1547
  }
1434
1548
  console.log(`${chalk.bold(name)} ${row.agentUid}`);
@@ -1452,7 +1566,14 @@ async function runBotStatus(nameArg, opts) {
1452
1566
  console.log(` inbox: last poll ${status.lastInboxPollAt}`);
1453
1567
  }
1454
1568
  console.log(` answering: ${row.busy ? chalk.green(`yes${row.busySince ? `, since ${row.busySince}` : ""}`) : chalk.gray("no")}`);
1455
- console.log(` launchd: ${row.daemonInstalled ? (row.daemonLoaded ? "installed, loaded" : "installed, not loaded") : "not installed"}`);
1569
+ if (fleet) {
1570
+ console.log(` service: ${fleetLocalServiceInstalled(config.agentUid) ? "installed (HQ Fleet gateway, starts at login)" : row.serviceInstallPending ? "not installed yet; installs on hq bot start" : "not installed"}`);
1571
+ if (channels)
1572
+ console.log(` channels: Slack ${channels.slack.replace(/_/g, " ")}, Telegram ${channels.telegram.replace(/_/g, " ")}`);
1573
+ }
1574
+ else {
1575
+ console.log(` launchd: ${row.daemonInstalled ? (row.daemonLoaded ? "installed, loaded" : "installed, not loaded") : "not installed"}`);
1576
+ }
1456
1577
  if (row.daemonNodeMissing) {
1457
1578
  console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
1458
1579
  }
@@ -2105,10 +2226,38 @@ export async function runBotIntro(nameArg) {
2105
2226
  export function botExists(name) {
2106
2227
  return readBotConfig(botDir(name)) !== null;
2107
2228
  }
2229
+ export async function updateFleetRuntimeBot(nameArg, opts = {}) {
2230
+ const name = validateBotName(nameArg);
2231
+ return withBotLock(name, async () => {
2232
+ const b = requireBot(name);
2233
+ if (!isFleetRuntimeBot(b.config) || hasPromotionHold(b.dir))
2234
+ return "unchanged";
2235
+ const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config, { pointerOnly: true });
2236
+ if (!adopted && b.config.fleetRestartPending !== true)
2237
+ return "unchanged";
2238
+ const config = readBotConfig(b.dir) ?? b.config;
2239
+ if (adopted) {
2240
+ refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(b.name, config));
2241
+ syncFleetRuntimeJobs(b.name, b.dir, config);
2242
+ }
2243
+ if (opts.busy?.()) {
2244
+ patchBotConfig(b.dir, { fleetRestartPending: true });
2245
+ return "waiting";
2246
+ }
2247
+ const result = controlFleetLocalBot(config.agentUid, "install");
2248
+ if (result.ok !== true) {
2249
+ patchBotConfig(b.dir, { fleetRestartPending: true });
2250
+ throw new Error(typeof result.summary === "string" && result.summary ? result.summary : "the HQ Fleet gateway did not restart");
2251
+ }
2252
+ patchBotConfig(b.dir, { fleetServiceInstallPending: false, fleetRestartPending: false, enabled: true });
2253
+ return "updated";
2254
+ }, { command: "hq daemon service fleet-bots-update" });
2255
+ }
2108
2256
  /** `hq bot restart <name>`: stop, then start (launchd when installed). */
2109
2257
  export async function runBotRestart(nameArg, opts = {}) {
2110
2258
  const b = requireBot(nameArg);
2111
2259
  if (isFleetRuntimeBot(b.config)) {
2260
+ refuseHeldFleetBot(b.dir);
2112
2261
  const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
2113
2262
  runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "restart", opts.json === true || opts.output === "json");
2114
2263
  return;
@@ -2150,8 +2299,7 @@ async function runBotAuth(nameArg, opts) {
2150
2299
  model: result.model,
2151
2300
  ...(b.config.fleetAuthPending === true ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
2152
2301
  });
2153
- const pid = fleetLocalGatewayPid(b.config.agentUid);
2154
- const running = pid !== null && isPidAlive(pid);
2302
+ const running = await fleetGatewayComesUp(b.config.agentUid, result.restarted ? fleetStartWaitMs() : 0);
2155
2303
  if (opts.json) {
2156
2304
  console.log(JSON.stringify({ ok: true, name: b.name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result, needsSignIn: false, running, serviceInstallPending: b.config.fleetAuthPending === true && !result.restarted }, null, 2));
2157
2305
  return;
@@ -2347,7 +2495,7 @@ export function configFromLocalConfig(input) {
2347
2495
  ...(kind === "company" ? { companies: local?.companies ?? [] } : {}),
2348
2496
  ...(model ? { model } : {}),
2349
2497
  ...(effort ? { effort } : {}),
2350
- ...(local?.autoApprove === false ? { autoApprove: false } : {}),
2498
+ ...(local?.autoApprove === true ? { autoApprove: true } : {}),
2351
2499
  hqRoot: input.hqRoot,
2352
2500
  workerDir,
2353
2501
  ...(workerSource === "worker" && workerId ? { workerSource, workerId } : {}),
@@ -2446,6 +2594,7 @@ async function adoptRemoteBotLocked(input) {
2446
2594
  systemPrompt: fleetBotSystemPrompt(name, config),
2447
2595
  provider: fleetProviderForRuntime(config.runtime, config.model),
2448
2596
  ownerCompanies: await fleetOwnerCompanies(),
2597
+ ...(config.autoApprove === true ? { autoApprove: true } : {}),
2449
2598
  ...(input.preserveFirstStart
2450
2599
  ? {
2451
2600
  firstStart: {
@@ -2762,20 +2911,18 @@ export function registerBotCommand(program) {
2762
2911
  .description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
2763
2912
  .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`)
2764
2913
  .option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
2765
- .option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`)
2766
- .option("--provider <provider>", `HQ Fleet provider: ${BOT_RUNTIMES.join("|")} (interactive Fleet creation prompts when omitted)`)
2767
- .option("--hq-fleet-runtime", "Create this personal bot with the local HQ Fleet Runtime")
2768
- .option("--legacy-runtime", "Run this bot with the model CLI even where the HQ Fleet Runtime is the default")
2769
- .option("--model <model>", "Model override passed to the runtime CLI")
2770
- .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})`)
2771
- .option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
2914
+ .option("--runtime <runtime>", `Model provider the bot answers with: ${BOT_RUNTIMES.join("|")} (same as --provider; default claude)`)
2915
+ .option("--provider <provider>", `HQ Fleet provider: ${BOT_RUNTIMES.join("|")} (default claude)`)
2916
+ .option("--hq-fleet-runtime", "Accepted for compatibility; every new bot runs on the HQ Fleet Runtime")
2917
+ .option("--legacy-runtime", "No longer supported; new bots always run on the HQ Fleet Runtime")
2918
+ .option("--model <model>", "Model the bot answers with")
2919
+ .option("--auto-approve", "Turn the runtime's command approval prompts off for every sender (default: prompts stay on; the owner's own turns are always allowed)")
2772
2920
  .option("--worker <workerId>", "Run an existing company or core worker (see hq bot workers): its instructions, skills, context, and company policies; a company worker binds the bot to that company on every turn")
2773
2921
  .option("--display-name <name>", `The human name people see for the bot in HQ (e.g. Maya; up to ${BOT_DISPLAY_NAME_MAX_CHARS} chars). The handle stays <name>`)
2774
2922
  .option("--intro <text>", `The hello the bot DMs you when it first comes online (verbatim, up to ${BOT_INTRO_MAX_CHARS} chars)`)
2775
2923
  .option("--kickoff <prompt>", `A first task the bot starts on by itself right after its hello: one model turn run as if you had sent it, answered in your DM, once (up to ${BOT_KICKOFF_MAX_CHARS} chars)`)
2776
2924
  .option("--memory <where>", "Where the bot keeps its memory: synced (personal/workers/<name>/memory in HQ) or local (~/.hq/bots/<name>/memory, this computer only)", "synced")
2777
- .option("--lean", "Claude bots: run each turn without this computer's plugins, MCP servers and HQ's session hooks (HQ's tool hooks still run), so replies take seconds")
2778
- .option("--no-daemon", "Do not install the launchd agent (macOS)")
2925
+ .option("--no-daemon", "Accepted for compatibility; HQ Fleet bots run under their own background service")
2779
2926
  .option("--no-start", "Create without starting")
2780
2927
  .option("--json", "Print machine-readable JSON")
2781
2928
  .action((name, opts) => wrap(() => runBotCreate(name, opts))());
@@ -2822,6 +2969,7 @@ export function registerBotCommand(program) {
2822
2969
  .action((name, opts) => wrap(async () => {
2823
2970
  const b = requireBot(name);
2824
2971
  if (isFleetRuntimeBot(b.config)) {
2972
+ refuseHeldFleetBot(b.dir);
2825
2973
  const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
2826
2974
  runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "start", opts.json === true);
2827
2975
  return;
@@ -2835,6 +2983,7 @@ export function registerBotCommand(program) {
2835
2983
  .action((name, opts) => wrap(async () => {
2836
2984
  const b = requireBot(name);
2837
2985
  if (isFleetRuntimeBot(b.config)) {
2986
+ refuseHeldFleetBot(b.dir);
2838
2987
  runFleetRuntimeControl(b.name, b.config, "stop", opts.json === true);
2839
2988
  return;
2840
2989
  }