@indigoai-us/hq-cli 5.345.197 → 5.345.199
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 +111 -0
- package/dist/command-catalog.generated.d.ts +393 -12
- package/dist/command-catalog.generated.js +482 -12
- package/dist/commands/bot-events.d.ts +19 -0
- package/dist/commands/bot-events.js +46 -0
- package/dist/commands/bot-jobs.d.ts +2 -0
- package/dist/commands/bot-jobs.js +43 -2
- package/dist/commands/bot.d.ts +5 -0
- package/dist/commands/bot.js +33 -1
- package/dist/commands/core-resolve-company-native.d.ts +2 -1
- package/dist/commands/core-resolve-company-native.js +2 -24
- package/dist/commands/deploy-domains.d.ts +14 -2
- package/dist/commands/deploy-domains.js +726 -42
- package/dist/commands/integrations-connect.js +41 -3
- package/dist/commands/lanes-report.d.ts +2 -1
- package/dist/commands/lanes-report.js +44 -1
- package/dist/commands/lanes.d.ts +9 -0
- package/dist/commands/lanes.js +95 -9
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/secrets.js +27 -6
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/lanes-drain-hook.js +9 -0
- package/dist/lib/bot/api.d.ts +5 -0
- package/dist/lib/bot/claude-login.d.ts +31 -0
- package/dist/lib/bot/claude-login.js +160 -0
- package/dist/lib/bot/company-bind.d.ts +34 -0
- package/dist/lib/bot/company-bind.js +112 -0
- package/dist/lib/bot/config.js +3 -1
- package/dist/lib/bot/events.d.ts +18 -0
- package/dist/lib/bot/events.js +160 -0
- package/dist/lib/bot/executive-settings.d.ts +10 -0
- package/dist/lib/bot/executive-settings.js +43 -0
- package/dist/lib/bot/fleet-cron.js +4 -0
- package/dist/lib/bot/fleet-local.d.ts +12 -0
- package/dist/lib/bot/fleet-local.js +50 -20
- package/dist/lib/bot/job-run.js +1 -1
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
- package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
- package/dist/lib/bot/jobs/meeting-detect.js +176 -0
- package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
- package/dist/lib/bot/jobs/meeting-rider.js +284 -0
- package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
- package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
- package/dist/lib/bot/local-jobs.d.ts +15 -2
- package/dist/lib/bot/local-jobs.js +24 -7
- package/dist/lib/bot/run.d.ts +7 -49
- package/dist/lib/bot/run.js +135 -12
- package/dist/lib/bot/session.d.ts +19 -1
- package/dist/lib/bot/session.js +75 -28
- package/dist/lib/bot/status.d.ts +21 -0
- package/dist/lib/core-utils/manifest-slugs.d.ts +2 -0
- package/dist/lib/core-utils/manifest-slugs.js +26 -0
- package/dist/lib/doctor/checks/bots.d.ts +6 -0
- package/dist/lib/doctor/checks/bots.js +47 -1
- package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
- package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
- package/dist/lib/hot-channels/schedule.js +2 -2
- package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
- package/dist/lib/hot-channels/signals-pass.js +51 -17
- package/dist/lib/lanes/admission.d.ts +2 -0
- package/dist/lib/lanes/admission.js +4 -0
- package/dist/lib/lanes/approval-gate.js +4 -1
- package/dist/lib/lanes/links.d.ts +8 -1
- package/dist/lib/lanes/links.js +84 -3
- package/dist/lib/lanes/outcome.d.ts +154 -0
- package/dist/lib/lanes/outcome.js +151 -0
- package/dist/lib/lanes/reconcile.js +1 -0
- package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
- package/dist/lib/lanes/signal-brief-template.js +40 -0
- package/dist/lib/lanes/signal-lane.d.ts +24 -0
- package/dist/lib/lanes/signal-lane.js +99 -0
- package/dist/lib/lanes/source-report.d.ts +37 -0
- package/dist/lib/lanes/source-report.js +66 -0
- package/dist/lib/lanes/spawn.d.ts +6 -0
- package/dist/lib/lanes/spawn.js +21 -0
- package/dist/lib/lanes/store.d.ts +3 -0
- package/dist/lib/lanes/store.js +64 -0
- package/dist/lib/lanes/story-ledger.d.ts +3 -0
- package/dist/lib/lanes/story-ledger.js +1 -0
- package/dist/lib/lanes/types.d.ts +137 -0
- package/dist/lib/lanes/types.js +17 -2
- package/dist/lib/lanes/watcher.js +4 -0
- package/dist/lib/live-meetings.d.ts +19 -0
- package/dist/lib/live-meetings.js +23 -0
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/spool.d.ts +8 -0
- package/dist/lib/mesh/live/spool.js +59 -40
- package/dist/lib/plan-limit-nag.d.ts +30 -4
- package/dist/lib/plan-limit-nag.js +68 -17
- package/dist/lib/signals/engine.d.ts +4 -0
- package/dist/lib/signals/engine.js +25 -5
- package/dist/lib/signals/incremental.d.ts +42 -0
- package/dist/lib/signals/incremental.js +191 -0
- package/dist/lib/signals/prior-signals.d.ts +11 -0
- package/dist/lib/signals/prior-signals.js +44 -0
- package/dist/lib/signals/reply.js +6 -1
- package/dist/lib/signals/settings.d.ts +5 -0
- package/dist/lib/signals/settings.js +17 -0
- package/dist/lib/signals/steps/extract.d.ts +30 -2
- package/dist/lib/signals/steps/extract.js +153 -11
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/secrets-env-action.d.ts +70 -0
- package/dist/secrets-env-action.js +220 -2
- package/dist/utils/cognito-callback-port.d.ts +15 -0
- package/dist/utils/cognito-callback-port.js +51 -0
- package/dist/utils/cognito-config.js +2 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +14 -14
- package/dist/utils/interactive-caller.d.ts +15 -0
- package/dist/utils/interactive-caller.js +70 -0
- package/dist/utils/login-browser.js +3 -1
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +9 -7
|
@@ -79,15 +79,15 @@ export function sharedRuntimeLayout(root, id, platform) {
|
|
|
79
79
|
}
|
|
80
80
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
81
81
|
repository: "indigoai-us/hq-fleet-local",
|
|
82
|
-
tag: "v0.4.
|
|
83
|
-
version: "0.4.
|
|
84
|
-
artifact: "hq-fleet-local-0.4.
|
|
85
|
-
artifactSha256: "
|
|
86
|
-
runtimeArtifact: "hq-fleet-runtime-
|
|
87
|
-
runtimeArtifactSha256: "
|
|
88
|
-
manifestSha256: "
|
|
89
|
-
signatureSha256: "
|
|
90
|
-
runtimeCommit: "
|
|
82
|
+
tag: "v0.4.24",
|
|
83
|
+
version: "0.4.24",
|
|
84
|
+
artifact: "hq-fleet-local-0.4.24.zip",
|
|
85
|
+
artifactSha256: "daa74b7aaf7f26d8c069f33aa94a453369fb2d7ae8c122e7fbe48a5b38e73f31",
|
|
86
|
+
runtimeArtifact: "hq-fleet-runtime-d257fdd1171f.tar",
|
|
87
|
+
runtimeArtifactSha256: "8262c5f28773d35c04f7f9ce6f61ed19a88e601847f9d12d5ab11fb5d8e87fb0",
|
|
88
|
+
manifestSha256: "302d2206957b815a846db583a49e7862948fd8a8a36aaff8e0e8a6e0aa7a1b9c",
|
|
89
|
+
signatureSha256: "efee90b00c6eb1d0f53a76e93a756435975c9da6c70c9c0b505aafcce79a586a",
|
|
90
|
+
runtimeCommit: "d257fdd1171f1edd5e381b3bad7baacab0cb00d3",
|
|
91
91
|
};
|
|
92
92
|
/** hq-fleet-local trust/release-public.pem (rotated 2026-10-30; signed v0.4.17). */
|
|
93
93
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
@@ -394,15 +394,15 @@ export function resolveFleetReleaseToken(env = process.env, run = execFileSync)
|
|
|
394
394
|
}
|
|
395
395
|
throw expectedError("HQ could not provide the HQ Fleet Runtime download. Sign in with `hq login` and retry.");
|
|
396
396
|
}
|
|
397
|
-
export async function
|
|
397
|
+
export async function lookupHqFleetReleaseLinks(release, deps = {}) {
|
|
398
398
|
let token;
|
|
399
399
|
try {
|
|
400
|
-
token = await ensureCognitoToken({ tokenSource: "person", interactive: false });
|
|
400
|
+
token = await (deps.token ?? (() => ensureCognitoToken({ tokenSource: "person", interactive: false })))();
|
|
401
401
|
}
|
|
402
402
|
catch {
|
|
403
|
-
return null;
|
|
403
|
+
return { links: null, reason: "signed_out" };
|
|
404
404
|
}
|
|
405
|
-
const response = await vaultApiFetch({
|
|
405
|
+
const response = await (deps.fetchApi ?? vaultApiFetch)({
|
|
406
406
|
token,
|
|
407
407
|
path: `/v1/fleet-local/releases/${encodeURIComponent(release.version)}`,
|
|
408
408
|
signal: AbortSignal.timeout(FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
|
|
@@ -411,7 +411,7 @@ export async function fetchHqFleetReleaseLinks(release) {
|
|
|
411
411
|
throw expectedError("HQ refused the HQ Fleet Runtime download. Sign in with `hq login` and retry.");
|
|
412
412
|
}
|
|
413
413
|
if (response.status === 404)
|
|
414
|
-
return null;
|
|
414
|
+
return { links: null, reason: "not_published" };
|
|
415
415
|
if (!response.ok) {
|
|
416
416
|
throw expectedError(`Could not get the HQ Fleet Local ${release.tag} download: HQ returned HTTP ${response.status}.`);
|
|
417
417
|
}
|
|
@@ -421,7 +421,22 @@ export async function fetchHqFleetReleaseLinks(release) {
|
|
|
421
421
|
if (typeof asset.name === "string" && typeof asset.url === "string")
|
|
422
422
|
links.set(asset.name, asset.url);
|
|
423
423
|
}
|
|
424
|
-
return links.size > 0 ? links : null;
|
|
424
|
+
return links.size > 0 ? { links, reason: null } : { links: null, reason: "not_published" };
|
|
425
|
+
}
|
|
426
|
+
export async function fetchHqFleetReleaseLinks(release) {
|
|
427
|
+
return (await lookupHqFleetReleaseLinks(release)).links;
|
|
428
|
+
}
|
|
429
|
+
/** The error for a machine that has neither HQ links nor a GitHub token, naming which one HQ lacked. */
|
|
430
|
+
function noReleaseSourceError(release, reason) {
|
|
431
|
+
if (reason === "not_published") {
|
|
432
|
+
return expectedError(`HQ has not published HQ Fleet Runtime ${release.version} for download yet. ` +
|
|
433
|
+
"Retry in a few minutes; if it keeps failing, report it with `hq bug`.");
|
|
434
|
+
}
|
|
435
|
+
if (reason === "signed_out") {
|
|
436
|
+
return expectedError("The HQ sign-in on this machine could not be used to get the HQ Fleet Runtime download. " +
|
|
437
|
+
"Run `hq login` and retry.");
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
425
440
|
}
|
|
426
441
|
export const FLEET_LOCAL_LATEST_TIMEOUT_MS = 5_000;
|
|
427
442
|
export const FLEET_LOCAL_LATEST_CACHE_MS = 10 * 60_000;
|
|
@@ -769,11 +784,26 @@ export async function installPinnedFleetLocalDistribution(deps = {}) {
|
|
|
769
784
|
}
|
|
770
785
|
else {
|
|
771
786
|
const fetcher = deps.fetch ?? fetch;
|
|
772
|
-
const
|
|
787
|
+
const lookup = deps.hqReleaseLinks
|
|
788
|
+
? { links: await deps.hqReleaseLinks(release), reason: null }
|
|
789
|
+
: await (deps.hqReleaseLookup ?? lookupHqFleetReleaseLinks)(release);
|
|
790
|
+
const links = lookup.links;
|
|
773
791
|
const cachedRuntime = cachedRuntimeArchive(root, release);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
792
|
+
let files;
|
|
793
|
+
if (links) {
|
|
794
|
+
files = await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
let githubToken;
|
|
798
|
+
try {
|
|
799
|
+
githubToken = (deps.token ?? resolveFleetReleaseToken)();
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
// A customer has no GitHub token; say why HQ had no download instead of asking them to sign in again.
|
|
803
|
+
throw noReleaseSourceError(release, lookup.reason) ?? error;
|
|
804
|
+
}
|
|
805
|
+
files = await downloadFleetLocalRelease(release, fetcher, githubToken, cachedRuntime);
|
|
806
|
+
}
|
|
777
807
|
verifyFleetLocalRelease(files, release, publicKey);
|
|
778
808
|
warnCompatibility(files.manifest);
|
|
779
809
|
installFiles(dir, files, release, publicKey);
|
|
@@ -1687,7 +1717,7 @@ async function installSharedFleetRuntime(installation, root, python, platform, r
|
|
|
1687
1717
|
function writeNativeProfileMarker(profileDir, shared, engine) {
|
|
1688
1718
|
const marker = path.join(profileDir, "bootstrap.json");
|
|
1689
1719
|
const temporary = `${marker}.${process.pid}.${crypto.randomUUID()}`;
|
|
1690
|
-
fs.writeFileSync(temporary, `${JSON.stringify({ schemaVersion: 2, runtimeId: shared.id, engine }, null, 2)}\n`, { mode: 0o600 });
|
|
1720
|
+
fs.writeFileSync(temporary, `${JSON.stringify({ schemaVersion: 2, runtimeId: shared.id, engine, cliVersion: CLI_VERSION }, null, 2)}\n`, { mode: 0o600 });
|
|
1691
1721
|
fs.renameSync(temporary, marker);
|
|
1692
1722
|
}
|
|
1693
1723
|
function removeEmbeddedRuntime(profileDir, platform = process.platform) {
|
package/dist/lib/bot/job-run.js
CHANGED
|
@@ -215,7 +215,7 @@ export function planJobRuns(items, handled) {
|
|
|
215
215
|
const byJob = new Map();
|
|
216
216
|
for (const item of items) {
|
|
217
217
|
const job = readJobRun(item);
|
|
218
|
-
if (!job)
|
|
218
|
+
if (!job || item.messageId.startsWith("localjob-event-"))
|
|
219
219
|
continue;
|
|
220
220
|
const list = byJob.get(job.jobId);
|
|
221
221
|
if (list)
|
|
@@ -48,6 +48,12 @@ export declare const HOT_SIGNAL_SYNC_JOB_KIND = "hot-signal-sync";
|
|
|
48
48
|
* own schedule in `jobs.json` is the one that runs.
|
|
49
49
|
*/
|
|
50
50
|
export declare const HOT_SIGNAL_SYNC_DEFAULT_CRON = "cron(30 6 * * ? *)";
|
|
51
|
+
interface BuiltinJobsMarker {
|
|
52
|
+
v: 1;
|
|
53
|
+
registered: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
declare function readMarker(dir: string): BuiltinJobsMarker;
|
|
56
|
+
declare function writeMarker(dir: string, marker: BuiltinJobsMarker): void;
|
|
51
57
|
export declare function hotSignalSyncRegistered(dir: string, company: string): boolean;
|
|
52
58
|
export declare function fleetHotSignalSyncRegistered(dir: string, company: string): boolean;
|
|
53
59
|
export declare function markFleetHotSignalSyncRegistered(dir: string, company: string, now?: Date): void;
|
|
@@ -210,4 +216,6 @@ export interface HotSignalSyncRunOutcome {
|
|
|
210
216
|
/** Run the scheduled sync once and describe it in plain words. Never throws. */
|
|
211
217
|
export declare function runHotSignalSyncJob(input: HotSignalSyncRunInput): Promise<HotSignalSyncRunOutcome>;
|
|
212
218
|
export declare function describeSyncReport(company: string, report: OnDemandSyncReport): string;
|
|
219
|
+
/** Shared registration marker for additive built-in event jobs. */
|
|
220
|
+
export { readMarker as readBuiltinJobsMarker, writeMarker as writeBuiltinJobsMarker };
|
|
213
221
|
//# sourceMappingURL=hot-signal-sync.d.ts.map
|
|
@@ -430,4 +430,6 @@ export function describeSyncReport(company, report) {
|
|
|
430
430
|
lines.push(notice);
|
|
431
431
|
return lines.join("\n");
|
|
432
432
|
}
|
|
433
|
+
/** Shared registration marker for additive built-in event jobs. */
|
|
434
|
+
export { readMarker as readBuiltinJobsMarker, writeMarker as writeBuiltinJobsMarker };
|
|
433
435
|
//# sourceMappingURL=hot-signal-sync.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type LiveMeetingBot } from "../../live-meetings.js";
|
|
2
|
+
import type { BotConfig } from "../config.js";
|
|
3
|
+
export declare function detectIntervalSeconds(hqRoot: string): number;
|
|
4
|
+
/** Desktop events carry company UIDs; local rider files use manifest slugs. */
|
|
5
|
+
export declare function normalizeDetectedCompany(hqRoot: string, payload: unknown): unknown;
|
|
6
|
+
export interface MeetingDetectDeps {
|
|
7
|
+
hqRoot: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
enabled: () => boolean;
|
|
10
|
+
companies: () => string[];
|
|
11
|
+
list: (company: string, signal: AbortSignal) => Promise<{
|
|
12
|
+
companyId: string;
|
|
13
|
+
bots: LiveMeetingBot[];
|
|
14
|
+
}>;
|
|
15
|
+
intervalSeconds?: () => number;
|
|
16
|
+
now?: () => Date;
|
|
17
|
+
log: (message: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Live reads are keyed by Recall ID; the desktop may emit a saved meeting ID or SDK window ID. */
|
|
20
|
+
export declare function resolveDetectedMeeting(hqRoot: string, payload: unknown, list: MeetingDetectDeps["list"]): Promise<unknown>;
|
|
21
|
+
export declare function createMeetingDetect(deps: MeetingDetectDeps): {
|
|
22
|
+
/** Called by the inbox loop; network work never holds up inbox delivery. */
|
|
23
|
+
tick(): void;
|
|
24
|
+
settled(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
export declare function defaultMeetingDetectDeps(config: BotConfig, dir: string, log: (message: string) => void): MeetingDetectDeps;
|
|
28
|
+
//# sourceMappingURL=meeting-detect.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** No-model producer of the same event the desktop emits. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import { fetchAttendedLiveBots } from "../../live-meetings.js";
|
|
7
|
+
import { ensureCognitoToken } from "../../../utils/cognito-session.js";
|
|
8
|
+
import { getCompanyUid } from "../../../utils/vault-api.js";
|
|
9
|
+
import { assertSafeSegment } from "../../hot-channels/landing.js";
|
|
10
|
+
import { readManifestCompanies } from "../company-bind.js";
|
|
11
|
+
import { emitBotEvent } from "../events.js";
|
|
12
|
+
import { executiveRiderEnabled, isExecutiveBot } from "../executive-settings.js";
|
|
13
|
+
const RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
const ACTIVE = new Set(["joining", "recording", "in_call"]);
|
|
15
|
+
export function detectIntervalSeconds(hqRoot) {
|
|
16
|
+
try {
|
|
17
|
+
const settings = yaml.load(fs.readFileSync(path.join(hqRoot, "core/settings/signals.yaml"), "utf8"));
|
|
18
|
+
const value = settings?.rider?.detect_interval_seconds;
|
|
19
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(5, value) : 60;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (err.code !== "ENOENT")
|
|
23
|
+
throw err;
|
|
24
|
+
return 60;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Desktop events carry company UIDs; local rider files use manifest slugs. */
|
|
28
|
+
export function normalizeDetectedCompany(hqRoot, payload) {
|
|
29
|
+
if (!payload || typeof payload !== "object" || !("company" in payload) || typeof payload.company !== "string")
|
|
30
|
+
return payload;
|
|
31
|
+
const company = payload.company;
|
|
32
|
+
if (!company.startsWith("cmp_"))
|
|
33
|
+
return payload;
|
|
34
|
+
const manifest = yaml.load(fs.readFileSync(path.join(hqRoot, "companies/manifest.yaml"), "utf8"));
|
|
35
|
+
const slug = Object.entries(manifest?.companies ?? {}).find(([, value]) => value?.cloud_uid === company)?.[0];
|
|
36
|
+
return slug ? { ...payload, company: slug } : payload;
|
|
37
|
+
}
|
|
38
|
+
/** Live reads are keyed by Recall ID; the desktop may emit a saved meeting ID or SDK window ID. */
|
|
39
|
+
export async function resolveDetectedMeeting(hqRoot, payload, list) {
|
|
40
|
+
const normalized = normalizeDetectedCompany(hqRoot, payload);
|
|
41
|
+
if (!normalized || typeof normalized !== "object")
|
|
42
|
+
return normalized;
|
|
43
|
+
const event = normalized;
|
|
44
|
+
if (event.source === "poll" || typeof event.company !== "string" || typeof event.meeting_url !== "string")
|
|
45
|
+
return normalized;
|
|
46
|
+
assertSafeSegment(event.company, "company");
|
|
47
|
+
const { companyId, bots } = await list(event.company, AbortSignal.timeout(15_000));
|
|
48
|
+
const row = bots.find((bot) => bot && (bot.companyId === undefined || bot.companyId === companyId) &&
|
|
49
|
+
typeof bot.status === "string" && ACTIVE.has(bot.status.toLowerCase().replaceAll("-", "_")) &&
|
|
50
|
+
([bot.recallBotId, bot.botId, bot.meetingId].includes(String(event.meeting_id)) || bot.meetingUrl === event.meeting_url));
|
|
51
|
+
const id = row?.recallBotId || row?.botId || row?.meetingId;
|
|
52
|
+
return id ? { ...event, meeting_id: id } : normalized;
|
|
53
|
+
}
|
|
54
|
+
export function createMeetingDetect(deps) {
|
|
55
|
+
const now = deps.now ?? (() => new Date());
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
let next = 0;
|
|
58
|
+
let disabledLogged = false;
|
|
59
|
+
let pending;
|
|
60
|
+
const poll = async () => {
|
|
61
|
+
// One slow or denied company must not delay detection in another company.
|
|
62
|
+
await Promise.all([...new Set(deps.companies())].map(async (company) => {
|
|
63
|
+
if (controller.signal.aborted || !deps.enabled())
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
assertSafeSegment(company, "company");
|
|
67
|
+
const root = path.join(deps.hqRoot, "workspace/executive/meetings", company);
|
|
68
|
+
const file = path.join(root, ".detected.json");
|
|
69
|
+
let stored = {};
|
|
70
|
+
try {
|
|
71
|
+
stored = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err.code !== "ENOENT")
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
if (!stored || typeof stored !== "object" || Array.isArray(stored))
|
|
78
|
+
throw new Error("Invalid detected ledger");
|
|
79
|
+
const time = now();
|
|
80
|
+
const seen = Object.fromEntries(Object.entries(stored).filter((entry) => typeof entry[1] === "string" && Date.parse(entry[1]) > time.getTime() - RETENTION_MS));
|
|
81
|
+
const { companyId, bots } = await deps.list(company, controller.signal);
|
|
82
|
+
if (controller.signal.aborted || !deps.enabled())
|
|
83
|
+
return;
|
|
84
|
+
fs.mkdirSync(root, { recursive: true });
|
|
85
|
+
for (const bot of bots) {
|
|
86
|
+
if (!bot || (bot.companyId !== undefined && bot.companyId !== companyId) || typeof bot.status !== "string" || !ACTIVE.has(bot.status.toLowerCase().replaceAll("-", "_")))
|
|
87
|
+
continue;
|
|
88
|
+
const id = bot.recallBotId || bot.botId || bot.meetingId;
|
|
89
|
+
if (typeof id !== "string" || !id)
|
|
90
|
+
continue;
|
|
91
|
+
assertSafeSegment(id, "meeting id");
|
|
92
|
+
if (Object.hasOwn(seen, id))
|
|
93
|
+
continue;
|
|
94
|
+
// Check aliases too: an older rider may have started with a Recall ID.
|
|
95
|
+
const ids = [id, bot.recallBotId, bot.botId, bot.meetingId].filter((value) => typeof value === "string" && value.length > 0);
|
|
96
|
+
for (const alias of ids)
|
|
97
|
+
assertSafeSegment(alias, "meeting id");
|
|
98
|
+
if (ids.some((alias) => [company, companyId].some((co) => {
|
|
99
|
+
assertSafeSegment(co, "company");
|
|
100
|
+
const base = path.join(deps.hqRoot, "workspace/executive/meetings", co, alias);
|
|
101
|
+
return fs.existsSync(`${base}.rider.lock`) || fs.existsSync(`${base}.rider.json`);
|
|
102
|
+
}))) {
|
|
103
|
+
seen[id] = time.toISOString();
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const event = emitBotEvent(deps.dir, "meeting.detected", { meeting_id: id, company, source: "poll" }, time, (prior) => prior.event === "meeting.detected" && [company, companyId].includes(String(prior.payload.company)) && ids.includes(String(prior.payload.meeting_id)));
|
|
107
|
+
seen[id] = event.at;
|
|
108
|
+
}
|
|
109
|
+
const tmp = `${file}.${randomUUID()}.tmp`;
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(tmp, JSON.stringify(seen), { mode: 0o600 });
|
|
112
|
+
fs.renameSync(tmp, file);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
fs.rmSync(tmp, { force: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
if (!controller.signal.aborted)
|
|
120
|
+
deps.log(`meeting detection failed for ${company}`);
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
/** Called by the inbox loop; network work never holds up inbox delivery. */
|
|
126
|
+
tick() {
|
|
127
|
+
if (controller.signal.aborted || pending)
|
|
128
|
+
return;
|
|
129
|
+
if (!deps.enabled()) {
|
|
130
|
+
if (!disabledLogged) {
|
|
131
|
+
deps.log("rider disabled");
|
|
132
|
+
disabledLogged = true;
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (now().getTime() < next)
|
|
137
|
+
return;
|
|
138
|
+
const seconds = deps.intervalSeconds?.() ?? 60;
|
|
139
|
+
next = now().getTime() + (Number.isFinite(seconds) ? Math.max(5, seconds) : 60) * 1000;
|
|
140
|
+
pending = poll().catch(() => deps.log("meeting detection failed")).finally(() => { pending = undefined; });
|
|
141
|
+
},
|
|
142
|
+
async settled() { await pending; },
|
|
143
|
+
async stop() { controller.abort(); await pending; },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Bound waiting even for shared auth/scope helpers that cannot cancel every internal call. */
|
|
147
|
+
async function untilAborted(signal, work) {
|
|
148
|
+
signal.throwIfAborted();
|
|
149
|
+
let abort;
|
|
150
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
151
|
+
abort = () => reject(signal.reason);
|
|
152
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
return await Promise.race([work(), cancelled]);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
signal.removeEventListener("abort", abort);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function defaultMeetingDetectDeps(config, dir, log) {
|
|
162
|
+
return {
|
|
163
|
+
hqRoot: config.hqRoot, dir, log,
|
|
164
|
+
enabled: () => isExecutiveBot(config) && executiveRiderEnabled(),
|
|
165
|
+
companies: () => config.companySlug ? [config.companySlug] : readManifestCompanies(config.hqRoot),
|
|
166
|
+
intervalSeconds: () => detectIntervalSeconds(config.hqRoot),
|
|
167
|
+
list: async (company, signal) => {
|
|
168
|
+
const bounded = AbortSignal.any([signal, AbortSignal.timeout(15_000)]);
|
|
169
|
+
const token = await untilAborted(bounded, () => ensureCognitoToken({ interactive: false, signal: bounded }));
|
|
170
|
+
const companyId = company.startsWith("cmp_") ? company : await untilAborted(bounded, () => getCompanyUid(token, company));
|
|
171
|
+
const bots = await untilAborted(bounded, () => fetchAttendedLiveBots(token, companyId, true, bounded));
|
|
172
|
+
return { companyId, bots };
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=meeting-detect.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type BotConfig } from "../config.js";
|
|
2
|
+
export { isExecutiveBot } from "../executive-settings.js";
|
|
3
|
+
import { type LocalJob } from "../local-jobs.js";
|
|
4
|
+
export declare const MEETING_RIDER_JOB_KIND = "meeting-rider";
|
|
5
|
+
export declare const END_POLLS = 3;
|
|
6
|
+
export interface MeetingIdentity {
|
|
7
|
+
meeting_id: string;
|
|
8
|
+
company: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RiderRecord {
|
|
11
|
+
started_at: string;
|
|
12
|
+
ended_at?: string;
|
|
13
|
+
passes: number;
|
|
14
|
+
signals_written: string[];
|
|
15
|
+
lanes: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface RiderDeps {
|
|
18
|
+
hqRoot: string;
|
|
19
|
+
dir: string;
|
|
20
|
+
bot: string;
|
|
21
|
+
/** Must check the authenticated owner's attended list in the requested company. */
|
|
22
|
+
live: (meeting: MeetingIdentity) => Promise<{
|
|
23
|
+
attended: boolean;
|
|
24
|
+
status: string;
|
|
25
|
+
}>;
|
|
26
|
+
runIncremental: (meeting: MeetingIdentity) => Promise<{
|
|
27
|
+
signal_ids: string[];
|
|
28
|
+
lanes?: string[];
|
|
29
|
+
skipped?: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
enabled: () => boolean;
|
|
32
|
+
intervalSeconds?: () => number;
|
|
33
|
+
now?: () => Date;
|
|
34
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
35
|
+
isAlive?: (pid: number) => boolean;
|
|
36
|
+
log: (message: string) => void;
|
|
37
|
+
emit?: (meeting: MeetingIdentity, record: RiderRecord) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function ensureMeetingRiderJob(dir: string): LocalJob | null;
|
|
40
|
+
export declare function meetingRiderSettings(hqRoot: string, home?: string): {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
interval: number;
|
|
43
|
+
};
|
|
44
|
+
export declare function createMeetingRider(deps: RiderDeps): {
|
|
45
|
+
/** Synchronously persists intent before the event handler may acknowledge delivery. */
|
|
46
|
+
start(payload: unknown): void;
|
|
47
|
+
resume(): void;
|
|
48
|
+
settled(): Promise<void>;
|
|
49
|
+
stop(): Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
/** Production dependencies use the same attended endpoint as `meetings live`. */
|
|
52
|
+
export declare function defaultMeetingRiderDeps(config: BotConfig, dir: string, log: (message: string) => void): RiderDeps;
|
|
53
|
+
//# sourceMappingURL=meeting-rider.d.ts.map
|