@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/command-catalog.generated.d.ts +393 -12
  3. package/dist/command-catalog.generated.js +482 -12
  4. package/dist/commands/bot-events.d.ts +19 -0
  5. package/dist/commands/bot-events.js +46 -0
  6. package/dist/commands/bot-jobs.d.ts +2 -0
  7. package/dist/commands/bot-jobs.js +43 -2
  8. package/dist/commands/bot.d.ts +5 -0
  9. package/dist/commands/bot.js +33 -1
  10. package/dist/commands/core-resolve-company-native.d.ts +2 -1
  11. package/dist/commands/core-resolve-company-native.js +2 -24
  12. package/dist/commands/deploy-domains.d.ts +14 -2
  13. package/dist/commands/deploy-domains.js +726 -42
  14. package/dist/commands/integrations-connect.js +41 -3
  15. package/dist/commands/lanes-report.d.ts +2 -1
  16. package/dist/commands/lanes-report.js +44 -1
  17. package/dist/commands/lanes.d.ts +9 -0
  18. package/dist/commands/lanes.js +95 -9
  19. package/dist/commands/meetings.js +1 -21
  20. package/dist/commands/secrets.js +27 -6
  21. package/dist/commands/signals-run.d.ts +2 -0
  22. package/dist/commands/signals-run.js +6 -1
  23. package/dist/lanes-drain-hook.js +9 -0
  24. package/dist/lib/bot/api.d.ts +5 -0
  25. package/dist/lib/bot/claude-login.d.ts +31 -0
  26. package/dist/lib/bot/claude-login.js +160 -0
  27. package/dist/lib/bot/company-bind.d.ts +34 -0
  28. package/dist/lib/bot/company-bind.js +112 -0
  29. package/dist/lib/bot/config.js +3 -1
  30. package/dist/lib/bot/events.d.ts +18 -0
  31. package/dist/lib/bot/events.js +160 -0
  32. package/dist/lib/bot/executive-settings.d.ts +10 -0
  33. package/dist/lib/bot/executive-settings.js +43 -0
  34. package/dist/lib/bot/fleet-cron.js +4 -0
  35. package/dist/lib/bot/fleet-local.d.ts +12 -0
  36. package/dist/lib/bot/fleet-local.js +50 -20
  37. package/dist/lib/bot/job-run.js +1 -1
  38. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
  39. package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
  40. package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
  41. package/dist/lib/bot/jobs/meeting-detect.js +176 -0
  42. package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
  43. package/dist/lib/bot/jobs/meeting-rider.js +284 -0
  44. package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
  45. package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
  46. package/dist/lib/bot/local-jobs.d.ts +15 -2
  47. package/dist/lib/bot/local-jobs.js +24 -7
  48. package/dist/lib/bot/run.d.ts +7 -49
  49. package/dist/lib/bot/run.js +135 -12
  50. package/dist/lib/bot/session.d.ts +19 -1
  51. package/dist/lib/bot/session.js +75 -28
  52. package/dist/lib/bot/status.d.ts +21 -0
  53. package/dist/lib/core-utils/manifest-slugs.d.ts +2 -0
  54. package/dist/lib/core-utils/manifest-slugs.js +26 -0
  55. package/dist/lib/doctor/checks/bots.d.ts +6 -0
  56. package/dist/lib/doctor/checks/bots.js +47 -1
  57. package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
  58. package/dist/lib/hot-channels/adapters/meetings.js +3 -1
  59. package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
  60. package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
  61. package/dist/lib/hot-channels/schedule.js +2 -2
  62. package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
  63. package/dist/lib/hot-channels/signals-pass.js +51 -17
  64. package/dist/lib/lanes/admission.d.ts +2 -0
  65. package/dist/lib/lanes/admission.js +4 -0
  66. package/dist/lib/lanes/approval-gate.js +4 -1
  67. package/dist/lib/lanes/links.d.ts +8 -1
  68. package/dist/lib/lanes/links.js +84 -3
  69. package/dist/lib/lanes/outcome.d.ts +154 -0
  70. package/dist/lib/lanes/outcome.js +151 -0
  71. package/dist/lib/lanes/reconcile.js +1 -0
  72. package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
  73. package/dist/lib/lanes/signal-brief-template.js +40 -0
  74. package/dist/lib/lanes/signal-lane.d.ts +24 -0
  75. package/dist/lib/lanes/signal-lane.js +99 -0
  76. package/dist/lib/lanes/source-report.d.ts +37 -0
  77. package/dist/lib/lanes/source-report.js +66 -0
  78. package/dist/lib/lanes/spawn.d.ts +6 -0
  79. package/dist/lib/lanes/spawn.js +21 -0
  80. package/dist/lib/lanes/store.d.ts +3 -0
  81. package/dist/lib/lanes/store.js +64 -0
  82. package/dist/lib/lanes/story-ledger.d.ts +3 -0
  83. package/dist/lib/lanes/story-ledger.js +1 -0
  84. package/dist/lib/lanes/types.d.ts +137 -0
  85. package/dist/lib/lanes/types.js +17 -2
  86. package/dist/lib/lanes/watcher.js +4 -0
  87. package/dist/lib/live-meetings.d.ts +19 -0
  88. package/dist/lib/live-meetings.js +23 -0
  89. package/dist/lib/mesh/live/daemon/doctor.js +6 -7
  90. package/dist/lib/mesh/live/spool.d.ts +8 -0
  91. package/dist/lib/mesh/live/spool.js +59 -40
  92. package/dist/lib/plan-limit-nag.d.ts +30 -4
  93. package/dist/lib/plan-limit-nag.js +68 -17
  94. package/dist/lib/signals/engine.d.ts +4 -0
  95. package/dist/lib/signals/engine.js +25 -5
  96. package/dist/lib/signals/incremental.d.ts +42 -0
  97. package/dist/lib/signals/incremental.js +191 -0
  98. package/dist/lib/signals/prior-signals.d.ts +11 -0
  99. package/dist/lib/signals/prior-signals.js +44 -0
  100. package/dist/lib/signals/reply.js +6 -1
  101. package/dist/lib/signals/settings.d.ts +5 -0
  102. package/dist/lib/signals/settings.js +17 -0
  103. package/dist/lib/signals/steps/extract.d.ts +30 -2
  104. package/dist/lib/signals/steps/extract.js +153 -11
  105. package/dist/lib/signals/steps/types.d.ts +1 -0
  106. package/dist/lib/signals/write-guard.js +7 -0
  107. package/dist/secrets-env-action.d.ts +70 -0
  108. package/dist/secrets-env-action.js +220 -2
  109. package/dist/utils/cognito-callback-port.d.ts +15 -0
  110. package/dist/utils/cognito-callback-port.js +51 -0
  111. package/dist/utils/cognito-config.js +2 -3
  112. package/dist/utils/cognito-session.d.ts +7 -6
  113. package/dist/utils/cognito-session.js +14 -14
  114. package/dist/utils/interactive-caller.d.ts +15 -0
  115. package/dist/utils/interactive-caller.js +70 -0
  116. package/dist/utils/login-browser.js +3 -1
  117. package/dist/utils/resident-package-preload.js +4 -0
  118. 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.22",
83
- version: "0.4.22",
84
- artifact: "hq-fleet-local-0.4.22.zip",
85
- artifactSha256: "b12c01d48c8add7609de557f02987eaa7570223751446915835f52dc9b69d1db",
86
- runtimeArtifact: "hq-fleet-runtime-f17ab10aa598.tar",
87
- runtimeArtifactSha256: "a2296305a82af7f834804f4240705d791f0c08fa52257955dae3dce091d39ee8",
88
- manifestSha256: "f3c62e4e64f91e0aeab32d0999a106c89122144c83757874c81781fb417c0392",
89
- signatureSha256: "f07502b61b35431292c9d656f9e602f5230f8fb8ca093993ec92d4353077b4ba",
90
- runtimeCommit: "f17ab10aa598d5915f47bf0b83d332a5cc243f98",
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 fetchHqFleetReleaseLinks(release) {
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 links = await (deps.hqReleaseLinks ?? fetchHqFleetReleaseLinks)(release);
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
- const files = links
775
- ? await downloadFleetLocalReleaseFromHq(release, fetcher, links, cachedRuntime)
776
- : await downloadFleetLocalRelease(release, fetcher, (deps.token ?? resolveFleetReleaseToken)(), cachedRuntime);
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) {
@@ -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