@indigoai-us/hq-cli 5.345.199 → 5.345.201
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 +40 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +44 -0
- package/dist/command-catalog.generated.js +52 -0
- package/dist/commands/bot-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +53 -0
- package/dist/commands/bot.js +239 -30
- package/dist/commands/cloud.d.ts +39 -0
- package/dist/commands/cloud.js +173 -82
- package/dist/commands/creators.js +2 -1
- package/dist/commands/deploy-domains.js +8 -4
- package/dist/commands/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/sync-held.js +2 -2
- package/dist/lib/bot/api.d.ts +8 -0
- package/dist/lib/bot/api.js +9 -0
- package/dist/lib/bot/fleet-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +79 -2
- package/dist/lib/bot/fleet-local.js +246 -74
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- package/dist/lib/bot/jobs/source-report.d.ts +31 -0
- package/dist/lib/bot/jobs/source-report.js +276 -0
- package/dist/lib/bot/legacy-convert.d.ts +107 -4
- package/dist/lib/bot/legacy-convert.js +159 -13
- package/dist/lib/bot/legacy-provider-login.d.ts +34 -0
- package/dist/lib/bot/legacy-provider-login.js +144 -0
- package/dist/lib/bot/run.js +20 -1
- package/dist/lib/doctor/checks/bots.d.ts +4 -0
- package/dist/lib/doctor/checks/bots.js +33 -0
- package/dist/lib/lanes/mesh-events.js +3 -1
- package/dist/lib/lanes/store.js +7 -0
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/types.js +1 -0
- package/dist/lib/lanes/watcher.d.ts +0 -33
- package/dist/lib/lanes/watcher.js +7 -0
- package/dist/lib/mesh/api.js +2 -1
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +2 -0
- package/dist/utils/cognito-callback-port.js +10 -5
- package/dist/utils/cognito-config.js +6 -1
- package/dist/utils/cognito-session.js +6 -1
- package/dist/utils/interactive-caller.d.ts +8 -0
- package/dist/utils/interactive-caller.js +10 -0
- package/dist/utils/login-browser.js +2 -1
- package/dist/utils/resident-package-preload.js +57 -4
- package/package.json +7 -7
package/dist/commands/bot.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { withLocalJobs } from "../lib/bot/local-jobs.js";
|
|
2
|
-
import {
|
|
2
|
+
import { clearFleetMoveRecords, fleetMoveHoldPath, fleetMoveRecentlyFailed, holdReportDue, legacyConversionDecision, legacyHoldFailureReport, legacyHoldNotice, legacyHoldNotifiesOwner, legacyMoveState, ownerHoldNoticeDue, readFleetMoveFailure, readFleetMoveHold, writeFleetMoveFailure, writeFleetMoveHold, } from "../lib/bot/legacy-convert.js";
|
|
3
|
+
import { legacyProviderLoginProbe } from "../lib/bot/legacy-provider-login.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { CLI_VERSION } from "../cli-version.js";
|
|
3
6
|
import { runSignals } from "../lib/signals/engine.js";
|
|
4
7
|
import { BOT_SIGNALS_ENGINE_JOB_FLAG, signalsEngineForJob } from "../lib/bot/signals-engine-flag.js";
|
|
5
8
|
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
@@ -70,7 +73,9 @@ import { unresolvedLastFailure } from "../lib/doctor/checks/bots.js";
|
|
|
70
73
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
71
74
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
72
75
|
import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
|
|
73
|
-
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";
|
|
76
|
+
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, importFleetLocalProviderLogin, fleetProviderNeedsSignIn, fleetOwnerCompanies, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, installTargetFleetLocalDistribution, resolveFleetLocalPointerTarget, resolveFleetLocalTargetRelease, fleetLocalProfileDir, fleetProfileOwnerCompanies, fleetLocalChannels, readFleetLocalGatewayState, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
77
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
78
|
+
import { fleetInstallProgressReporter, runBotRuntimeInstall, runBotRuntimeStatus } from "./bot-runtime.js";
|
|
74
79
|
function collectRepeated(value, previous) {
|
|
75
80
|
return [...previous, value];
|
|
76
81
|
}
|
|
@@ -332,7 +337,15 @@ export function botTokenSupplier(dir) {
|
|
|
332
337
|
process.env.HQ_MACHINE_TOKEN_STATE_DIR = botTokenStateDir(dir);
|
|
333
338
|
return () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
334
339
|
}
|
|
335
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Whether `hq bot` may open a browser sign-in for its owner: a TTY on stdin,
|
|
342
|
+
* and the shared interactive-caller gate (no lane, daemon, bot runtime, or
|
|
343
|
+
* HQ_NO_BROWSER_LOGIN child).
|
|
344
|
+
*/
|
|
345
|
+
export function ownerSignInAllowed() {
|
|
346
|
+
return gatedInteractiveLogin(process.stdin.isTTY === true);
|
|
347
|
+
}
|
|
348
|
+
async function ownerToken(interactive = ownerSignInAllowed()) {
|
|
336
349
|
if (peekHqApiKey()) {
|
|
337
350
|
throw Object.assign(new Error("hq bot needs your HQ login session (HQ_API_KEY is not supported here). Run `hq login`."), {
|
|
338
351
|
expected: true,
|
|
@@ -358,11 +371,39 @@ export function runtimeSignedIn(status, processAlive) {
|
|
|
358
371
|
return false;
|
|
359
372
|
return status.runtimeSignInCheck?.signedIn ?? null;
|
|
360
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Whether the bot's model provider must be signed in before it can run, from
|
|
376
|
+
* what this computer knows: the bot's settings, its last reported failure or
|
|
377
|
+
* sign-in check, and (legacy) why it has not moved to the HQ Fleet Runtime.
|
|
378
|
+
*/
|
|
379
|
+
export function brainSignInFor(name, config, status, legacy) {
|
|
380
|
+
const provider = config.runtime;
|
|
381
|
+
const command = fleetAuthCommand(name, config);
|
|
382
|
+
if (legacy && !legacy.decision.convert && legacyHoldNotifiesOwner(legacy.decision.reason)) {
|
|
383
|
+
return { provider, needed: true, reason: legacy.notice?.cause ?? legacy.decision.detail, command };
|
|
384
|
+
}
|
|
385
|
+
if (config.fleetAuthPending === true)
|
|
386
|
+
return { provider, needed: true, reason: `${name} has not signed in to ${runtimeDisplayName(provider)} yet.`, command };
|
|
387
|
+
// A legacy bot that moves at its next start needs no sign-in, whatever its old runner last reported.
|
|
388
|
+
if (legacy?.decision.convert)
|
|
389
|
+
return null;
|
|
390
|
+
const failure = unresolvedLastFailure(status);
|
|
391
|
+
if (failure?.class === "auth")
|
|
392
|
+
return { provider, needed: true, reason: failure.cause, command };
|
|
393
|
+
if (status?.runtimeSignIn)
|
|
394
|
+
return { provider, needed: true, reason: signedOutReason(name, status.runtimeSignIn), command };
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
function signedOutReason(name, issue) {
|
|
398
|
+
const tool = runtimeDisplayName(issue.runtime);
|
|
399
|
+
return issue.state === "signed-out" ? `${tool} is not signed in for ${name} on this computer.` : `${tool} needs ${name}'s owner to sign in again on this computer.`;
|
|
400
|
+
}
|
|
361
401
|
function rowFor(name, opts = {}) {
|
|
362
402
|
const dir = botDir(name);
|
|
363
403
|
const config = readBotConfig(dir);
|
|
364
404
|
const fleet = isFleetRuntimeBot(config);
|
|
365
405
|
const status = readBotStatus(dir);
|
|
406
|
+
const legacy = fleet ? null : opts.legacy ?? legacyMoveState(dir, config, legacyConvertSeams.providerLogin(dir, config));
|
|
366
407
|
const pid = fleet ? fleetLocalGatewayPid(config.agentUid) : (status?.pid ?? null);
|
|
367
408
|
const processAlive = pid !== null && isPidAlive(pid) && (fleet || status?.state === "running");
|
|
368
409
|
// Turns this bot has taken and not finished: what "thinking" means.
|
|
@@ -409,6 +450,8 @@ function rowFor(name, opts = {}) {
|
|
|
409
450
|
...(fleet && config.fleetAuthPending === true ? { needsSignIn: true, authCommand: fleetAuthCommand(name, config) } : {}),
|
|
410
451
|
...(fleet && config.fleetServiceInstallPending === true ? { needsSignIn: false, serviceInstallPending: true } : {}),
|
|
411
452
|
signInCommand: fleet ? fleetAuthCommand(name, config) : signInCommand(config.runtime),
|
|
453
|
+
brainSignIn: brainSignInFor(name, config, status, legacy),
|
|
454
|
+
...(legacy ? { legacy: legacyStatusJson(legacy) } : {}),
|
|
412
455
|
runtimeCrash: processAlive ? runtimeCrashSinceStart(status) : null,
|
|
413
456
|
daemonInstalled,
|
|
414
457
|
daemonLoaded,
|
|
@@ -515,9 +558,12 @@ async function startBot(name, dir, config, output, extra = {}, opts = {}) {
|
|
|
515
558
|
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
516
559
|
if (isFleetRuntimeBot(config))
|
|
517
560
|
return startFleetRuntimeBot(name, dir, config, output, extra);
|
|
518
|
-
const
|
|
519
|
-
if (
|
|
520
|
-
return reportFleetBotStart(name, dir,
|
|
561
|
+
const move = opts.keepLegacy ? null : await convertLegacyBotToFleet(name, dir, config, "start");
|
|
562
|
+
if (move?.fleet)
|
|
563
|
+
return reportFleetBotStart(name, dir, move.fleet, output, { ...extra, movedToFleet: true }, true, "started by the HQ Fleet Runtime");
|
|
564
|
+
// The app reads these to say the bot is still legacy and, when a sign-in is all it waits for, to offer it.
|
|
565
|
+
if (move?.state)
|
|
566
|
+
extra = { ...extra, movedToFleet: false, legacy: legacyStatusJson(move.state), brainSignIn: brainSignInFor(name, config, readBotStatus(dir), move.state) };
|
|
521
567
|
if (config.enabled === false)
|
|
522
568
|
patchBotConfig(dir, { enabled: true });
|
|
523
569
|
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
@@ -594,21 +640,37 @@ async function startBot(name, dir, config, output, extra = {}, opts = {}) {
|
|
|
594
640
|
* itself starting (launchd, hq daemon or a detached run), before it polls:
|
|
595
641
|
* its own startup agent is booted out last, detached.
|
|
596
642
|
*/
|
|
643
|
+
/** Seams for the legacy move, replaced in tests. */
|
|
644
|
+
export const legacyConvertSeams = {
|
|
645
|
+
/** Whether the provider sign-in a Codex or Grok legacy bot needs is readable here, as HQ Fleet Local imports it. */
|
|
646
|
+
providerLogin: (dir, config) => legacyProviderLoginProbe(dir, config),
|
|
647
|
+
};
|
|
597
648
|
async function convertLegacyBotToFleet(name, dir, config, from) {
|
|
598
|
-
if (
|
|
599
|
-
return null;
|
|
600
|
-
// A launchd or daemon restart loop would otherwise retry a failing install before every start.
|
|
649
|
+
if (hasPromotionHold(dir))
|
|
650
|
+
return { fleet: null, state: null };
|
|
651
|
+
// A launchd or daemon restart loop would otherwise retry a failing install (and probe the sign-in) before every start.
|
|
601
652
|
if (from === "run" && fleetMoveRecentlyFailed(dir))
|
|
602
|
-
return null;
|
|
653
|
+
return { fleet: null, state: null };
|
|
654
|
+
const probe = legacyConvertSeams.providerLogin(dir, config);
|
|
655
|
+
const decision = legacyConversionDecision(config, process.env, probe);
|
|
656
|
+
if (!decision.convert) {
|
|
657
|
+
if (legacyHoldNotifiesOwner(decision.reason))
|
|
658
|
+
await holdLegacyBot(name, dir, config, decision);
|
|
659
|
+
return { fleet: null, state: { decision, notice: legacyHoldNotice(name, config.runtime, decision), lastFailure: readFleetMoveFailure(dir), hold: readFleetMoveHold(dir) } };
|
|
660
|
+
}
|
|
661
|
+
// The hold is over: whatever stops the move from here is a failure, not a hold.
|
|
662
|
+
fs.rmSync(fleetMoveHoldPath(dir), { force: true });
|
|
663
|
+
const ready = () => ({ fleet: null, state: { decision, notice: null, lastFailure: readFleetMoveFailure(dir), hold: null } });
|
|
603
664
|
if (from === "start") {
|
|
604
665
|
const running = readBotStatus(dir);
|
|
605
666
|
if (running && isPidAlive(running.pid) && selfStopProbe(running.pid))
|
|
606
|
-
return
|
|
667
|
+
return ready();
|
|
607
668
|
}
|
|
608
669
|
const log = createBotLogger(dir);
|
|
609
670
|
const keepLegacy = (step, err) => {
|
|
610
|
-
|
|
611
|
-
|
|
671
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
672
|
+
writeFleetMoveFailure(dir, new Date(), { step, error });
|
|
673
|
+
log("warn", `could not ${step} ${name} on the HQ Fleet Runtime (${error}); it runs on ${config.runtime} this time and tries again at its next start`);
|
|
612
674
|
};
|
|
613
675
|
let engine;
|
|
614
676
|
try {
|
|
@@ -618,9 +680,27 @@ async function convertLegacyBotToFleet(name, dir, config, from) {
|
|
|
618
680
|
}
|
|
619
681
|
catch (err) {
|
|
620
682
|
keepLegacy("install", err);
|
|
621
|
-
return
|
|
683
|
+
return ready();
|
|
622
684
|
}
|
|
623
|
-
|
|
685
|
+
if (decision.signIn) {
|
|
686
|
+
try {
|
|
687
|
+
importFleetLocalProviderLogin(config.agentUid, decision.signIn.runtime, {}, decision.signIn.env ? { env: decision.signIn.env } : {});
|
|
688
|
+
log("info", `imported ${name}'s ${config.runtime} sign-in from this computer for the HQ Fleet Runtime`);
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
keepLegacy("sign in", err);
|
|
692
|
+
return ready();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const current = readBotConfig(dir) ?? config;
|
|
696
|
+
const fleet = {
|
|
697
|
+
...current,
|
|
698
|
+
engine,
|
|
699
|
+
enabled: true,
|
|
700
|
+
fleetServiceInstallPending: false,
|
|
701
|
+
// The imported login is the bot's Fleet sign-in now; the legacy own-login setting no longer applies.
|
|
702
|
+
...(decision.signIn?.env ? { runtimeAuth: withRuntimeAuth(current.runtimeAuth, decision.signIn.runtime, null) } : {}),
|
|
703
|
+
};
|
|
624
704
|
if (from === "start")
|
|
625
705
|
await stopBot(name, dir, config, "silent");
|
|
626
706
|
let failure = null;
|
|
@@ -641,16 +721,77 @@ async function convertLegacyBotToFleet(name, dir, config, from) {
|
|
|
641
721
|
/* nothing was running */
|
|
642
722
|
}
|
|
643
723
|
keepLegacy("start", failure);
|
|
644
|
-
return
|
|
724
|
+
return ready();
|
|
645
725
|
}
|
|
646
726
|
writeBotConfig(dir, fleet);
|
|
647
|
-
|
|
727
|
+
clearFleetMoveRecords(dir);
|
|
648
728
|
// The legacy runner's copy of the machine credentials goes only now: a failed move above still needs it.
|
|
649
729
|
fs.rmSync(botCredsPath(dir), { force: true });
|
|
650
730
|
log("info", `moved ${name} to the HQ Fleet Runtime under the same identity; its ${config.runtime} runner is retired`);
|
|
651
731
|
syncFleetRuntimeJobs(name, dir, fleet);
|
|
652
732
|
retireLegacyBotAgent(name, dir, config, from === "run" ? "detached" : "inline");
|
|
653
|
-
return fleet;
|
|
733
|
+
return { fleet, state: null };
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* A legacy bot that cannot move yet: record why, and tell the owner once per
|
|
737
|
+
* reason through the bot's own DM, the way a Fleet bot reports a failed turn.
|
|
738
|
+
* The record is what `hq bot status` and `hq doctor` show.
|
|
739
|
+
*/
|
|
740
|
+
async function holdLegacyBot(name, dir, config, decision) {
|
|
741
|
+
const log = createBotLogger(dir);
|
|
742
|
+
const notice = legacyHoldNotice(name, config.runtime, decision);
|
|
743
|
+
const previous = readFleetMoveHold(dir);
|
|
744
|
+
const now = new Date().toISOString();
|
|
745
|
+
const hold = {
|
|
746
|
+
reason: decision.reason,
|
|
747
|
+
detail: decision.detail,
|
|
748
|
+
at: now,
|
|
749
|
+
...(previous?.notifiedAt ? { notifiedAt: previous.notifiedAt } : {}),
|
|
750
|
+
...(previous?.notifiedReason ? { notifiedReason: previous.notifiedReason } : {}),
|
|
751
|
+
...(previous?.reportId ? { reportId: previous.reportId } : {}),
|
|
752
|
+
...(previous?.reportedReason ? { reportedReason: previous.reportedReason } : {}),
|
|
753
|
+
};
|
|
754
|
+
writeFleetMoveHold(dir, hold);
|
|
755
|
+
log("warn", `${name} stays on its ${config.runtime} runner: ${notice.cause} ${notice.fix}`);
|
|
756
|
+
const reportDue = holdReportDue(previous, decision.reason);
|
|
757
|
+
const noticeDue = ownerHoldNoticeDue(previous, decision.reason);
|
|
758
|
+
if (!reportDue && !noticeDue)
|
|
759
|
+
return;
|
|
760
|
+
const api = new BotApi({ token: botTokenSupplier(dir) });
|
|
761
|
+
// One failure report per reason, the same route a Fleet bot files a failed turn through, so stuck legacy bots are visible in HQ.
|
|
762
|
+
if (reportDue) {
|
|
763
|
+
try {
|
|
764
|
+
const report = legacyHoldFailureReport({
|
|
765
|
+
reportId: randomUUID(),
|
|
766
|
+
createdAt: now,
|
|
767
|
+
name,
|
|
768
|
+
config,
|
|
769
|
+
decision,
|
|
770
|
+
notice,
|
|
771
|
+
machine: os.hostname().trim(),
|
|
772
|
+
hqCliVersion: CLI_VERSION,
|
|
773
|
+
os: `${process.platform} ${os.release()}`,
|
|
774
|
+
logPath: botLogPath(dir),
|
|
775
|
+
});
|
|
776
|
+
const { reportId } = await api.reportFailure(config.agentUid, report);
|
|
777
|
+
Object.assign(hold, { reportedReason: decision.reason, ...(reportId ? { reportId } : {}) });
|
|
778
|
+
writeFleetMoveHold(dir, hold);
|
|
779
|
+
log("info", `filed a failure report with HQ for ${name}'s held move (${decision.reason}${reportId ? `, report ${reportId}` : ""})`);
|
|
780
|
+
}
|
|
781
|
+
catch (err) {
|
|
782
|
+
log("warn", `could not file ${name}'s held move with HQ (${err instanceof Error ? err.message : String(err)}); the next start tries again`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (!noticeDue)
|
|
786
|
+
return;
|
|
787
|
+
try {
|
|
788
|
+
await api.sendDm({ toPersonUid: config.ownerUid, body: `${notice.cause} ${notice.fix}` });
|
|
789
|
+
writeFleetMoveHold(dir, { ...hold, notifiedAt: now, notifiedReason: decision.reason });
|
|
790
|
+
log("info", `told the owner once that ${name} has not moved to the HQ Fleet Runtime (${decision.reason})`);
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
log("warn", `could not tell the owner why ${name} has not moved to the HQ Fleet Runtime (${err instanceof Error ? err.message : String(err)}); the next start tries again`);
|
|
794
|
+
}
|
|
654
795
|
}
|
|
655
796
|
/** Remove a legacy bot's own startup agent once the bot runs on the HQ Fleet Runtime. True when there was one. */
|
|
656
797
|
function retireLegacyBotAgent(name, dir, config, bootout) {
|
|
@@ -735,9 +876,10 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
735
876
|
return null;
|
|
736
877
|
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: target.release }))
|
|
737
878
|
return null;
|
|
879
|
+
const progress = opts.progress ?? fleetInstallProgressReporter("silent");
|
|
738
880
|
let installation;
|
|
739
881
|
try {
|
|
740
|
-
installation = await installTargetFleetLocalDistribution({ target });
|
|
882
|
+
installation = await installTargetFleetLocalDistribution({ target, onProgress: progress });
|
|
741
883
|
}
|
|
742
884
|
catch (err) {
|
|
743
885
|
if (target.source !== "pointer")
|
|
@@ -749,8 +891,9 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
749
891
|
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}`);
|
|
750
892
|
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: HQ_FLEET_LOCAL_RELEASE }))
|
|
751
893
|
return null;
|
|
752
|
-
installation = await installPinnedFleetLocalDistribution();
|
|
894
|
+
installation = await installPinnedFleetLocalDistribution({ onProgress: progress });
|
|
753
895
|
}
|
|
896
|
+
progress({ phase: "python" });
|
|
754
897
|
const python = await ensureFleetPython();
|
|
755
898
|
const companyScope = effectiveBotKind(config) === "company"
|
|
756
899
|
? effectiveBotCompanies(config)
|
|
@@ -765,7 +908,7 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
765
908
|
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
766
909
|
ownerCompanies: companyScope,
|
|
767
910
|
...(config.autoApprove === true ? { autoApprove: true } : {}),
|
|
768
|
-
}, { python, start: false });
|
|
911
|
+
}, { python, start: false, onProgress: progress });
|
|
769
912
|
const credentials = botCredsPath(dir);
|
|
770
913
|
if (fs.existsSync(credentials))
|
|
771
914
|
importFleetLocalMachineCredential(config.agentUid, credentials, {}, { keepSource: opts.keepCredentials === true });
|
|
@@ -1228,7 +1371,9 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1228
1371
|
// of sending another create that the entity store will reject.
|
|
1229
1372
|
if (isSetupBot && (await adoptOwnedSetupBot(true)))
|
|
1230
1373
|
return;
|
|
1231
|
-
const
|
|
1374
|
+
const progress = fleetInstallProgressReporter(opts.json ? "silent" : "text");
|
|
1375
|
+
const fleetInstallation = await installTargetFleetLocalDistribution({ onProgress: progress });
|
|
1376
|
+
progress({ phase: "python" });
|
|
1232
1377
|
const fleetPython = await ensureFleetPython();
|
|
1233
1378
|
const ownerCompanies = kind === "personal" ? await fleetOwnerCompanies() : companies;
|
|
1234
1379
|
// A company bot's companies are resolved first (a typo must not cost an
|
|
@@ -1388,7 +1533,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1388
1533
|
firstStart: toolOffer
|
|
1389
1534
|
? { intro: setupToolOfferText(toolOffer, setupWelcomeOpening(intro)) }
|
|
1390
1535
|
: { intro: introText, ...(kickoff ? { kickoff } : {}) },
|
|
1391
|
-
}, { start: opts.start !== false && !fleetAuthPending, python: fleetPython });
|
|
1536
|
+
}, { start: opts.start !== false && !fleetAuthPending, python: fleetPython, onProgress: progress });
|
|
1392
1537
|
syncFleetRuntimeJobs(name, dir, config);
|
|
1393
1538
|
const fleetProfile = fleetBootstrap.profile;
|
|
1394
1539
|
const runtimeLabel = HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
@@ -1562,7 +1707,7 @@ async function runBotResident(nameArg) {
|
|
|
1562
1707
|
console.log(orphanBotRunMessage(name, outcome));
|
|
1563
1708
|
return;
|
|
1564
1709
|
}
|
|
1565
|
-
if (await convertLegacyBotToFleet(name, dir, config, "run"))
|
|
1710
|
+
if ((await convertLegacyBotToFleet(name, dir, config, "run")).fleet)
|
|
1566
1711
|
return;
|
|
1567
1712
|
if (healNodePathFromJob(name, dir, config))
|
|
1568
1713
|
return;
|
|
@@ -1680,18 +1825,28 @@ async function runBotList(opts) {
|
|
|
1680
1825
|
async function runBotStatus(nameArg, opts) {
|
|
1681
1826
|
const { name, dir, config } = requireBot(nameArg);
|
|
1682
1827
|
const fleet = isFleetRuntimeBot(config);
|
|
1683
|
-
const
|
|
1828
|
+
const legacy = fleet ? null : legacyMoveState(dir, config, legacyConvertSeams.providerLogin(dir, config));
|
|
1829
|
+
const row = rowFor(name, { legacy });
|
|
1684
1830
|
await decorateOnline([row]);
|
|
1685
1831
|
const status = readBotStatus(dir);
|
|
1686
1832
|
const creds = readBotCredsIdentity(dir);
|
|
1687
1833
|
const channels = fleet ? fleetLocalChannels(readFleetLocalGatewayState(config.agentUid), row.processAlive) : null;
|
|
1688
1834
|
if (opts.json) {
|
|
1689
|
-
console.log(JSON.stringify({
|
|
1835
|
+
console.log(JSON.stringify({
|
|
1836
|
+
...row,
|
|
1837
|
+
executive: executiveStatus(config, dir),
|
|
1838
|
+
...(fleet ? { engine: config.engine, channels } : {}),
|
|
1839
|
+
credsPresent: fleet || !!creds,
|
|
1840
|
+
login: describeBotAuth(dir, config),
|
|
1841
|
+
status,
|
|
1842
|
+
}, null, 2));
|
|
1690
1843
|
return;
|
|
1691
1844
|
}
|
|
1692
1845
|
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
1693
1846
|
if (fleet)
|
|
1694
1847
|
console.log(` engine: HQ Fleet Runtime ${config.engine?.version ?? ""} ${chalk.dim(`logs ~/.hq-fleet/profiles/${config.agentUid}/logs`)}`);
|
|
1848
|
+
else
|
|
1849
|
+
console.log(` engine: ${chalk.yellow(`legacy ${runtimeDisplayName(config.runtime)} runner (not on the HQ Fleet Runtime)`)}`);
|
|
1695
1850
|
console.log(` kind: ${describeKind(row.kind, row.companies)}`);
|
|
1696
1851
|
const login = describeBotAuth(dir, config);
|
|
1697
1852
|
console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
|
|
@@ -1718,6 +1873,10 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1718
1873
|
else {
|
|
1719
1874
|
console.log(` launchd: ${row.daemonInstalled ? (row.daemonLoaded ? "installed, loaded" : "installed, not loaded") : "not installed"}`);
|
|
1720
1875
|
}
|
|
1876
|
+
if (legacy) {
|
|
1877
|
+
for (const line of legacyStatusLines(name, legacy))
|
|
1878
|
+
console.log(` ${line}`);
|
|
1879
|
+
}
|
|
1721
1880
|
if (row.daemonNodeMissing) {
|
|
1722
1881
|
console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
|
|
1723
1882
|
}
|
|
@@ -1767,6 +1926,37 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1767
1926
|
}
|
|
1768
1927
|
console.log(chalk.dim(` state dir: ${dir}`));
|
|
1769
1928
|
}
|
|
1929
|
+
/** The `legacy` object in `hq bot status --json` for a bot still on its legacy runner. */
|
|
1930
|
+
export function legacyStatusJson(state) {
|
|
1931
|
+
const { decision } = state;
|
|
1932
|
+
return {
|
|
1933
|
+
moves: decision.convert,
|
|
1934
|
+
...(decision.convert ? {} : { reason: decision.reason, detail: decision.detail, cause: state.notice?.cause ?? null, fix: state.notice?.fix ?? null }),
|
|
1935
|
+
lastMoveFailure: state.lastFailure,
|
|
1936
|
+
ownerNotifiedAt: state.hold?.notifiedAt ?? null,
|
|
1937
|
+
reportId: state.hold?.reportId ?? null,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
/** The `fleet move:` lines of `hq bot status` for a bot still on its legacy runner. */
|
|
1941
|
+
export function legacyStatusLines(name, state) {
|
|
1942
|
+
const lines = [];
|
|
1943
|
+
const { decision } = state;
|
|
1944
|
+
if (decision.convert) {
|
|
1945
|
+
lines.push(`fleet move: ready; it moves to the HQ Fleet Runtime at its next start (hq bot start ${name})`);
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
lines.push(`fleet move: ${chalk.yellow(`held (${decision.reason})`)}: ${state.notice?.cause ?? decision.detail}`);
|
|
1949
|
+
if (state.notice?.fix)
|
|
1950
|
+
lines.push(` fix: ${state.notice.fix}`);
|
|
1951
|
+
if (legacyHoldNotifiesOwner(decision.reason))
|
|
1952
|
+
lines.push(chalk.dim(` owner DM: ${state.hold?.notifiedAt ? `sent ${state.hold.notifiedAt}` : "not sent yet"}`));
|
|
1953
|
+
}
|
|
1954
|
+
if (state.lastFailure) {
|
|
1955
|
+
const f = state.lastFailure;
|
|
1956
|
+
lines.push(`last move: ${chalk.yellow(`failed ${f.at}${f.step ? ` (could not ${f.step})` : ""}${f.error ? `: ${f.error}` : ""}`)}; it tries again at its next start`);
|
|
1957
|
+
}
|
|
1958
|
+
return lines;
|
|
1959
|
+
}
|
|
1770
1960
|
async function runBotLogs(nameArg, opts) {
|
|
1771
1961
|
const { dir } = requireBot(nameArg);
|
|
1772
1962
|
const file = botLogPath(dir);
|
|
@@ -2489,6 +2679,7 @@ async function runBotAuth(nameArg, opts) {
|
|
|
2489
2679
|
if (legacy && engine) {
|
|
2490
2680
|
const moved = { ...(readBotConfig(b.dir) ?? legacy), engine };
|
|
2491
2681
|
writeBotConfig(b.dir, moved);
|
|
2682
|
+
clearFleetMoveRecords(b.dir);
|
|
2492
2683
|
fs.rmSync(botCredsPath(b.dir), { force: true });
|
|
2493
2684
|
createBotLogger(b.dir)("info", `moved ${b.name} to the HQ Fleet Runtime with its ${opts.provider} sign-in; its ${legacy.runtime} runner is retired`);
|
|
2494
2685
|
syncFleetRuntimeJobs(b.name, b.dir, moved);
|
|
@@ -2496,8 +2687,11 @@ async function runBotAuth(nameArg, opts) {
|
|
|
2496
2687
|
}
|
|
2497
2688
|
const signedInRuntime = result.runtimeProvider === "anthropic" ? "claude" : result.runtimeProvider === "xai-oauth" ? "grok" : result.runtimeProvider === "openai-codex" ? "codex" : null;
|
|
2498
2689
|
const pending = b.config.fleetAuthPending === true || legacy !== null;
|
|
2690
|
+
// A moved bot answers with the login it just signed in with; its legacy own-login setting no longer applies.
|
|
2691
|
+
const runtimeAuth = legacy && signedInRuntime && legacy.runtimeAuth?.[signedInRuntime] !== undefined ? withRuntimeAuth(legacy.runtimeAuth, signedInRuntime, null) : undefined;
|
|
2499
2692
|
patchBotConfig(b.dir, {
|
|
2500
2693
|
...(signedInRuntime ? { runtime: signedInRuntime } : {}),
|
|
2694
|
+
...(legacy && signedInRuntime && legacy.runtimeAuth?.[signedInRuntime] !== undefined ? { runtimeAuth } : {}),
|
|
2501
2695
|
model: result.model,
|
|
2502
2696
|
...(pending ? { fleetAuthPending: false, fleetServiceInstallPending: !result.restarted, enabled: result.restarted } : {}),
|
|
2503
2697
|
});
|
|
@@ -2776,7 +2970,10 @@ async function adoptRemoteBotLocked(input) {
|
|
|
2776
2970
|
...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
|
|
2777
2971
|
});
|
|
2778
2972
|
// A bot made on the HQ Fleet Runtime comes back on it, on the release this CLI pins.
|
|
2779
|
-
const
|
|
2973
|
+
const progress = input.progress ?? fleetInstallProgressReporter("silent");
|
|
2974
|
+
const fleetInstallation = fleetEngine ? await installPinnedFleetLocalDistribution({ onProgress: progress }) : null;
|
|
2975
|
+
if (fleetInstallation)
|
|
2976
|
+
progress({ phase: "python" });
|
|
2780
2977
|
const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
|
|
2781
2978
|
if (fleetInstallation)
|
|
2782
2979
|
config.engine = fleetInstallation.engine;
|
|
@@ -2805,7 +3002,7 @@ async function adoptRemoteBotLocked(input) {
|
|
|
2805
3002
|
},
|
|
2806
3003
|
}
|
|
2807
3004
|
: {}),
|
|
2808
|
-
}, { python: fleetPython, start: false });
|
|
3005
|
+
}, { python: fleetPython, start: false, onProgress: progress });
|
|
2809
3006
|
}
|
|
2810
3007
|
createBotLogger(dir)("info", `adopted ${name} (${bot.agentUid}) on ${localMachineId()} kind=${config.kind}${local ? "" : " (no saved settings; defaults)"} runtime=${config.runtime}`);
|
|
2811
3008
|
let daemonMessage = null;
|
|
@@ -2870,6 +3067,7 @@ export async function runBotAdopt(nameArg, opts) {
|
|
|
2870
3067
|
name,
|
|
2871
3068
|
api,
|
|
2872
3069
|
hqRoot,
|
|
3070
|
+
progress: fleetInstallProgressReporter(opts.json ? "silent" : "text"),
|
|
2873
3071
|
overrides: {
|
|
2874
3072
|
...(isBotRuntimeId(opts.runtime) ? { runtime: opts.runtime } : {}),
|
|
2875
3073
|
...(model ? { model } : {}),
|
|
@@ -2983,7 +3181,7 @@ export async function runBotRestore(opts) {
|
|
|
2983
3181
|
if (here) {
|
|
2984
3182
|
return { name, agentUid: bot.agentUid, action: "repaired", detail: await repairLocalBot(name, api, bot, opts.daemon !== false), ...shown };
|
|
2985
3183
|
}
|
|
2986
|
-
const { result, config } = await adoptRemoteBot({ bot, name, api, hqRoot, daemon: opts.daemon !== false });
|
|
3184
|
+
const { result, config } = await adoptRemoteBot({ bot, name, api, hqRoot, daemon: opts.daemon !== false, progress: fleetInstallProgressReporter(opts.json ? "silent" : "text") });
|
|
2987
3185
|
// Silent: this command's own JSON has to be the only document on stdout.
|
|
2988
3186
|
const started = opts.start === false ? null : await startBot(name, result.dir, config, "silent").catch(() => null);
|
|
2989
3187
|
return {
|
|
@@ -3173,7 +3371,7 @@ export function registerBotCommand(program) {
|
|
|
3173
3371
|
const b = requireBot(name);
|
|
3174
3372
|
if (isFleetRuntimeBot(b.config)) {
|
|
3175
3373
|
refuseHeldFleetBot(b.dir);
|
|
3176
|
-
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
3374
|
+
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config, { progress: fleetInstallProgressReporter(opts.json ? "silent" : "text") });
|
|
3177
3375
|
runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "start", opts.json === true);
|
|
3178
3376
|
return;
|
|
3179
3377
|
}
|
|
@@ -3271,6 +3469,17 @@ export function registerBotCommand(program) {
|
|
|
3271
3469
|
.command("intro <name>")
|
|
3272
3470
|
.description("Have the bot re-send its introduction DM to you")
|
|
3273
3471
|
.action((name) => wrap(() => runBotIntro(name))());
|
|
3472
|
+
const runtime = bot.command("runtime").description("The HQ Fleet Runtime every local bot on this computer shares");
|
|
3473
|
+
runtime
|
|
3474
|
+
.command("install")
|
|
3475
|
+
.description("Download and build the runtime once for this computer, with progress, so creating a bot takes seconds. Safe to run again: a finished install returns at once, an interrupted download continues where it stopped")
|
|
3476
|
+
.option("--json", "Print one JSON object per line: progress events, then { ok, event: \"done\", release, runtimeDir }")
|
|
3477
|
+
.action((opts) => wrap(() => runBotRuntimeInstall(opts))());
|
|
3478
|
+
runtime
|
|
3479
|
+
.command("status")
|
|
3480
|
+
.description("Say whether the runtime is installed on this computer, step by step")
|
|
3481
|
+
.option("--json", "Print machine-readable JSON")
|
|
3482
|
+
.action((opts) => wrap(() => runBotRuntimeStatus(opts))());
|
|
3274
3483
|
const daemon = bot.command("daemon").description("Manage the bot's launchd user agent (macOS)");
|
|
3275
3484
|
daemon
|
|
3276
3485
|
.command("install <name>")
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -59,10 +59,49 @@ export declare class SyncSessionExpiredError extends Error {
|
|
|
59
59
|
readonly reason?: string | undefined;
|
|
60
60
|
constructor(kind: "reauth" | "unavailable", reason?: string | undefined);
|
|
61
61
|
}
|
|
62
|
+
/** Shown when the vault rejected the session even after a forced refresh. */
|
|
63
|
+
export declare const SYNC_SESSION_REJECTED_MESSAGE = "The HQ server rejected your session even after it was refreshed, so the saved sign-in is no longer accepted. `hq auth status` only checks the saved expiry, not the server. Run `hq login`, then rerun the sync.";
|
|
64
|
+
/** Reason text when token acquisition does not settle within the bound. */
|
|
65
|
+
export declare const SYNC_TOKEN_TIMEOUT_REASON = "timed out waiting for the sign-in service";
|
|
66
|
+
/** Longest a single token acquisition or refresh may take before the sync fails. */
|
|
67
|
+
export declare const SYNC_TOKEN_TIMEOUT_MS = 45000;
|
|
68
|
+
/**
|
|
69
|
+
* The vault rejected the session after a forced refresh had already replaced
|
|
70
|
+
* the saved token. Retrying again cannot help; the user has to sign in again.
|
|
71
|
+
*/
|
|
72
|
+
export declare class SyncSessionRejectedError extends Error {
|
|
73
|
+
constructor();
|
|
74
|
+
}
|
|
62
75
|
export interface SyncVaultConfigDeps {
|
|
63
76
|
/** Non-interactive token resolver: returns the cached token or refreshes it. */
|
|
64
77
|
ensureToken?: () => Promise<string>;
|
|
78
|
+
/** Bound on one token acquisition (default {@link SYNC_TOKEN_TIMEOUT_MS}). */
|
|
79
|
+
tokenTimeoutMs?: number;
|
|
65
80
|
}
|
|
81
|
+
export interface RejectedSessionRecoveryDeps {
|
|
82
|
+
/** Refresh the saved session even though it looks valid. */
|
|
83
|
+
forceRefresh?: () => Promise<{
|
|
84
|
+
refreshed: boolean;
|
|
85
|
+
reason?: string;
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* The token-getter failure recorded for the latest attempt, if any. hq-cloud
|
|
89
|
+
* turns a getter error (network, refresh failure, identity swap) into a plain
|
|
90
|
+
* 401, so a 401 with a recorded failure is not a server rejection of a token.
|
|
91
|
+
*/
|
|
92
|
+
recordedFailure?: () => Error | null;
|
|
93
|
+
/** Runs once, after a recoverable 401 and before the refresh and rerun. */
|
|
94
|
+
onRetry?: () => void | Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* feedback_3d752910: the vault can reject a token that still looks valid in the
|
|
98
|
+
* local cache (revoked or replaced server-side). The cached expiry check never
|
|
99
|
+
* notices, and `hq auth refresh` is a no-op for a valid-looking token, so every
|
|
100
|
+
* sync kept presenting the same rejected token. On a vault 401, force one real
|
|
101
|
+
* refresh and rerun the sync once. A second rejection is reported as a rejected
|
|
102
|
+
* session (sign in again), never retried again.
|
|
103
|
+
*/
|
|
104
|
+
export declare function runWithRejectedSessionRecovery<T>(run: () => Promise<T>, deps?: RejectedSessionRecoveryDeps): Promise<T>;
|
|
66
105
|
/** The last token failure recorded by a {@link buildSyncVaultConfig} config. */
|
|
67
106
|
export declare function syncAuthFailureFor(config: VaultServiceConfig | undefined): Error | null;
|
|
68
107
|
/**
|