@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.
- package/CHANGELOG.md +420 -0
- package/assets/bot-workers/setup/worker.yaml +91 -34
- package/dist/command-catalog.generated.d.ts +96 -19
- package/dist/command-catalog.generated.js +124 -21
- package/dist/commands/bot-jobs.d.ts +21 -18
- package/dist/commands/bot-jobs.js +127 -39
- package/dist/commands/bot.d.ts +7 -4
- package/dist/commands/bot.js +289 -140
- package/dist/commands/core-jobs-schedule.js +10 -0
- package/dist/commands/daemon.js +37 -0
- package/dist/commands/files.d.ts +41 -6
- package/dist/commands/files.js +108 -27
- package/dist/commands/pack-start-bot.d.ts +4 -6
- package/dist/commands/pack-start-bot.js +4 -8
- package/dist/commands/signals-run.d.ts +45 -0
- package/dist/commands/signals-run.js +175 -0
- package/dist/commands/signals.d.ts +1 -0
- package/dist/commands/signals.js +3 -0
- package/dist/commands/sources.d.ts +11 -0
- package/dist/commands/sources.js +137 -25
- package/dist/commands/staff/reset-test-account.d.ts +10 -0
- package/dist/commands/staff/reset-test-account.js +18 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/api.d.ts +3 -2
- package/dist/lib/bot/config.d.ts +2 -0
- package/dist/lib/bot/fleet-cron.d.ts +121 -0
- package/dist/lib/bot/fleet-cron.js +319 -0
- package/dist/lib/bot/fleet-local.d.ts +113 -2
- package/dist/lib/bot/fleet-local.js +597 -35
- package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
- package/dist/lib/bot/fleet-owner-companies.js +30 -0
- package/dist/lib/bot/fleet-watch.d.ts +25 -0
- package/dist/lib/bot/fleet-watch.js +51 -0
- package/dist/lib/bot/index.d.ts +1 -1
- package/dist/lib/bot/index.js +1 -1
- package/dist/lib/bot/job-run.d.ts +5 -4
- package/dist/lib/bot/job-run.js +5 -4
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
- package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
- package/dist/lib/bot/local-jobs.d.ts +13 -2
- package/dist/lib/bot/local-jobs.js +17 -4
- package/dist/lib/bot/progress.d.ts +14 -0
- package/dist/lib/bot/progress.js +47 -1
- package/dist/lib/bot/run.d.ts +55 -2
- package/dist/lib/bot/run.js +192 -10
- package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
- package/dist/lib/bot/setup-tool-offer.js +16 -0
- package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
- package/dist/lib/bot/signals-engine-flag.js +30 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +29 -9
- package/dist/lib/daemon/legacy.d.ts +6 -0
- package/dist/lib/daemon/legacy.js +16 -10
- package/dist/lib/daemon/run.d.ts +1 -0
- package/dist/lib/daemon/run.js +10 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.d.ts +13 -1
- package/dist/lib/flag-registry.js +46 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
- package/dist/lib/hot-channels/adapters/attio.js +367 -29
- package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
- package/dist/lib/hot-channels/adapters/gmail.js +59 -2
- package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
- package/dist/lib/hot-channels/adapters/linear.js +71 -1
- package/dist/lib/hot-channels/adapters/meetings.js +4 -1
- package/dist/lib/hot-channels/adapters/slack.js +7 -3
- package/dist/lib/hot-channels/alert.js +1 -1
- package/dist/lib/hot-channels/contract.d.ts +11 -0
- package/dist/lib/hot-channels/contract.js +1 -0
- package/dist/lib/hot-channels/cursors.js +1 -1
- package/dist/lib/hot-channels/landing.d.ts +6 -2
- package/dist/lib/hot-channels/landing.js +13 -6
- package/dist/lib/hot-channels/ledger.js +2 -0
- package/dist/lib/hot-channels/loop.js +18 -4
- package/dist/lib/hot-channels/registry.js +9 -1
- package/dist/lib/hot-channels/run.d.ts +18 -0
- package/dist/lib/hot-channels/run.js +15 -4
- package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
- package/dist/lib/hot-channels/signals-pass.js +7 -5
- package/dist/lib/hot-channels/sync-lock.js +22 -6
- package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
- package/dist/lib/mesh/live/daemon/install.js +117 -30
- package/dist/lib/signals/adapters-catalog.d.ts +77 -0
- package/dist/lib/signals/adapters-catalog.js +194 -0
- package/dist/lib/signals/engine.d.ts +196 -0
- package/dist/lib/signals/engine.js +495 -0
- package/dist/lib/signals/failure-alert.d.ts +89 -0
- package/dist/lib/signals/failure-alert.js +175 -0
- package/dist/lib/signals/local-date.d.ts +3 -0
- package/dist/lib/signals/local-date.js +6 -0
- package/dist/lib/signals/projection.d.ts +42 -0
- package/dist/lib/signals/projection.js +67 -0
- package/dist/lib/signals/reply.d.ts +112 -0
- package/dist/lib/signals/reply.js +368 -0
- package/dist/lib/signals/run-lock.d.ts +36 -0
- package/dist/lib/signals/run-lock.js +148 -0
- package/dist/lib/signals/run-record.d.ts +24 -0
- package/dist/lib/signals/run-record.js +82 -0
- package/dist/lib/signals/sanitize.d.ts +105 -0
- package/dist/lib/signals/sanitize.js +439 -0
- package/dist/lib/signals/settings.d.ts +10 -0
- package/dist/lib/signals/settings.js +48 -0
- package/dist/lib/signals/steps/adapters.d.ts +14 -0
- package/dist/lib/signals/steps/adapters.js +156 -0
- package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
- package/dist/lib/signals/steps/entity-similarity.js +47 -0
- package/dist/lib/signals/steps/extract.d.ts +113 -0
- package/dist/lib/signals/steps/extract.js +765 -0
- package/dist/lib/signals/steps/garden.d.ts +24 -0
- package/dist/lib/signals/steps/garden.js +250 -0
- package/dist/lib/signals/steps/types.d.ts +112 -0
- package/dist/lib/signals/steps/types.js +4 -0
- package/dist/lib/signals/steps/upload.d.ts +43 -0
- package/dist/lib/signals/steps/upload.js +324 -0
- package/dist/lib/signals/write-guard.d.ts +63 -0
- package/dist/lib/signals/write-guard.js +287 -0
- package/dist/utils/resident-package-preload.js +16 -0
- package/package.json +9 -9
- package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
- package/dist/lib/bot/fleet-default-flag.js +0 -127
package/dist/commands/bot.js
CHANGED
|
@@ -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] [--
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
793
|
-
|
|
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
|
|
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 (
|
|
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
|
|
874
|
-
|
|
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
|
-
|
|
1113
|
+
engine: fleetInstallation.engine,
|
|
989
1114
|
kind,
|
|
990
1115
|
...(kind === "company" ? { companies } : {}),
|
|
991
1116
|
...(model ? { model } : {}),
|
|
992
|
-
...(
|
|
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
|
-
|
|
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
|
-
...(
|
|
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,
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
},
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const fleetProfile = fleetBootstrap
|
|
1134
|
-
const runtimeLabel =
|
|
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
|
|
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:
|
|
1158
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: (
|
|
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
|
-
|
|
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
|
|
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 ===
|
|
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
|
|
2766
|
-
.option("--provider <provider>", `HQ Fleet provider: ${BOT_RUNTIMES.join("|")} (
|
|
2767
|
-
.option("--hq-fleet-runtime", "
|
|
2768
|
-
.option("--legacy-runtime", "
|
|
2769
|
-
.option("--model <model>", "Model
|
|
2770
|
-
.option("--
|
|
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("--
|
|
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
|
}
|