@indigoai-us/hq-cli 5.345.182 → 5.345.187
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 +16 -0
- package/dist/command-catalog.generated.d.ts +29 -0
- package/dist/command-catalog.generated.js +35 -0
- package/dist/commands/bot.d.ts +5 -5
- package/dist/commands/bot.js +30 -31
- package/dist/commands/sources.d.ts +37 -0
- package/dist/commands/sources.js +104 -1
- package/dist/lib/anywhere-person-setting.js +24 -10
- package/dist/lib/bot/fleet-local.d.ts +4 -2
- package/dist/lib/bot/fleet-local.js +23 -7
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -7
- package/dist/lib/bot/jobs/hot-signal-sync.js +106 -9
- package/dist/lib/bot/local-jobs.d.ts +4 -2
- package/dist/lib/bot/local-jobs.js +33 -4
- package/dist/lib/bot/run.js +2 -2
- package/dist/lib/hot-channels/adapters/attio.js +3 -0
- package/dist/lib/hot-channels/adapters/linear.js +4 -0
- package/dist/lib/hot-channels/adapters/meetings.js +4 -0
- package/dist/lib/hot-channels/adapters/slack.js +3 -0
- package/dist/lib/hot-channels/contract.d.ts +7 -0
- package/dist/lib/hot-channels/ledger.d.ts +8 -0
- package/dist/lib/hot-channels/ledger.js +6 -2
- package/dist/lib/hot-channels/loop.d.ts +6 -0
- package/dist/lib/hot-channels/loop.js +16 -2
- package/dist/lib/hot-channels/person-vault-reader.d.ts +41 -0
- package/dist/lib/hot-channels/person-vault-reader.js +59 -0
- package/dist/lib/hot-channels/run.d.ts +8 -0
- package/dist/lib/hot-channels/run.js +19 -0
- package/dist/lib/hot-channels/schedule.d.ts +64 -0
- package/dist/lib/hot-channels/schedule.js +162 -0
- package/dist/lib/hot-channels/selection.d.ts +87 -0
- package/dist/lib/hot-channels/selection.js +175 -0
- package/dist/utils/shipped-codex-hooks.js +1 -1
- package/dist/utils/version-report.d.ts +1 -1
- package/dist/utils/version-report.js +20 -7
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.187] — 2026-10-30
|
|
6
|
+
- The HQ Anywhere person gate now follows the desktop Settings key `hq-anywhere-opt-out`. Turning HQ Anywhere on in the app enables `hq mcp serve` without a separate `hq-anywhere` write. Older servers that only have `hq-anywhere` still work.
|
|
7
|
+
|
|
8
|
+
- `hq version` and the desktop HQ Core row now send your GitHub token when they ask GitHub for the latest HQ Core release. Without it, GitHub's anonymous cap made the check fail and the app showed Not checked. A stale or revoked token falls back to the anonymous public lookup once.
|
|
9
|
+
- Repairing an existing local bot now installs the CLI's pinned HQ Fleet Runtime release before restarting it.
|
|
10
|
+
- Local bots now keep each HQ DM thread in its own conversation, so a reply cannot use context from another thread.
|
|
11
|
+
- Updating an existing local bot now imports its refreshed machine credential exactly once.
|
|
12
|
+
- Updating an existing local bot now records the HQ Fleet Runtime release it actually installed.
|
|
13
|
+
- The hot-channel sync now honours the channels, issues and records you picked for each connection. Slack lands only threads in picked channels, Linear only picked issues (by id or identifier such as ENG-123) and Attio only picked records. An empty pick on a Slack, Linear or Attio connection counts as no choice yet and syncs everything; saving an empty pick on every meeting recorder leaves meetings out. A connection with no saved pick syncs everything. When a saved pick may exist but cannot be read (an error, no answer within 5 seconds, or a malformed file), that connection is skipped for the run and its run-ledger line is marked failed with reason `selection-unreadable`; the other connections still sync. Gmail does not use picks yet.
|
|
14
|
+
- A bot with no company of its own, such as the personal bot first-run setup creates, now registers the daily hot-channel sync for your active company (`activeCompany` in the HQ config, the company `hq sources sync` uses). Only one local bot registers it per company, and a job you removed is not added back. When the job is first created the bot runs one sync right away in the background, marked `firstRun` in the run ledger, so a new install does not wait for the first scheduled time.
|
|
15
|
+
- New `hq sources schedule` shows when the bot's daily hot-channel sync runs, on which bot, and the latest run of each adapter over the last 7 days. `--at HH:MM` moves it to that time each day in the job's own time zone; `--json` prints `{ company, job, lastRuns }`.
|
|
16
|
+
- Adding a job to a local bot no longer replaces an unreadable `jobs.json` with a list holding only the new job; it stops with an error and leaves the file as it is.
|
|
17
|
+
|
|
18
|
+
## [5.345.183] — 2026-10-30
|
|
19
|
+
- New local Grok bots now reuse an existing encrypted Grok login on the same computer instead of asking you to sign in again.
|
|
20
|
+
|
|
5
21
|
## [5.345.182] — 2026-10-30
|
|
6
22
|
- Keep returning clients on the legacy engine until they update to the corrected cloud receiver, and preserve rename/delete convergence when switching sync engines.
|
|
7
23
|
- Local Grok bots now answer the first owner message normally after their welcome instead of introducing themselves again with `/help`.
|
|
@@ -5239,6 +5239,35 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5239
5239
|
readonly defaultValue: "<runtime-path>";
|
|
5240
5240
|
}];
|
|
5241
5241
|
readonly subcommands: readonly [];
|
|
5242
|
+
}, {
|
|
5243
|
+
readonly name: "schedule";
|
|
5244
|
+
readonly description: "Show when the bot's daily hot-channel sync runs, or set it with --at HH:MM";
|
|
5245
|
+
readonly aliases: readonly [];
|
|
5246
|
+
readonly hidden: false;
|
|
5247
|
+
readonly helpOption: true;
|
|
5248
|
+
readonly helpCommand: false;
|
|
5249
|
+
readonly usage: "[options]";
|
|
5250
|
+
readonly helpText: readonly [];
|
|
5251
|
+
readonly arguments: readonly [];
|
|
5252
|
+
readonly options: readonly [{
|
|
5253
|
+
readonly flags: "--company <slug>";
|
|
5254
|
+
readonly description: "Company slug (defaults to .hq/config.json activeCompany)";
|
|
5255
|
+
readonly hidden: false;
|
|
5256
|
+
}, {
|
|
5257
|
+
readonly flags: "--at <HH:MM>";
|
|
5258
|
+
readonly description: "Run the sync daily at this 24-hour time, in the job's time zone";
|
|
5259
|
+
readonly hidden: false;
|
|
5260
|
+
}, {
|
|
5261
|
+
readonly flags: "--json";
|
|
5262
|
+
readonly description: "Print the schedule and last runs as JSON";
|
|
5263
|
+
readonly hidden: false;
|
|
5264
|
+
}, {
|
|
5265
|
+
readonly flags: "--hq-root <path>";
|
|
5266
|
+
readonly description: "Local HQ tree root (default: <runtime-path>)";
|
|
5267
|
+
readonly hidden: false;
|
|
5268
|
+
readonly defaultValue: "<runtime-path>";
|
|
5269
|
+
}];
|
|
5270
|
+
readonly subcommands: readonly [];
|
|
5242
5271
|
}, {
|
|
5243
5272
|
readonly name: "sync";
|
|
5244
5273
|
readonly description: "Run every hot channel now: each adapter once per connection you can use";
|
|
@@ -6353,6 +6353,41 @@ export const COMMAND_CATALOG = [
|
|
|
6353
6353
|
],
|
|
6354
6354
|
"subcommands": []
|
|
6355
6355
|
},
|
|
6356
|
+
{
|
|
6357
|
+
"name": "schedule",
|
|
6358
|
+
"description": "Show when the bot's daily hot-channel sync runs, or set it with --at HH:MM",
|
|
6359
|
+
"aliases": [],
|
|
6360
|
+
"hidden": false,
|
|
6361
|
+
"helpOption": true,
|
|
6362
|
+
"helpCommand": false,
|
|
6363
|
+
"usage": "[options]",
|
|
6364
|
+
"helpText": [],
|
|
6365
|
+
"arguments": [],
|
|
6366
|
+
"options": [
|
|
6367
|
+
{
|
|
6368
|
+
"flags": "--company <slug>",
|
|
6369
|
+
"description": "Company slug (defaults to .hq/config.json activeCompany)",
|
|
6370
|
+
"hidden": false
|
|
6371
|
+
},
|
|
6372
|
+
{
|
|
6373
|
+
"flags": "--at <HH:MM>",
|
|
6374
|
+
"description": "Run the sync daily at this 24-hour time, in the job's time zone",
|
|
6375
|
+
"hidden": false
|
|
6376
|
+
},
|
|
6377
|
+
{
|
|
6378
|
+
"flags": "--json",
|
|
6379
|
+
"description": "Print the schedule and last runs as JSON",
|
|
6380
|
+
"hidden": false
|
|
6381
|
+
},
|
|
6382
|
+
{
|
|
6383
|
+
"flags": "--hq-root <path>",
|
|
6384
|
+
"description": "Local HQ tree root (default: <runtime-path>)",
|
|
6385
|
+
"hidden": false,
|
|
6386
|
+
"defaultValue": "<runtime-path>"
|
|
6387
|
+
}
|
|
6388
|
+
],
|
|
6389
|
+
"subcommands": []
|
|
6390
|
+
},
|
|
6356
6391
|
{
|
|
6357
6392
|
"name": "sync",
|
|
6358
6393
|
"description": "Run every hot channel now: each adapter once per connection you can use",
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -409,10 +409,10 @@ export interface RemoteBotRow {
|
|
|
409
409
|
export declare function registerBotCommand(program: Command): void;
|
|
410
410
|
/**
|
|
411
411
|
* The company a bot's hot-signal-sync job syncs: the bot's own company, else
|
|
412
|
-
* the first company it serves
|
|
413
|
-
*
|
|
414
|
-
* when it was given one explicitly (`--company`), so the job never writes into
|
|
415
|
-
* a vault the bot does not belong to.
|
|
412
|
+
* the first company it serves, else (for a bot with no company) the HQ's
|
|
413
|
+
* active company. Null = none, and no job is registered.
|
|
416
414
|
*/
|
|
417
|
-
export declare function hotSignalSyncCompany(config: Pick<BotConfig, "companySlug" | "kind" | "companies" | "workerId">
|
|
415
|
+
export declare function hotSignalSyncCompany(config: Pick<BotConfig, "companySlug" | "kind" | "companies" | "workerId"> & {
|
|
416
|
+
hqRoot?: string;
|
|
417
|
+
}): string | null;
|
|
418
418
|
//# sourceMappingURL=bot.d.ts.map
|
package/dist/commands/bot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { withLocalJobs } from "../lib/bot/local-jobs.js";
|
|
2
|
-
import { currentHotSignalSyncCompany,
|
|
2
|
+
import { currentHotSignalSyncCompany, readActiveCompanySlug, registerHotSignalSyncOnStart, runHotSignalSyncJob } from "../lib/bot/jobs/hot-signal-sync.js";
|
|
3
3
|
import { createClaudeSignalsPass } from "../lib/hot-channels/signals-pass.js";
|
|
4
4
|
import { machineTimeZone } from "../lib/bot/job-run.js";
|
|
5
5
|
import { runCompanyHotChannelSync } from "./sources.js";
|
|
@@ -478,6 +478,7 @@ async function adoptFleetRuntimeProfile(name, dir, config) {
|
|
|
478
478
|
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
479
479
|
ownerCompanies: companyScope,
|
|
480
480
|
}, { python, start: false });
|
|
481
|
+
patchBotConfig(dir, { engine: installation.engine });
|
|
481
482
|
const credentials = botCredsPath(dir);
|
|
482
483
|
if (fs.existsSync(credentials))
|
|
483
484
|
importFleetLocalMachineCredential(config.agentUid, credentials);
|
|
@@ -1310,24 +1311,31 @@ async function runBotResident(nameArg) {
|
|
|
1310
1311
|
// The built-in hot-signal-sync job, registered once in the bot's own
|
|
1311
1312
|
// jobs.json (06:30 daily in this computer's time zone until the owner
|
|
1312
1313
|
// reschedules it with `hq bot jobs`). It runs in-process, with no model turn.
|
|
1313
|
-
|
|
1314
|
+
// A bot with no company (the personal bot first-run setup creates) syncs the
|
|
1315
|
+
// HQ's active company. Only one local bot registers the job per company, and
|
|
1316
|
+
// the first sync starts in the background right after the job is created.
|
|
1317
|
+
const runHotSignalSync = (input) => runHotSignalSyncJob({
|
|
1318
|
+
...input,
|
|
1319
|
+
sync: (syncInput) => runCompanyHotChannelSync({ hqRoot: config.hqRoot, ...syncInput }),
|
|
1320
|
+
// A new pass per run: the login preflight and the 20-document cap are per run.
|
|
1321
|
+
signalsPass: createClaudeSignalsPass({ hqRoot: config.hqRoot, company: input.company }),
|
|
1322
|
+
});
|
|
1323
|
+
let otherBotDirs = [];
|
|
1314
1324
|
try {
|
|
1315
|
-
|
|
1316
|
-
for (const stale of removeStaleHotSignalSyncJobs(dir, syncCompany)) {
|
|
1317
|
-
console.log(`Removed the hot-channel sync job ${stale.jobId}: this bot no longer syncs that company.`);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
catch (err) {
|
|
1321
|
-
console.error(`Could not check the hot-channel sync jobs: ${err instanceof Error ? err.message : String(err)}`);
|
|
1325
|
+
otherBotDirs = listBotNames(botsRoot()).map((n) => botDir(n));
|
|
1322
1326
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
ensureHotSignalSyncJob(dir, { company: syncCompany, timezone: machineTimeZone() });
|
|
1326
|
-
}
|
|
1327
|
-
catch (err) {
|
|
1328
|
-
console.error(`Could not register the hot-channel sync job for ${syncCompany}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1329
|
-
}
|
|
1327
|
+
catch {
|
|
1328
|
+
// No other bots could be listed; this bot is the only one checked.
|
|
1330
1329
|
}
|
|
1330
|
+
registerHotSignalSyncOnStart({
|
|
1331
|
+
dir,
|
|
1332
|
+
config,
|
|
1333
|
+
activeCompany: readActiveCompanySlug(config.hqRoot) ?? null,
|
|
1334
|
+
otherBotDirs,
|
|
1335
|
+
timezone: machineTimeZone(),
|
|
1336
|
+
runFirst: (company) => runHotSignalSync({ company, catchUp: false, firstRun: true, now: () => new Date() }),
|
|
1337
|
+
log: (level, message) => (level === "warn" ? console.error(message) : console.log(message)),
|
|
1338
|
+
});
|
|
1331
1339
|
const owner = new OwnerContextCache(() => lookupOwnerContext(config.ownerUid), Date.now, undefined, config.ownerUid);
|
|
1332
1340
|
const handle = await runBot({
|
|
1333
1341
|
dir,
|
|
@@ -1335,15 +1343,7 @@ async function runBotResident(nameArg) {
|
|
|
1335
1343
|
api,
|
|
1336
1344
|
runtime: runtimeFor(config.runtime),
|
|
1337
1345
|
ownerContext: () => owner.get(),
|
|
1338
|
-
hotSignalSync: ({ company, catchUp, now, sendAlert }) =>
|
|
1339
|
-
company,
|
|
1340
|
-
catchUp,
|
|
1341
|
-
now,
|
|
1342
|
-
sendAlert,
|
|
1343
|
-
sync: (input) => runCompanyHotChannelSync({ hqRoot: config.hqRoot, ...input }),
|
|
1344
|
-
// A new pass per run: the login preflight and the 20-document cap are per run.
|
|
1345
|
-
signalsPass: createClaudeSignalsPass({ hqRoot: config.hqRoot, company }),
|
|
1346
|
-
}),
|
|
1346
|
+
hotSignalSync: ({ company, catchUp, now, sendAlert }) => runHotSignalSync({ company, catchUp, now, sendAlert }),
|
|
1347
1347
|
// US-005: decision picks in the owner DM, with the bot's own machine token
|
|
1348
1348
|
// (never the owner's login).
|
|
1349
1349
|
decisions: new DecisionsClient(token),
|
|
@@ -2556,7 +2556,8 @@ async function repairLocalBot(name, api, bot, daemon) {
|
|
|
2556
2556
|
createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
|
|
2557
2557
|
if (isFleetRuntimeBot(config)) {
|
|
2558
2558
|
const adopted = await adoptFleetRuntimeProfile(name, dir, config);
|
|
2559
|
-
|
|
2559
|
+
if (!adopted)
|
|
2560
|
+
importFleetLocalMachineCredential(bot.agentUid, botCredsPath(dir));
|
|
2560
2561
|
const restarted = controlFleetLocalBot(bot.agentUid, adopted ? "install" : "restart").ok === true;
|
|
2561
2562
|
return restarted ? "new credentials, HQ Fleet Runtime restarted" : "new credentials; the HQ Fleet Runtime did not restart (hq bot start " + name + ")";
|
|
2562
2563
|
}
|
|
@@ -2937,12 +2938,10 @@ export function registerBotCommand(program) {
|
|
|
2937
2938
|
}
|
|
2938
2939
|
/**
|
|
2939
2940
|
* The company a bot's hot-signal-sync job syncs: the bot's own company, else
|
|
2940
|
-
* the first company it serves
|
|
2941
|
-
*
|
|
2942
|
-
* when it was given one explicitly (`--company`), so the job never writes into
|
|
2943
|
-
* a vault the bot does not belong to.
|
|
2941
|
+
* the first company it serves, else (for a bot with no company) the HQ's
|
|
2942
|
+
* active company. Null = none, and no job is registered.
|
|
2944
2943
|
*/
|
|
2945
2944
|
export function hotSignalSyncCompany(config) {
|
|
2946
|
-
return currentHotSignalSyncCompany(config);
|
|
2945
|
+
return currentHotSignalSyncCompany(config, config.hqRoot ? (readActiveCompanySlug(config.hqRoot) ?? null) : null);
|
|
2947
2946
|
}
|
|
2948
2947
|
//# sourceMappingURL=bot.js.map
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* hq sources entities List entities the caller has access to.
|
|
14
14
|
* hq sources runs Show the local hot-channel run ledger for one day.
|
|
15
15
|
* hq sources sync Run every hot channel now for the signed-in person.
|
|
16
|
+
* hq sources schedule Show or set when the bot's daily hot-channel sync runs.
|
|
16
17
|
*
|
|
17
18
|
* Defaults:
|
|
18
19
|
* --entity falls back to .hq/config.json activeCompany (per sync.ts).
|
|
@@ -22,6 +23,8 @@
|
|
|
22
23
|
import { Command } from "commander";
|
|
23
24
|
import { type RunLedgerAdapterSummary, type RunLedgerEntry, type RunTrigger, type SignalsGeneratedFn } from "../lib/hot-channels/ledger.js";
|
|
24
25
|
import type { HotChannelRegistry } from "../lib/hot-channels/registry.js";
|
|
26
|
+
import { type SelectionLoader } from "../lib/hot-channels/selection.js";
|
|
27
|
+
import { type ScheduleReport } from "../lib/hot-channels/schedule.js";
|
|
25
28
|
import { type OnDemandSyncDeps, type OnDemandSyncReport } from "../lib/hot-channels/run.js";
|
|
26
29
|
type ListFormat = "table" | "json";
|
|
27
30
|
export interface RunsOptions {
|
|
@@ -58,6 +61,8 @@ export interface SyncOverrides {
|
|
|
58
61
|
now?: () => Date;
|
|
59
62
|
/** The signals pass; defaults to the local `claude -p` pass under the user's own login. */
|
|
60
63
|
signalsGenerated?: SignalsGeneratedFn;
|
|
64
|
+
/** This run's saved-selection loader; defaults to the person's vault bucket. */
|
|
65
|
+
createSelections?: () => SelectionLoader | null;
|
|
61
66
|
}
|
|
62
67
|
/**
|
|
63
68
|
* Network access for one sync: the catalog, the person's usable connections,
|
|
@@ -74,6 +79,8 @@ export interface CompanyHotChannelSyncInput {
|
|
|
74
79
|
/** `scheduled` for the bot's hot-signal-sync job; `on-demand` by default. */
|
|
75
80
|
trigger?: RunTrigger;
|
|
76
81
|
catchUp?: boolean;
|
|
82
|
+
/** The first run after the bot registered its hot-signal-sync job; marked in the ledger. */
|
|
83
|
+
firstRun?: boolean;
|
|
77
84
|
signalsGenerated?: SignalsGeneratedFn;
|
|
78
85
|
/** The signed-in person's `prs_` person uid in this company; defaults to a roster lookup by email. */
|
|
79
86
|
resolvePersonUid?: (token: string, companyUid: string, email: string) => Promise<string | null>;
|
|
@@ -83,6 +90,12 @@ export interface CompanyHotChannelSyncInput {
|
|
|
83
90
|
* `settings/signals/preferences.yaml`, through the HQ integrations gateway.
|
|
84
91
|
*/
|
|
85
92
|
signalsAlert?: SignalsAlertSenders;
|
|
93
|
+
/**
|
|
94
|
+
* Builds this run's saved-selection loader (one per run). Defaults to
|
|
95
|
+
* reading `integrations/hot-channels/<connectionId>.json` from the
|
|
96
|
+
* person's own vault bucket. Return null to sync without selections.
|
|
97
|
+
*/
|
|
98
|
+
createSelections?: () => SelectionLoader | null;
|
|
86
99
|
}
|
|
87
100
|
export interface SignalsAlertSenders {
|
|
88
101
|
/** The primary alert to the owner; throws when it was not delivered. */
|
|
@@ -106,6 +119,30 @@ export declare function runCompanyHotChannelSync(input: CompanyHotChannelSyncInp
|
|
|
106
119
|
* line under `companies/{company}/sources/`, and nothing else.
|
|
107
120
|
*/
|
|
108
121
|
export declare function runSync(options: SyncOptions, overrides?: SyncOverrides): Promise<OnDemandSyncReport>;
|
|
122
|
+
export interface ScheduleOptions {
|
|
123
|
+
company?: string;
|
|
124
|
+
at?: string;
|
|
125
|
+
json?: boolean;
|
|
126
|
+
hqRoot: string;
|
|
127
|
+
}
|
|
128
|
+
export interface ScheduleOverrides {
|
|
129
|
+
now?: Date;
|
|
130
|
+
/** Where local bots live; defaults to `~/.hq/bots` (or HQ_BOTS_ROOT). */
|
|
131
|
+
botsRoot?: string;
|
|
132
|
+
/** Whose ledger to read; defaults to the signed-in person. */
|
|
133
|
+
email?: string;
|
|
134
|
+
}
|
|
135
|
+
export declare class NoHotSignalSyncJobError extends Error {
|
|
136
|
+
readonly company: string;
|
|
137
|
+
constructor(company: string);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Show the company's hot-signal-sync job (bot, schedule, next and last run)
|
|
141
|
+
* and each adapter's most recent ledger entry over the last 7 UTC days. With
|
|
142
|
+
* `--at HH:MM`, reschedule the job to daily at that time in its own time zone
|
|
143
|
+
* first. Exits non-zero when `--at` is given and no job exists.
|
|
144
|
+
*/
|
|
145
|
+
export declare function runSchedule(options: ScheduleOptions, overrides?: ScheduleOverrides): Promise<ScheduleReport>;
|
|
109
146
|
export declare function registerSourcesCommand(program: Command): void;
|
|
110
147
|
export {};
|
|
111
148
|
//# sourceMappingURL=sources.d.ts.map
|
package/dist/commands/sources.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* hq sources entities List entities the caller has access to.
|
|
14
14
|
* hq sources runs Show the local hot-channel run ledger for one day.
|
|
15
15
|
* hq sources sync Run every hot channel now for the signed-in person.
|
|
16
|
+
* hq sources schedule Show or set when the bot's daily hot-channel sync runs.
|
|
16
17
|
*
|
|
17
18
|
* Defaults:
|
|
18
19
|
* --entity falls back to .hq/config.json activeCompany (per sync.ts).
|
|
@@ -24,7 +25,7 @@ import * as fs from "node:fs";
|
|
|
24
25
|
import * as path from "node:path";
|
|
25
26
|
import chalk from "chalk";
|
|
26
27
|
import { SOURCE_CHANNELS, assertSourceChannel, resolveEntity, listAvailableEntities, listSources, getSource, VaultClient, } from "@indigoai-us/hq-cloud";
|
|
27
|
-
import { ensureCognitoToken, ensureCognitoIdToken, buildVaultConfig, DEFAULT_HQ_ROOT, } from "../utils/cognito-session.js";
|
|
28
|
+
import { ensureCognitoToken, ensureCognitoIdToken, buildVaultConfig, DEFAULT_COGNITO, DEFAULT_HQ_ROOT, } from "../utils/cognito-session.js";
|
|
28
29
|
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
29
30
|
import { isAuthError } from "../utils/auth-error.js";
|
|
30
31
|
import { IntegrationsCliError, callGateway, fetchUsableApps, gatewayResultIsError, isBuiltInGatewayProvider, raiseForResponse, toolPrefixForProvider, unwrapGatewayResult, } from "./integrations-core.js";
|
|
@@ -37,6 +38,12 @@ import { createClaudeSignalsPass } from "../lib/hot-channels/signals-pass.js";
|
|
|
37
38
|
import { assertSafeSegment } from "../lib/hot-channels/landing.js";
|
|
38
39
|
import { acquireSyncLock, SYNC_LOCK_BUSY_REASON } from "../lib/hot-channels/sync-lock.js";
|
|
39
40
|
import { createBuiltinHotChannelRegistry } from "../lib/hot-channels/adapters/index.js";
|
|
41
|
+
import { createSelectionLoader } from "../lib/hot-channels/selection.js";
|
|
42
|
+
import { createPersonVaultReader } from "../lib/hot-channels/person-vault-reader.js";
|
|
43
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
44
|
+
import { resolveCanonicalPersonUid } from "./cloud.js";
|
|
45
|
+
import { botsRoot as defaultBotsRoot } from "../lib/bot/paths.js";
|
|
46
|
+
import { findHotSignalSyncJobs, lastRunsPerAdapter, parseDailyAt, renderScheduleReport, rescheduleDaily, toJobView, } from "../lib/hot-channels/schedule.js";
|
|
40
47
|
import { listActiveMembers } from "./members.js";
|
|
41
48
|
import { runOnDemandSync, } from "../lib/hot-channels/run.js";
|
|
42
49
|
// ---------------------------------------------------------------------------
|
|
@@ -376,6 +383,36 @@ async function signedInSession(company) {
|
|
|
376
383
|
throw err;
|
|
377
384
|
}
|
|
378
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* The default selection loader: reads each connection's saved pick from the
|
|
388
|
+
* signed-in person's vault bucket through the same STS vend-self path as
|
|
389
|
+
* `hq files cat --personal`. Read failures log to stderr and fall back to
|
|
390
|
+
* landing everything; they never fail the run.
|
|
391
|
+
*/
|
|
392
|
+
function defaultSelections() {
|
|
393
|
+
let client;
|
|
394
|
+
const vault = () => {
|
|
395
|
+
client ??= (async () => new VaultClient(buildVaultConfig(process.env.HQ_ACCESS_TOKEN || (await ensureCognitoToken({ interactive: false })))))();
|
|
396
|
+
return client;
|
|
397
|
+
};
|
|
398
|
+
const reader = createPersonVaultReader({
|
|
399
|
+
resolvePersonUid: async () => {
|
|
400
|
+
const c = await vault();
|
|
401
|
+
return resolveCanonicalPersonUid({
|
|
402
|
+
listMyMemberships: () => c.listMyMemberships(),
|
|
403
|
+
listPersonEntities: () => c.entity.listByType("person"),
|
|
404
|
+
getEntity: async () => null,
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
getEntity: async (uid) => (await vault()).entity.get(uid),
|
|
408
|
+
vendSelf: async (personUid) => (await vault()).sts.vendSelf({ personUid }),
|
|
409
|
+
s3Factory: ({ region, credentials }) => new S3Client({ region, credentials }),
|
|
410
|
+
region: DEFAULT_COGNITO.region,
|
|
411
|
+
});
|
|
412
|
+
return createSelectionLoader(reader, {
|
|
413
|
+
log: (message) => void process.stderr.write(chalk.yellow(`${message}\n`)),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
379
416
|
/** The signed-in member's `prs_` uid from the company roster, or null when the roster is unavailable. */
|
|
380
417
|
async function rosterPersonUid(token, companyUid, email) {
|
|
381
418
|
try {
|
|
@@ -440,6 +477,7 @@ async function skippedReport(input, vaultRoot, personKey, personUid, now) {
|
|
|
440
477
|
reason: SYNC_LOCK_BUSY_REASON,
|
|
441
478
|
personUid,
|
|
442
479
|
catchUp: input.catchUp ?? false,
|
|
480
|
+
...(input.firstRun ? { firstRun: true } : {}),
|
|
443
481
|
};
|
|
444
482
|
await appendRunLedgerEntry(vaultRoot, personKey, line);
|
|
445
483
|
return {
|
|
@@ -454,7 +492,10 @@ async function skippedReport(input, vaultRoot, personKey, personUid, now) {
|
|
|
454
492
|
}
|
|
455
493
|
async function runLockedCompanySync(input, vaultRoot, personKey, author, session, lockHeld) {
|
|
456
494
|
const deps = gatewaySyncDeps(session.token, session.companyUid);
|
|
495
|
+
// A fresh loader per run: selections are cached for this run only.
|
|
496
|
+
const selections = input.createSelections ? input.createSelections() : defaultSelections();
|
|
457
497
|
const report = await runOnDemandSync({
|
|
498
|
+
...(selections ? { selections } : {}),
|
|
458
499
|
deps,
|
|
459
500
|
registry: input.registry ??
|
|
460
501
|
createBuiltinHotChannelRegistry({ vaultRoot, identities: await meetingIdentities(input, session, author.email) }),
|
|
@@ -465,6 +506,7 @@ async function runLockedCompanySync(input, vaultRoot, personKey, author, session
|
|
|
465
506
|
...(input.now ? { now: input.now } : {}),
|
|
466
507
|
...(input.trigger ? { trigger: input.trigger } : {}),
|
|
467
508
|
...(input.catchUp === undefined ? {} : { catchUp: input.catchUp }),
|
|
509
|
+
...(input.firstRun ? { firstRun: true } : {}),
|
|
468
510
|
...(input.signalsGenerated ? { signalsGenerated: input.signalsGenerated } : {}),
|
|
469
511
|
beforeWrite: lockHeld,
|
|
470
512
|
});
|
|
@@ -500,6 +542,7 @@ export async function runSync(options, overrides = {}) {
|
|
|
500
542
|
...(options.adapter !== undefined ? { adapter: options.adapter } : {}),
|
|
501
543
|
...(overrides.registry ? { registry: overrides.registry } : {}),
|
|
502
544
|
...(overrides.now ? { now: overrides.now } : {}),
|
|
545
|
+
...(overrides.createSelections ? { createSelections: overrides.createSelections } : {}),
|
|
503
546
|
signalsGenerated: overrides.signalsGenerated ?? createClaudeSignalsPass({ hqRoot: options.hqRoot, company }),
|
|
504
547
|
// The CLI has no owner-DM sender: the terminal is the primary alert here.
|
|
505
548
|
signalsAlert: {
|
|
@@ -549,6 +592,56 @@ function renderSyncTable(company, report) {
|
|
|
549
592
|
}
|
|
550
593
|
return lines.join("\n");
|
|
551
594
|
}
|
|
595
|
+
export class NoHotSignalSyncJobError extends Error {
|
|
596
|
+
company;
|
|
597
|
+
constructor(company) {
|
|
598
|
+
super(`No hot-channel sync job for ${company} on any local bot, so there is nothing to reschedule. ` +
|
|
599
|
+
"Start a bot bound to this company (it adds the job on first start), then run this again.");
|
|
600
|
+
this.company = company;
|
|
601
|
+
this.name = "NoHotSignalSyncJobError";
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Show the company's hot-signal-sync job (bot, schedule, next and last run)
|
|
606
|
+
* and each adapter's most recent ledger entry over the last 7 UTC days. With
|
|
607
|
+
* `--at HH:MM`, reschedule the job to daily at that time in its own time zone
|
|
608
|
+
* first. Exits non-zero when `--at` is given and no job exists.
|
|
609
|
+
*/
|
|
610
|
+
export async function runSchedule(options, overrides = {}) {
|
|
611
|
+
const now = overrides.now ?? new Date();
|
|
612
|
+
const company = options.company ?? readActiveCompanySlug(options.hqRoot);
|
|
613
|
+
if (!company) {
|
|
614
|
+
throw new Error("No company specified. Pass --company <slug> or set activeCompany in .hq/config.json.");
|
|
615
|
+
}
|
|
616
|
+
assertSafeSegment(company, "company");
|
|
617
|
+
// Validate before touching any job file.
|
|
618
|
+
if (options.at !== undefined)
|
|
619
|
+
parseDailyAt(options.at);
|
|
620
|
+
let found = findHotSignalSyncJobs(overrides.botsRoot ?? defaultBotsRoot(), company);
|
|
621
|
+
if (options.at !== undefined) {
|
|
622
|
+
if (found.length === 0)
|
|
623
|
+
throw new NoHotSignalSyncJobError(company);
|
|
624
|
+
found = rescheduleDaily(found, options.at, now);
|
|
625
|
+
}
|
|
626
|
+
const email = overrides.email ?? resolveUploadAuthor()?.email;
|
|
627
|
+
const lastRuns = email
|
|
628
|
+
? await lastRunsPerAdapter(path.join(options.hqRoot, "companies", company), audienceKey([email]), now)
|
|
629
|
+
: [];
|
|
630
|
+
const report = {
|
|
631
|
+
company,
|
|
632
|
+
job: found[0] ? toJobView(found[0], now) : null,
|
|
633
|
+
lastRuns,
|
|
634
|
+
};
|
|
635
|
+
if (options.json) {
|
|
636
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
process.stdout.write(renderScheduleReport(report, found.slice(1).map((f) => f.botName)) + "\n");
|
|
640
|
+
if (!email)
|
|
641
|
+
process.stdout.write("Sign in (hq login) to see recent runs.\n");
|
|
642
|
+
}
|
|
643
|
+
return report;
|
|
644
|
+
}
|
|
552
645
|
// ---------------------------------------------------------------------------
|
|
553
646
|
// Registration
|
|
554
647
|
// ---------------------------------------------------------------------------
|
|
@@ -612,6 +705,16 @@ export function registerSourcesCommand(program) {
|
|
|
612
705
|
.action(withErrorHandling(async (options) => {
|
|
613
706
|
await runRuns(options);
|
|
614
707
|
}));
|
|
708
|
+
sources
|
|
709
|
+
.command("schedule")
|
|
710
|
+
.description("Show when the bot's daily hot-channel sync runs, or set it with --at HH:MM")
|
|
711
|
+
.option("--company <slug>", "Company slug (defaults to .hq/config.json activeCompany)")
|
|
712
|
+
.option("--at <HH:MM>", "Run the sync daily at this 24-hour time, in the job's time zone")
|
|
713
|
+
.option("--json", "Print the schedule and last runs as JSON")
|
|
714
|
+
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
715
|
+
.action(withErrorHandling(async (options) => {
|
|
716
|
+
await runSchedule(options);
|
|
717
|
+
}));
|
|
615
718
|
sources
|
|
616
719
|
.command("sync")
|
|
617
720
|
.description("Run every hot channel now: each adapter once per connection you can use")
|
|
@@ -7,6 +7,20 @@ function hasPersonTokenCache(env, home) {
|
|
|
7
7
|
const stateDir = env.HQ_STATE_DIR?.trim() || path.join(home, ".hq");
|
|
8
8
|
return fs.existsSync(path.join(stateDir, "cognito-tokens.json"));
|
|
9
9
|
}
|
|
10
|
+
const HQ_ANYWHERE_OPT_OUT_KEY = "hq-anywhere-opt-out";
|
|
11
|
+
const HQ_ANYWHERE_OPT_IN_KEY = "hq-anywhere";
|
|
12
|
+
async function readPersonSettingEnvelope(fetchImpl, apiBaseUrl, token, key) {
|
|
13
|
+
const response = await fetchImpl(`${apiBaseUrl.replace(/\/$/, "")}/v1/bot/auto-schedule?setting=${key}`, { method: "GET", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5_000) });
|
|
14
|
+
if (!response.ok)
|
|
15
|
+
return null;
|
|
16
|
+
const body = await response.json();
|
|
17
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
18
|
+
return null;
|
|
19
|
+
const envelope = body;
|
|
20
|
+
if (envelope.key !== key || typeof envelope.value !== "boolean")
|
|
21
|
+
return null;
|
|
22
|
+
return { key: envelope.key, value: envelope.value };
|
|
23
|
+
}
|
|
10
24
|
/** Returns null when the person preference cannot be read; callers must fail closed. */
|
|
11
25
|
export async function readHqAnywherePersonSetting(options = {}) {
|
|
12
26
|
const env = options.env ?? process.env;
|
|
@@ -21,16 +35,16 @@ export async function readHqAnywherePersonSetting(options = {}) {
|
|
|
21
35
|
});
|
|
22
36
|
const token = await getPersonToken();
|
|
23
37
|
const apiBaseUrl = options.apiBaseUrl ?? env.HQ_PRO_API_URL ?? env.HQ_API_URL ?? env.HQ_VAULT_API_URL ?? "https://hqapi.hq.computer";
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
39
|
+
// Desktop Settings writes hq-anywhere-opt-out (hq-pro #5241). true = opted
|
|
40
|
+
// out. A person who never chose reads false, which means Anywhere is on.
|
|
41
|
+
const optOut = await readPersonSettingEnvelope(fetchImpl, apiBaseUrl, token, HQ_ANYWHERE_OPT_OUT_KEY);
|
|
42
|
+
if (optOut)
|
|
43
|
+
return !optOut.value;
|
|
44
|
+
const optIn = await readPersonSettingEnvelope(fetchImpl, apiBaseUrl, token, HQ_ANYWHERE_OPT_IN_KEY);
|
|
45
|
+
if (optIn)
|
|
46
|
+
return optIn.value;
|
|
47
|
+
throw new Error("InvalidEnvelope");
|
|
34
48
|
}
|
|
35
49
|
catch (error) {
|
|
36
50
|
const errorName = error instanceof Error && /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(error.name)
|
|
@@ -136,7 +136,7 @@ export interface FleetLocalRemovalResult {
|
|
|
136
136
|
credentialsRemoved: number;
|
|
137
137
|
}
|
|
138
138
|
export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
|
|
139
|
-
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\
|
|
139
|
+
export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=\n-----END PUBLIC KEY-----\n";
|
|
140
140
|
export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
141
141
|
export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
|
|
142
142
|
export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv): string;
|
|
@@ -147,7 +147,9 @@ export declare const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS: number;
|
|
|
147
147
|
export declare function installPinnedFleetLocalDistribution(deps?: FleetLocalInstallDeps): Promise<FleetLocalInstallation>;
|
|
148
148
|
/** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
|
|
149
149
|
export declare function fleetLocalGatewayPid(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): number | null;
|
|
150
|
-
export declare function fleetLocalProfileNeedsAdoption(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform">
|
|
150
|
+
export declare function fleetLocalProfileNeedsAdoption(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform"> & {
|
|
151
|
+
release?: FleetLocalReleasePin;
|
|
152
|
+
}): boolean;
|
|
151
153
|
export declare function controlFleetLocalBot(agentUid: string, action: FleetLocalServiceAction, deps?: FleetLocalControlDeps): Record<string, unknown>;
|
|
152
154
|
export declare function exportFleetLocalMachineCredential(agentUid: string, deps?: FleetLocalControlDeps): BotMachineCreds;
|
|
153
155
|
export declare function fleetLocalPromotionRuntime(agentUid: string, deps?: FleetLocalControlDeps): LocalBotPromotionRuntime;
|
|
@@ -36,18 +36,18 @@ export function fleetProviderNeedsSignIn(provider) {
|
|
|
36
36
|
}
|
|
37
37
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
38
38
|
repository: "indigoai-us/hq-fleet-local",
|
|
39
|
-
tag: "v0.4.
|
|
40
|
-
version: "0.4.
|
|
41
|
-
artifact: "hq-fleet-local-0.4.
|
|
42
|
-
artifactSha256: "
|
|
39
|
+
tag: "v0.4.17",
|
|
40
|
+
version: "0.4.17",
|
|
41
|
+
artifact: "hq-fleet-local-0.4.17.zip",
|
|
42
|
+
artifactSha256: "d2c5c49d4ef47da6003f4c2f3be7dcd2d8a287523c8577dfe0239e6e0705e4b4",
|
|
43
43
|
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
44
44
|
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
45
|
-
manifestSha256: "
|
|
46
|
-
signatureSha256: "
|
|
45
|
+
manifestSha256: "63663ffe8c6bef4f2aefa587d20431156cab4170a0f23533cdab7aab444a7d35",
|
|
46
|
+
signatureSha256: "bb4af0b453ae03fdbc18e6335ca23e2f0de42bd9e322dba7c4f31d0c8dc08aa7",
|
|
47
47
|
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
48
48
|
};
|
|
49
49
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
50
|
-
|
|
50
|
+
MCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=
|
|
51
51
|
-----END PUBLIC KEY-----
|
|
52
52
|
`;
|
|
53
53
|
function digest(value) {
|
|
@@ -388,6 +388,20 @@ function sharedRuntimeFromMarker(profileDir, root) {
|
|
|
388
388
|
return null;
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
|
+
function profileEngineMatchesRelease(profileDir, release) {
|
|
392
|
+
try {
|
|
393
|
+
const marker = JSON.parse(fs.readFileSync(path.join(profileDir, "bootstrap.json"), "utf8"));
|
|
394
|
+
const expected = engineMetadata(release);
|
|
395
|
+
return marker.engine?.id === expected.id &&
|
|
396
|
+
marker.engine.distribution === expected.distribution &&
|
|
397
|
+
marker.engine.version === expected.version &&
|
|
398
|
+
marker.engine.artifactSha256 === expected.artifactSha256 &&
|
|
399
|
+
marker.engine.runtimeCommit === expected.runtimeCommit;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
391
405
|
function hasEmbeddedRuntime(profileDir, platform) {
|
|
392
406
|
const runtimeDir = path.join(profileDir, "runtime");
|
|
393
407
|
try {
|
|
@@ -467,6 +481,8 @@ export function fleetLocalProfileNeedsAdoption(agentUid, deps = {}) {
|
|
|
467
481
|
const shared = sharedRuntimeFromMarker(profileDir, root);
|
|
468
482
|
if (!shared)
|
|
469
483
|
return true;
|
|
484
|
+
if (!profileEngineMatchesRelease(profileDir, deps.release ?? HQ_FLEET_LOCAL_RELEASE))
|
|
485
|
+
return true;
|
|
470
486
|
const platform = deps.platform ?? process.platform;
|
|
471
487
|
if (!["hq-fleet", "hq-fleet-local", "python"].every((name) => fs.existsSync(venvExecutable(shared.runtimeDir, name, platform))))
|
|
472
488
|
return true;
|