@indigoai-us/hq-cli 5.345.200 → 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 +14 -0
- package/dist/commands/bot.d.ts +47 -0
- package/dist/commands/bot.js +200 -20
- package/dist/commands/cloud.d.ts +39 -0
- package/dist/commands/cloud.js +173 -82
- package/dist/commands/deploy-domains.js +8 -4
- package/dist/lib/bot/api.d.ts +8 -0
- package/dist/lib/bot/api.js +9 -0
- package/dist/lib/bot/fleet-local.d.ts +28 -0
- package/dist/lib/bot/fleet-local.js +75 -6
- 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/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/utils/resident-package-preload.js +57 -4
- package/package.json +7 -7
package/dist/commands/cloud.js
CHANGED
|
@@ -22,7 +22,7 @@ import * as path from "path";
|
|
|
22
22
|
import { share, getStateDir, listJournals, createIgnoreFilter, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, readSyncProgress, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
23
23
|
import { syncLeg } from "../utils/sync-operation-lock.js";
|
|
24
24
|
import { SYNC_WATCHDOG_EXIT_CODE, SYNC_WATCHDOG_REPORT_GRACE_MS, SyncWatchdogTimeoutError, createSnapshotProgressProbe, resolveSyncWatchdogLimits, withSyncWatchdog, } from "../utils/sync-watchdog.js";
|
|
25
|
-
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, IdentitySwapRefusal, cognitoCachePrincipalUid, isDefinitivePersonAuthRejection, isIdentitySwapRefusal, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
25
|
+
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, IdentitySwapRefusal, cognitoCachePrincipalUid, isDefinitivePersonAuthRejection, isIdentitySwapRefusal, loadCachedTokens, refreshCachedSession, } from "../utils/cognito-session.js";
|
|
26
26
|
import { resolveUploadAuthor } from "../utils/upload-author.js";
|
|
27
27
|
import { companyFolderExceedsThreshold, emitNarrowHint, isStrictRefusal, resolveBannerLevel, resolveNarrowHintPresentationLevel, resolveNarrowHintMinBytes, } from "../lib/narrow-hint-banner.js";
|
|
28
28
|
import { beginSyncHealthReport, classifySyncFailure, } from "../utils/client-health.js";
|
|
@@ -90,6 +90,80 @@ export class SyncSessionExpiredError extends Error {
|
|
|
90
90
|
this.name = "SyncSessionExpiredError";
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
/** Shown when the vault rejected the session even after a forced refresh. */
|
|
94
|
+
export 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.";
|
|
95
|
+
/** Reason text when token acquisition does not settle within the bound. */
|
|
96
|
+
export const SYNC_TOKEN_TIMEOUT_REASON = "timed out waiting for the sign-in service";
|
|
97
|
+
/** Longest a single token acquisition or refresh may take before the sync fails. */
|
|
98
|
+
export const SYNC_TOKEN_TIMEOUT_MS = 45_000;
|
|
99
|
+
/**
|
|
100
|
+
* The vault rejected the session after a forced refresh had already replaced
|
|
101
|
+
* the saved token. Retrying again cannot help; the user has to sign in again.
|
|
102
|
+
*/
|
|
103
|
+
export class SyncSessionRejectedError extends Error {
|
|
104
|
+
constructor() {
|
|
105
|
+
super(SYNC_SESSION_REJECTED_MESSAGE);
|
|
106
|
+
this.name = "SyncSessionRejectedError";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Reject with a named "unavailable" error when `promise` has not settled within `ms`. */
|
|
110
|
+
function withTokenTimeout(promise, ms) {
|
|
111
|
+
let timer;
|
|
112
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
113
|
+
timer = setTimeout(() => reject(new SyncSessionExpiredError("unavailable", SYNC_TOKEN_TIMEOUT_REASON)), ms);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
});
|
|
116
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
117
|
+
if (timer !== undefined)
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* feedback_3d752910: the vault can reject a token that still looks valid in the
|
|
123
|
+
* local cache (revoked or replaced server-side). The cached expiry check never
|
|
124
|
+
* notices, and `hq auth refresh` is a no-op for a valid-looking token, so every
|
|
125
|
+
* sync kept presenting the same rejected token. On a vault 401, force one real
|
|
126
|
+
* refresh and rerun the sync once. A second rejection is reported as a rejected
|
|
127
|
+
* session (sign in again), never retried again.
|
|
128
|
+
*/
|
|
129
|
+
export async function runWithRejectedSessionRecovery(run, deps = {}) {
|
|
130
|
+
const forceRefresh = deps.forceRefresh ?? (() => refreshCachedSession({ force: true }));
|
|
131
|
+
try {
|
|
132
|
+
return await run();
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (!isVaultAuthRejection(err))
|
|
136
|
+
throw err;
|
|
137
|
+
// The getter already failed to produce a token; a forced refresh would hit
|
|
138
|
+
// the same failure. Report the real cause.
|
|
139
|
+
const firstFailure = deps.recordedFailure?.();
|
|
140
|
+
if (firstFailure)
|
|
141
|
+
throw firstFailure;
|
|
142
|
+
await deps.onRetry?.();
|
|
143
|
+
let refreshed;
|
|
144
|
+
try {
|
|
145
|
+
refreshed = await withTokenTimeout(forceRefresh(), SYNC_TOKEN_TIMEOUT_MS);
|
|
146
|
+
}
|
|
147
|
+
catch (refreshErr) {
|
|
148
|
+
throw refreshErr instanceof SyncSessionExpiredError
|
|
149
|
+
? refreshErr
|
|
150
|
+
: classifyRefreshFailure(refreshErr);
|
|
151
|
+
}
|
|
152
|
+
if (!refreshed.refreshed) {
|
|
153
|
+
throw classifyRefreshFailure(new Error(refreshed.reason ?? "refresh failed"));
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
return await run();
|
|
157
|
+
}
|
|
158
|
+
catch (retryErr) {
|
|
159
|
+
if (isVaultAuthRejection(retryErr)) {
|
|
160
|
+
// A getter failure surfaced as a 401 is not a server rejection.
|
|
161
|
+
throw deps.recordedFailure?.() ?? new SyncSessionRejectedError();
|
|
162
|
+
}
|
|
163
|
+
throw retryErr;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
93
167
|
/**
|
|
94
168
|
* Per-config record of the last token-function failure. hq-cloud's one-shot
|
|
95
169
|
* 401 retry swallows getter errors and rethrows the original 401, so
|
|
@@ -128,6 +202,7 @@ function classifyRefreshFailure(err) {
|
|
|
128
202
|
*/
|
|
129
203
|
export function buildSyncVaultConfig(initialToken, deps = {}) {
|
|
130
204
|
const ensureToken = deps.ensureToken ?? (() => ensureCognitoToken({ interactive: false }));
|
|
205
|
+
const tokenTimeoutMs = deps.tokenTimeoutMs ?? SYNC_TOKEN_TIMEOUT_MS;
|
|
131
206
|
const runPrincipal = cognitoCachePrincipalUid(initialToken);
|
|
132
207
|
// A start token with no sub/entityUid cannot be bound to a principal, and a
|
|
133
208
|
// null binding would accept any later identity-less token. Refuse up front.
|
|
@@ -142,7 +217,7 @@ export function buildSyncVaultConfig(initialToken, deps = {}) {
|
|
|
142
217
|
throw refused;
|
|
143
218
|
let token;
|
|
144
219
|
try {
|
|
145
|
-
token = await ensureToken();
|
|
220
|
+
token = await withTokenTimeout(ensureToken(), tokenTimeoutMs);
|
|
146
221
|
}
|
|
147
222
|
catch (err) {
|
|
148
223
|
const failure = isIdentitySwapRefusal(err) ? err : classifyRefreshFailure(err);
|
|
@@ -177,7 +252,9 @@ function isVaultAuthRejection(err) {
|
|
|
177
252
|
* CLI are released independently.
|
|
178
253
|
*/
|
|
179
254
|
export function formatSyncFailure(err, ctx = {}) {
|
|
180
|
-
if (err instanceof SyncSessionExpiredError ||
|
|
255
|
+
if (err instanceof SyncSessionExpiredError ||
|
|
256
|
+
err instanceof SyncSessionRejectedError ||
|
|
257
|
+
isIdentitySwapRefusal(err)) {
|
|
181
258
|
return err.message;
|
|
182
259
|
}
|
|
183
260
|
if (isVaultAuthRejection(err)) {
|
|
@@ -1622,87 +1699,101 @@ export function registerCloudCommands(program) {
|
|
|
1622
1699
|
console.log(chalk.bold("\nHQ Sync — Pull"));
|
|
1623
1700
|
console.log(` HQ root: ${options.hqRoot}`);
|
|
1624
1701
|
console.log(` Company: ${options.company ?? "(from .hq/config.json)"}\n`);
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
nudgeExceedsSize &&
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1702
|
+
// feedback_3d752910: one forced refresh and rerun when the vault
|
|
1703
|
+
// rejects a token that still looks valid in the local cache.
|
|
1704
|
+
await runWithRejectedSessionRecovery(async () => {
|
|
1705
|
+
runVaultConfig = undefined;
|
|
1706
|
+
const accessToken = await withTokenTimeout(ensureSyncCognitoToken(), SYNC_TOKEN_TIMEOUT_MS);
|
|
1707
|
+
const vaultConfig = buildSyncVaultConfig(accessToken);
|
|
1708
|
+
runVaultConfig = vaultConfig;
|
|
1709
|
+
// US-011 (2026-05-21 fix): resolve the caller's sync-config for
|
|
1710
|
+
// the targeted membership BEFORE the pull, so we can (a) emit the
|
|
1711
|
+
// narrow-hint banner after success if still on all-mode and
|
|
1712
|
+
// (b) respect strict-mode refusal mirror of the --all + sync-now
|
|
1713
|
+
// paths. Failure to resolve degrades silently — pull still works,
|
|
1714
|
+
// banner just stays quiet (same as the catch in runPullAll).
|
|
1715
|
+
const narrowHintLevel = resolveBannerLevel();
|
|
1716
|
+
const pullClient = new VaultClient(vaultConfig);
|
|
1717
|
+
const { resolvedCompanyUid, resolvedMode } = await resolvePerCompanyPullPlan(pullClient, options.company);
|
|
1718
|
+
// DEV-1768: resolve the REAL effective scope (mode + prefixSet) and
|
|
1719
|
+
// thread it into the pull below — not just the banner. Best-effort;
|
|
1720
|
+
// degrades to "all" inside the resolver on any failure.
|
|
1721
|
+
const pullScope = await resolveCliPullScope(pullClient, options.company, options.hqRoot);
|
|
1722
|
+
// Size gate: only a large local folder is nudged / strict-refused.
|
|
1723
|
+
const nudgeExceedsSize = resolvedMode === "all" &&
|
|
1724
|
+
resolvedCompanyUid !== undefined &&
|
|
1725
|
+
narrowNudgeExceedsSize(options.hqRoot, options.company);
|
|
1726
|
+
const strictRefusal = resolvedMode === "all" &&
|
|
1727
|
+
nudgeExceedsSize &&
|
|
1728
|
+
isStrictRefusal(resolvedMode, narrowHintLevel) &&
|
|
1729
|
+
options.modeAll !== true;
|
|
1730
|
+
const bannerLevel = resolveNarrowHintPresentationLevel(narrowHintLevel, strictRefusal);
|
|
1731
|
+
// Strict-mode refusal: matches runPullAll + runNowSingle behavior.
|
|
1732
|
+
// Default banner level is 'hint' which never triggers refusal —
|
|
1733
|
+
// wired now so a future release can flip the default to 'strict'
|
|
1734
|
+
// (still size-gated) without re-touching this command.
|
|
1735
|
+
if (strictRefusal &&
|
|
1736
|
+
resolvedMode === "all" &&
|
|
1737
|
+
resolvedCompanyUid) {
|
|
1738
|
+
emitNarrowHint({
|
|
1739
|
+
companyUid: resolvedCompanyUid,
|
|
1740
|
+
syncMode: resolvedMode,
|
|
1741
|
+
level: bannerLevel,
|
|
1742
|
+
});
|
|
1743
|
+
console.error(chalk.red("\n✗ Pull refused: strict narrow-hint mode is on and this " +
|
|
1744
|
+
"company's local folder has grown large. Run `hq sync narrow --apply` " +
|
|
1745
|
+
"to migrate, or re-run with --mode-all."));
|
|
1746
|
+
process.exit(1);
|
|
1747
|
+
}
|
|
1748
|
+
const excludePrefixes = readScopeExcludePrefixes(pullScope);
|
|
1749
|
+
syncHealth = beginSyncHealthReport();
|
|
1750
|
+
const result = await syncLeg({
|
|
1751
|
+
company: options.company,
|
|
1752
|
+
onConflict: options.onConflict,
|
|
1753
|
+
vaultConfig,
|
|
1754
|
+
hqRoot: options.hqRoot,
|
|
1755
|
+
...(pullScope?.syncMode !== undefined
|
|
1756
|
+
? { syncMode: pullScope.syncMode }
|
|
1757
|
+
: {}),
|
|
1758
|
+
...(pullScope?.prefixSet !== undefined
|
|
1759
|
+
? { prefixSet: pullScope.prefixSet }
|
|
1760
|
+
: {}),
|
|
1761
|
+
// Sessions-exclusion: never materialize excluded prefixes even for
|
|
1762
|
+
// a wide-STS owner.
|
|
1763
|
+
...(excludePrefixes ? { excludePrefixes } : {}),
|
|
1764
|
+
...(options.forceScopeShrink ? { forceScopeShrink: true } : {}),
|
|
1661
1765
|
});
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
company: options.company,
|
|
1671
|
-
onConflict: options.onConflict,
|
|
1672
|
-
vaultConfig,
|
|
1673
|
-
hqRoot: options.hqRoot,
|
|
1674
|
-
...(pullScope?.syncMode !== undefined
|
|
1675
|
-
? { syncMode: pullScope.syncMode }
|
|
1676
|
-
: {}),
|
|
1677
|
-
...(pullScope?.prefixSet !== undefined
|
|
1678
|
-
? { prefixSet: pullScope.prefixSet }
|
|
1679
|
-
: {}),
|
|
1680
|
-
// Sessions-exclusion: never materialize excluded prefixes even for
|
|
1681
|
-
// a wide-STS owner.
|
|
1682
|
-
...(excludePrefixes ? { excludePrefixes } : {}),
|
|
1683
|
-
...(options.forceScopeShrink ? { forceScopeShrink: true } : {}),
|
|
1684
|
-
});
|
|
1685
|
-
if (result.aborted) {
|
|
1686
|
-
console.log(chalk.yellow(`\n⚠ Pull aborted (${result.filesDownloaded} downloaded, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`));
|
|
1687
|
-
await syncHealth.failed();
|
|
1688
|
-
process.exit(1);
|
|
1689
|
-
}
|
|
1690
|
-
recordFirstWorkBearingSync({
|
|
1691
|
-
filesDownloaded: result.filesDownloaded,
|
|
1692
|
-
aborted: result.aborted,
|
|
1693
|
-
});
|
|
1694
|
-
console.log(chalk.green(`\n✓ Pulled ${result.filesDownloaded} file(s) (${formatBytes(result.bytesDownloaded)}, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`));
|
|
1695
|
-
// Emit the hint banner after success so it appears alongside the
|
|
1696
|
-
// summary line. Mirrors the wiring in runPullAll and runNowSingle.
|
|
1697
|
-
// Size-gated: only a large local folder is nudged.
|
|
1698
|
-
if (resolvedMode === "all" && nudgeExceedsSize && resolvedCompanyUid) {
|
|
1699
|
-
emitNarrowHint({
|
|
1700
|
-
companyUid: resolvedCompanyUid,
|
|
1701
|
-
syncMode: resolvedMode,
|
|
1702
|
-
level: bannerLevel,
|
|
1766
|
+
if (result.aborted) {
|
|
1767
|
+
console.log(chalk.yellow(`\n⚠ Pull aborted (${result.filesDownloaded} downloaded, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`));
|
|
1768
|
+
await syncHealth.failed();
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
recordFirstWorkBearingSync({
|
|
1772
|
+
filesDownloaded: result.filesDownloaded,
|
|
1773
|
+
aborted: result.aborted,
|
|
1703
1774
|
});
|
|
1704
|
-
|
|
1705
|
-
|
|
1775
|
+
console.log(chalk.green(`\n✓ Pulled ${result.filesDownloaded} file(s) (${formatBytes(result.bytesDownloaded)}, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`));
|
|
1776
|
+
// Emit the hint banner after success so it appears alongside the
|
|
1777
|
+
// summary line. Mirrors the wiring in runPullAll and runNowSingle.
|
|
1778
|
+
// Size-gated: only a large local folder is nudged.
|
|
1779
|
+
if (resolvedMode === "all" && nudgeExceedsSize && resolvedCompanyUid) {
|
|
1780
|
+
emitNarrowHint({
|
|
1781
|
+
companyUid: resolvedCompanyUid,
|
|
1782
|
+
syncMode: resolvedMode,
|
|
1783
|
+
level: bannerLevel,
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
await syncHealth.succeeded();
|
|
1787
|
+
}, {
|
|
1788
|
+
recordedFailure: () => syncAuthFailureFor(runVaultConfig),
|
|
1789
|
+
onRetry: async () => {
|
|
1790
|
+
console.error(chalk.yellow("HQ rejected the session; refreshing once and retrying."));
|
|
1791
|
+
// Close the first attempt's health report; the rerun opens its own.
|
|
1792
|
+
const first = syncHealth;
|
|
1793
|
+
syncHealth = undefined;
|
|
1794
|
+
await first?.failed("AUTH_EXPIRED");
|
|
1795
|
+
},
|
|
1796
|
+
});
|
|
1706
1797
|
}
|
|
1707
1798
|
catch (err) {
|
|
1708
1799
|
if (syncHealth)
|
|
@@ -907,12 +907,16 @@ function availabilityLabel(kind) {
|
|
|
907
907
|
function isOfferable(kind) {
|
|
908
908
|
return kind === "available" || kind === "premium";
|
|
909
909
|
}
|
|
910
|
+
/** hq-pro sends the registrar currency as lowercase "usd". Either case is US dollars. */
|
|
911
|
+
function isUsdCurrency(currency) {
|
|
912
|
+
return typeof currency === "string" && currency.toUpperCase() === "USD";
|
|
913
|
+
}
|
|
910
914
|
function moneyFromCents(cents, currency) {
|
|
911
915
|
if (typeof cents !== "number" || !Number.isFinite(cents) || cents < 0 || !Number.isInteger(cents))
|
|
912
916
|
return undefined;
|
|
913
917
|
const amount = cents / 100;
|
|
914
918
|
const text = Number.isInteger(amount) ? `$${amount}` : `$${amount.toFixed(2)}`;
|
|
915
|
-
if (typeof currency === "string" && currency !== "" && currency
|
|
919
|
+
if (typeof currency === "string" && currency !== "" && !isUsdCurrency(currency))
|
|
916
920
|
return `${text} ${currency}`;
|
|
917
921
|
return text;
|
|
918
922
|
}
|
|
@@ -1053,7 +1057,7 @@ function registrarCurrency(result) {
|
|
|
1053
1057
|
/** First-year amount comparable to --price. A quote in another currency is not one. */
|
|
1054
1058
|
function usdFirstYearAmount(result) {
|
|
1055
1059
|
const currency = registrarCurrency(result);
|
|
1056
|
-
if (currency !== undefined && currency
|
|
1060
|
+
if (currency !== undefined && !isUsdCurrency(currency))
|
|
1057
1061
|
return undefined;
|
|
1058
1062
|
const registrar = result.registrarPrice;
|
|
1059
1063
|
const block = registrar && typeof registrar === "object" ? registrar : undefined;
|
|
@@ -1067,7 +1071,7 @@ function usdFirstYearAmount(result) {
|
|
|
1067
1071
|
}
|
|
1068
1072
|
function assertMatchingUsdPrice(match, quote, expectedPriceUsd) {
|
|
1069
1073
|
const currency = registrarCurrency(match);
|
|
1070
|
-
if (currency !== undefined && currency
|
|
1074
|
+
if (currency !== undefined && !isUsdCurrency(currency)) {
|
|
1071
1075
|
const shown = quote.firstYear
|
|
1072
1076
|
? `The registrar's first-year price is ${quote.firstYear}, not US dollars.`
|
|
1073
1077
|
: "The registrar's first-year price is not in US dollars.";
|
|
@@ -1362,7 +1366,7 @@ async function renewAction(domainArg, autoRenew, options) {
|
|
|
1362
1366
|
const cents = detail.renewalPriceCents;
|
|
1363
1367
|
const currency = detail.currency;
|
|
1364
1368
|
const amount = moneyFromCents(cents, currency);
|
|
1365
|
-
if (typeof cents !== "number" || !Number.isSafeInteger(cents) || cents < 0 || currency
|
|
1369
|
+
if (typeof cents !== "number" || !Number.isSafeInteger(cents) || cents < 0 || !isUsdCurrency(currency) || !amount || !terms) {
|
|
1366
1370
|
throw new DeployDomainsError("Could not load complete USD renewal terms. Nothing was changed. Try again later.", "RENEWAL_TERMS_UNAVAILABLE");
|
|
1367
1371
|
}
|
|
1368
1372
|
const nextRenewal = detail.nextRenewalAt ?? detail.expiresAt;
|
package/dist/lib/bot/api.d.ts
CHANGED
|
@@ -243,6 +243,14 @@ export declare class BotApi {
|
|
|
243
243
|
createdAt?: string;
|
|
244
244
|
state?: string;
|
|
245
245
|
}>;
|
|
246
|
+
/**
|
|
247
|
+
* POST /v1/agents/{agentUid}/failure-reports, as the bot. The same route
|
|
248
|
+
* the HQ Fleet Runtime's HQ DM adapter files a failed turn through; the
|
|
249
|
+
* payload mirrors its `build_upload`. Returns the id HQ stored it under.
|
|
250
|
+
*/
|
|
251
|
+
reportFailure(agentUid: string, report: Record<string, unknown>): Promise<{
|
|
252
|
+
reportId: string | null;
|
|
253
|
+
}>;
|
|
246
254
|
/** POST|DELETE /v1/notify/reactions on a DM message (best-effort thinking status). */
|
|
247
255
|
setReaction(input: {
|
|
248
256
|
peerUid: string;
|
package/dist/lib/bot/api.js
CHANGED
|
@@ -163,6 +163,15 @@ export class BotApi {
|
|
|
163
163
|
const { body } = await this.call("/v1/notify/dm", { method: "POST", body: payload });
|
|
164
164
|
return body;
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* POST /v1/agents/{agentUid}/failure-reports, as the bot. The same route
|
|
168
|
+
* the HQ Fleet Runtime's HQ DM adapter files a failed turn through; the
|
|
169
|
+
* payload mirrors its `build_upload`. Returns the id HQ stored it under.
|
|
170
|
+
*/
|
|
171
|
+
async reportFailure(agentUid, report) {
|
|
172
|
+
const { body } = await this.call(`/v1/agents/${encodeURIComponent(agentUid)}/failure-reports`, { method: "POST", body: report });
|
|
173
|
+
return { reportId: typeof body?.reportId === "string" && body.reportId.trim() ? body.reportId.trim() : null };
|
|
174
|
+
}
|
|
166
175
|
/** POST|DELETE /v1/notify/reactions on a DM message (best-effort thinking status). */
|
|
167
176
|
async setReaction(input, present) {
|
|
168
177
|
try {
|
|
@@ -369,6 +369,34 @@ export declare function windowsExtractionPathProblem(destination: string, longes
|
|
|
369
369
|
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps, opts?: {
|
|
370
370
|
keepSource?: boolean;
|
|
371
371
|
}): void;
|
|
372
|
+
/** The bots with an HQ Fleet profile on this computer, by the id their profile.json names. */
|
|
373
|
+
export declare function listFleetLocalProfiles(deps?: Pick<FleetLocalControlDeps, "fleetHome">): Array<{
|
|
374
|
+
agentUid: string;
|
|
375
|
+
profileDir: string;
|
|
376
|
+
}>;
|
|
377
|
+
export interface FleetLocalProviderAuthStatus {
|
|
378
|
+
authenticated: boolean;
|
|
379
|
+
/** `oauth` or `api_key`, when authenticated. */
|
|
380
|
+
authType?: string;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The helper's word on a provider credential a bot holds here (never the
|
|
384
|
+
* credential itself). Null when the helper cannot be asked: the profile is
|
|
385
|
+
* not installed, or the helper failed.
|
|
386
|
+
*/
|
|
387
|
+
export declare function fleetLocalProviderAuthStatus(agentUid: string, provider: string, deps?: FleetLocalControlDeps): FleetLocalProviderAuthStatus | null;
|
|
388
|
+
/**
|
|
389
|
+
* Sign a bot in from a login already on this computer, without a browser:
|
|
390
|
+
* the helper imports the Codex CLI login (CODEX_HOME, else ~/.codex) or
|
|
391
|
+
* copies another Fleet bot's Grok login. `env` is added to the helper's
|
|
392
|
+
* environment (a bot's own Codex home). The profile's provider is already
|
|
393
|
+
* the runtime's preset from the bootstrap; this re-renders and checks it.
|
|
394
|
+
*/
|
|
395
|
+
export declare function importFleetLocalProviderLogin(agentUid: string, runtime: "codex" | "grok", deps?: FleetLocalControlDeps, opts?: {
|
|
396
|
+
env?: Record<string, string>;
|
|
397
|
+
}): {
|
|
398
|
+
runtimeProvider: string;
|
|
399
|
+
};
|
|
372
400
|
export interface FleetRuntimeInstallResult {
|
|
373
401
|
release: FleetLocalReleasePin;
|
|
374
402
|
distributionDir: string;
|
|
@@ -80,14 +80,14 @@ export function sharedRuntimeLayout(root, id, platform) {
|
|
|
80
80
|
}
|
|
81
81
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
82
82
|
repository: "indigoai-us/hq-fleet-local",
|
|
83
|
-
tag: "v0.4.
|
|
84
|
-
version: "0.4.
|
|
85
|
-
artifact: "hq-fleet-local-0.4.
|
|
86
|
-
artifactSha256: "
|
|
83
|
+
tag: "v0.4.26",
|
|
84
|
+
version: "0.4.26",
|
|
85
|
+
artifact: "hq-fleet-local-0.4.26.zip",
|
|
86
|
+
artifactSha256: "a738704bf3ea7109ecc133c091b97a2d602d54d83df74a44a25fed508ed8ce27",
|
|
87
87
|
runtimeArtifact: "hq-fleet-runtime-d257fdd1171f.tar.gz",
|
|
88
88
|
runtimeArtifactSha256: "4d26d250eb100efff280bcd254e06e3a2993875c1fff4eec1733eaf99e52cd70",
|
|
89
|
-
manifestSha256: "
|
|
90
|
-
signatureSha256: "
|
|
89
|
+
manifestSha256: "07d4ee6e34dc63c5f42df009fa9c70d3ef6a93527143956373a9d3cef6f5c3cc",
|
|
90
|
+
signatureSha256: "34793d91b60acdcc7e508d4ddb98311ef0d34b8a60c57930cef08e649288297a",
|
|
91
91
|
runtimeCommit: "d257fdd1171f1edd5e381b3bad7baacab0cb00d3",
|
|
92
92
|
};
|
|
93
93
|
/** hq-fleet-local trust/release-public.pem (rotated 2026-10-30; signed v0.4.17). */
|
|
@@ -1695,6 +1695,75 @@ export function importFleetLocalMachineCredential(agentUid, machineCredentials,
|
|
|
1695
1695
|
if (!opts.keepSource)
|
|
1696
1696
|
fs.rmSync(machineCredentials, { force: true });
|
|
1697
1697
|
}
|
|
1698
|
+
/** The bots with an HQ Fleet profile on this computer, by the id their profile.json names. */
|
|
1699
|
+
export function listFleetLocalProfiles(deps = {}) {
|
|
1700
|
+
const profiles = path.join(deps.fleetHome ?? fleetHome(), "profiles");
|
|
1701
|
+
let entries;
|
|
1702
|
+
try {
|
|
1703
|
+
entries = fs.readdirSync(profiles, { withFileTypes: true });
|
|
1704
|
+
}
|
|
1705
|
+
catch {
|
|
1706
|
+
return [];
|
|
1707
|
+
}
|
|
1708
|
+
const found = [];
|
|
1709
|
+
for (const entry of entries) {
|
|
1710
|
+
if (!entry.isDirectory())
|
|
1711
|
+
continue;
|
|
1712
|
+
const profileDir = path.join(profiles, entry.name);
|
|
1713
|
+
try {
|
|
1714
|
+
const metadata = JSON.parse(fs.readFileSync(path.join(profileDir, "profile.json"), "utf8"));
|
|
1715
|
+
if (typeof metadata.botId === "string" && /^agt_[A-Za-z0-9_-]+$/.test(metadata.botId))
|
|
1716
|
+
found.push({ agentUid: metadata.botId, profileDir });
|
|
1717
|
+
}
|
|
1718
|
+
catch {
|
|
1719
|
+
/* not a bot profile */
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return found.sort((a, b) => a.profileDir.localeCompare(b.profileDir));
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* The helper's word on a provider credential a bot holds here (never the
|
|
1726
|
+
* credential itself). Null when the helper cannot be asked: the profile is
|
|
1727
|
+
* not installed, or the helper failed.
|
|
1728
|
+
*/
|
|
1729
|
+
export function fleetLocalProviderAuthStatus(agentUid, provider, deps = {}) {
|
|
1730
|
+
let files;
|
|
1731
|
+
try {
|
|
1732
|
+
files = fleetLocalExecutables(agentUid, deps);
|
|
1733
|
+
}
|
|
1734
|
+
catch {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
const run = deps.run ?? execFileSync;
|
|
1738
|
+
try {
|
|
1739
|
+
const status = runJson(run, files.helper, ["auth", "status", "--profile", files.profileDir, "--bot-id", agentUid, "--provider", provider], { stdio: ["ignore", "pipe", "pipe"], timeout: 15_000 }, `${provider} sign-in status`);
|
|
1740
|
+
return { authenticated: status.authenticated === true, ...(typeof status.authType === "string" ? { authType: status.authType } : {}) };
|
|
1741
|
+
}
|
|
1742
|
+
catch {
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
/** The helper waits this long for a sign-in import before the move counts as failed. */
|
|
1747
|
+
const FLEET_PROVIDER_IMPORT_TIMEOUT_MS = 60_000;
|
|
1748
|
+
/**
|
|
1749
|
+
* Sign a bot in from a login already on this computer, without a browser:
|
|
1750
|
+
* the helper imports the Codex CLI login (CODEX_HOME, else ~/.codex) or
|
|
1751
|
+
* copies another Fleet bot's Grok login. `env` is added to the helper's
|
|
1752
|
+
* environment (a bot's own Codex home). The profile's provider is already
|
|
1753
|
+
* the runtime's preset from the bootstrap; this re-renders and checks it.
|
|
1754
|
+
*/
|
|
1755
|
+
export function importFleetLocalProviderLogin(agentUid, runtime, deps = {}, opts = {}) {
|
|
1756
|
+
const provider = runtime === "grok" ? "xai-oauth" : "openai-codex";
|
|
1757
|
+
const run = deps.run ?? execFileSync;
|
|
1758
|
+
const files = fleetLocalExecutables(agentUid, deps);
|
|
1759
|
+
const env = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
1760
|
+
const auth = runJson(run, files.helper, ["auth", "login", "--profile", files.profileDir, "--bot-id", agentUid, "--provider", provider, "--no-browser"], { stdio: ["ignore", "pipe", "pipe"], timeout: FLEET_PROVIDER_IMPORT_TIMEOUT_MS, ...(env ? { env } : {}) }, `${provider} sign-in import`);
|
|
1761
|
+
if (auth.authenticated !== true)
|
|
1762
|
+
throw expectedError(`HQ Fleet Local could not import a ${provider} sign-in from this computer.`);
|
|
1763
|
+
runJson(run, files.helper, ["render-profile", "--spec", files.profile, "--mode", "update"], { stdio: ["ignore", "pipe", "pipe"] }, "profile update");
|
|
1764
|
+
runJson(run, files.helper, ["doctor-profile", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "profile validation");
|
|
1765
|
+
return { runtimeProvider: provider };
|
|
1766
|
+
}
|
|
1698
1767
|
async function installSharedFleetRuntime(installation, root, python, platform, run, spawn, onProgress) {
|
|
1699
1768
|
const identity = digest(Buffer.from(JSON.stringify({
|
|
1700
1769
|
version: installation.release.version,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BotApi, JobRunCompletion } from "../api.js";
|
|
2
|
+
import { type BotConfig } from "../config.js";
|
|
3
|
+
import { type LocalJob } from "../local-jobs.js";
|
|
4
|
+
import { type LaneRecord } from "../../lanes/types.js";
|
|
5
|
+
export declare const SOURCE_REPORT_JOB_KIND = "source-report";
|
|
6
|
+
export declare const REPORT_COOLDOWN_MS: number;
|
|
7
|
+
export declare const sourceLaneTerminal: (lane: LaneRecord) => boolean;
|
|
8
|
+
export declare function ensureSourceReportJobs(dir: string, config: BotConfig, companies?: string[], home?: string): LocalJob[];
|
|
9
|
+
/** Local append only; duplicate watcher observations are harmless. */
|
|
10
|
+
export declare function emitExecutiveLaneFinished(hqRoot: string, lane: LaneRecord, root?: string, home?: string): boolean;
|
|
11
|
+
/** Replay terminal commits whose watcher never durably published its event. */
|
|
12
|
+
export declare function flushExecutiveLaneFinished(hqRoot: string, root?: string, home?: string): void;
|
|
13
|
+
export declare function sourceReportReceiptPath(hqRoot: string, company: string, sourceRef: string): string;
|
|
14
|
+
export interface SourceReporterDeps {
|
|
15
|
+
config: BotConfig;
|
|
16
|
+
api: Pick<BotApi, "sendDm">;
|
|
17
|
+
home?: string;
|
|
18
|
+
botsDir?: string;
|
|
19
|
+
companies?: () => string[];
|
|
20
|
+
now?: () => Date;
|
|
21
|
+
lanes?: (company: string, sourceRef: string) => LaneRecord[];
|
|
22
|
+
/** Fault injection at the two durable boundaries, without changing delivery. */
|
|
23
|
+
afterIntent?: () => void;
|
|
24
|
+
afterSend?: () => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createSourceReporter(deps: SourceReporterDeps): {
|
|
27
|
+
handle(event: unknown, payload: unknown, company: unknown): Promise<JobRunCompletion>;
|
|
28
|
+
/** Poll from the resident bot loop, so a last lane inside cooldown is not lost. */
|
|
29
|
+
recover(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=source-report.d.ts.map
|