@indigoai-us/hq-cli 5.345.202 → 5.345.203
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 +9 -0
- package/dist/command-catalog.generated.d.ts +24 -0
- package/dist/command-catalog.generated.js +26 -0
- package/dist/commands/capacity.js +10 -2
- package/dist/commands/daemon.d.ts +2 -1
- package/dist/commands/daemon.js +225 -4
- package/dist/commands/integrations-core.d.ts +3 -0
- package/dist/commands/integrations-core.js +11 -0
- package/dist/commands/integrations-manage.js +2 -2
- package/dist/commands/integrations.js +5 -5
- package/dist/commands/sync-core.js +2 -1
- package/dist/lib/bot/executive-settings.d.ts +1 -1
- package/dist/lib/bot/executive-settings.js +8 -6
- package/dist/lib/bot/jobs/meeting-detect.js +2 -2
- package/dist/lib/bot/jobs/meeting-rider.js +2 -2
- package/dist/lib/bot/run.js +3 -3
- package/dist/lib/capacity/codex-availability.d.ts +6 -0
- package/dist/lib/capacity/codex-availability.js +33 -0
- package/dist/lib/capacity/ledger.d.ts +0 -11
- package/dist/lib/capacity/ledger.js +30 -3
- package/dist/lib/capacity/route.js +11 -5
- package/dist/lib/daemon/catalog.d.ts +3 -0
- package/dist/lib/daemon/catalog.js +15 -2
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +9 -2
- package/dist/lib/daemon/install.d.ts +99 -0
- package/dist/lib/daemon/install.js +316 -7
- package/dist/lib/daemon/migrate.d.ts +3 -1
- package/dist/lib/daemon/migrate.js +14 -2
- package/dist/lib/daemon/plist-refresh.d.ts +34 -0
- package/dist/lib/daemon/plist-refresh.js +62 -0
- package/dist/lib/daemon/run.js +25 -0
- package/dist/lib/daemon/selfcheck.d.ts +71 -0
- package/dist/lib/daemon/selfcheck.js +150 -0
- package/dist/lib/daemon/supervisor.d.ts +31 -0
- package/dist/lib/daemon/supervisor.js +130 -6
- package/dist/lib/daemon/unit-holds.js +2 -1
- package/dist/lib/service-manager/launchd.d.ts +25 -0
- package/dist/lib/service-manager/launchd.js +51 -3
- package/dist/lib/service-manager/migration.d.ts +14 -0
- package/dist/lib/service-manager/migration.js +34 -0
- package/dist/lib/service-manager/systemd.d.ts +13 -0
- package/dist/lib/service-manager/systemd.js +19 -0
- package/dist/lib/service-manager/types.d.ts +8 -0
- package/dist/lib/sync-core/reconcile.d.ts +4 -1
- package/dist/lib/sync-core/reconcile.js +12 -2
- package/dist/lib/sync-core/release.d.ts +23 -0
- package/dist/lib/sync-core/release.js +39 -0
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.203] — 2026-11-05
|
|
6
|
+
- Fix Codex capacity routing after plan exhaustion: available credits keep accounts usable at lowest priority, and superseded snapshots or passed resets no longer retain exhausted usage.
|
|
7
|
+
- Keep company Outpost integration commands on their delegated machine session and allow sharing connections with an `out_*` principal.
|
|
8
|
+
- On macOS the `hq daemon` LaunchAgent now runs node directly and is no longer attributed to the HQ app, so launchd loads it at login whether or not the app is installed or allowed. `hq daemon status` now asks launchd or systemd and says whether the daemon runs under the service manager, runs outside it, or is not loaded. New `hq daemon start` loads the unit when it is missing and starts it. A Mac that still has the older plist, which runs through the app, gets it rewritten and reloaded when the daemon next starts or when `hq daemon start` runs, with no `hq daemon install` needed; `hq daemon status` warns while the plist, or the job launchd has loaded, still runs through the app, and `hq daemon start` reloads a job left on the old definition. `hq daemon start` leaves a daemon the desktop app hosts alone, and no longer mistakes state left by an earlier app-hosted daemon for that. `hq daemon install` treats a failed bootstrap as an error and retries it, and `hq daemon migrate --apply` loads the daemon's agent first and loads an older agent again if taking it over fails.
|
|
9
|
+
- The daemon no longer holds a sync engine for 30 minutes when its heartbeat timeouts come from a slow transfer the engine reports progress on (up to six such restarts per episode; after that they count again), and a sync engine held because another process had its lock (exit 17) starts again as soon as that process exits.
|
|
10
|
+
- The sync engine release can now be pinned on the server (`pin.json` on the engine download base); the CLI's built-in release is the fallback, and an installed engine is never downgraded.
|
|
11
|
+
- A self-check in the daemon sends the person signed in on the machine a DM when a sync engine unit has been held or failing for over 30 minutes, at most once a day per machine, naming every affected vault. It reports only units the daemon runs, and sends nothing when the machine is signed in as an agent.
|
|
12
|
+
- The Executive opt-in key is now `executive.enabled` in the HQ menubar settings file. The old `executiveMeetingRider.enabled` key is ignored, so values saved under it earlier now read as off.
|
|
13
|
+
|
|
5
14
|
## [5.345.202] — 2026-11-04
|
|
6
15
|
- The long-running `hq daemon run` and `hq mesh daemon run` processes no longer start Sentry. On some Macs, Sentry in these processes kept opening connections after network changes until the machine ran out of ports, so Claude and HQ lost internet access while other apps kept working. Short-lived CLI commands still report errors to Sentry, and the daemons keep their local logs.
|
|
7
16
|
- The Executive (meeting rider, action-item lanes and source report DMs) is now off unless `executiveMeetingRider.enabled` is set to `true` in the HQ menubar settings file. In 5.345.200 it was on by default for every setup bot.
|
|
@@ -18236,6 +18236,18 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
18236
18236
|
readonly hidden: false;
|
|
18237
18237
|
}];
|
|
18238
18238
|
readonly subcommands: readonly [];
|
|
18239
|
+
}, {
|
|
18240
|
+
readonly name: "start";
|
|
18241
|
+
readonly description: "Load the daemon's OS unit if the service manager does not have it, and start it";
|
|
18242
|
+
readonly aliases: readonly [];
|
|
18243
|
+
readonly hidden: false;
|
|
18244
|
+
readonly helpOption: true;
|
|
18245
|
+
readonly helpCommand: false;
|
|
18246
|
+
readonly usage: "[options]";
|
|
18247
|
+
readonly helpText: readonly [];
|
|
18248
|
+
readonly arguments: readonly [];
|
|
18249
|
+
readonly options: readonly [];
|
|
18250
|
+
readonly subcommands: readonly [];
|
|
18239
18251
|
}, {
|
|
18240
18252
|
readonly name: "uninstall";
|
|
18241
18253
|
readonly description: "Stop the daemon and remove its OS unit";
|
|
@@ -18816,6 +18828,18 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
18816
18828
|
readonly hidden: false;
|
|
18817
18829
|
}];
|
|
18818
18830
|
readonly subcommands: readonly [];
|
|
18831
|
+
}, {
|
|
18832
|
+
readonly name: "sync-core-selfcheck";
|
|
18833
|
+
readonly description: "DM the person signed in here when a sync engine unit has been held or failing for over 30 minutes (at most daily per unit set)";
|
|
18834
|
+
readonly aliases: readonly [];
|
|
18835
|
+
readonly hidden: false;
|
|
18836
|
+
readonly helpOption: true;
|
|
18837
|
+
readonly helpCommand: false;
|
|
18838
|
+
readonly usage: "[options]";
|
|
18839
|
+
readonly helpText: readonly [];
|
|
18840
|
+
readonly arguments: readonly [];
|
|
18841
|
+
readonly options: readonly [];
|
|
18842
|
+
readonly subcommands: readonly [];
|
|
18819
18843
|
}, {
|
|
18820
18844
|
readonly name: "sync-core-manifest";
|
|
18821
18845
|
readonly description: "Upload the sync-audit manifest for every vault the new sync engine owns on this machine";
|
|
@@ -22248,6 +22248,19 @@ export const COMMAND_CATALOG = [
|
|
|
22248
22248
|
],
|
|
22249
22249
|
"subcommands": []
|
|
22250
22250
|
},
|
|
22251
|
+
{
|
|
22252
|
+
"name": "start",
|
|
22253
|
+
"description": "Load the daemon's OS unit if the service manager does not have it, and start it",
|
|
22254
|
+
"aliases": [],
|
|
22255
|
+
"hidden": false,
|
|
22256
|
+
"helpOption": true,
|
|
22257
|
+
"helpCommand": false,
|
|
22258
|
+
"usage": "[options]",
|
|
22259
|
+
"helpText": [],
|
|
22260
|
+
"arguments": [],
|
|
22261
|
+
"options": [],
|
|
22262
|
+
"subcommands": []
|
|
22263
|
+
},
|
|
22251
22264
|
{
|
|
22252
22265
|
"name": "uninstall",
|
|
22253
22266
|
"description": "Stop the daemon and remove its OS unit",
|
|
@@ -22941,6 +22954,19 @@ export const COMMAND_CATALOG = [
|
|
|
22941
22954
|
],
|
|
22942
22955
|
"subcommands": []
|
|
22943
22956
|
},
|
|
22957
|
+
{
|
|
22958
|
+
"name": "sync-core-selfcheck",
|
|
22959
|
+
"description": "DM the person signed in here when a sync engine unit has been held or failing for over 30 minutes (at most daily per unit set)",
|
|
22960
|
+
"aliases": [],
|
|
22961
|
+
"hidden": false,
|
|
22962
|
+
"helpOption": true,
|
|
22963
|
+
"helpCommand": false,
|
|
22964
|
+
"usage": "[options]",
|
|
22965
|
+
"helpText": [],
|
|
22966
|
+
"arguments": [],
|
|
22967
|
+
"options": [],
|
|
22968
|
+
"subcommands": []
|
|
22969
|
+
},
|
|
22944
22970
|
{
|
|
22945
22971
|
"name": "sync-core-manifest",
|
|
22946
22972
|
"description": "Upload the sync-audit manifest for every vault the new sync engine owns on this machine",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasCodexCreditRoom } from "../lib/capacity/codex-availability.js";
|
|
1
2
|
import * as crypto from "node:crypto";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as os from "node:os";
|
|
@@ -98,7 +99,10 @@ export function formatCapacityTable(view, nowMs, accounts = []) {
|
|
|
98
99
|
return view.providers.map((provider) => {
|
|
99
100
|
const head = `${provider.provider} ${provider.state}`;
|
|
100
101
|
if (!provider.accounts || provider.accounts.length === 0) {
|
|
101
|
-
|
|
102
|
+
const creditNote = provider.provider === "codex" && provider.state === "ok"
|
|
103
|
+
&& provider.limits.some((limit) => limit.state === "exhausted")
|
|
104
|
+
? " — usable on Codex credits" : "";
|
|
105
|
+
return `${head}${creditNote}\n${formatLimitTable(provider.limits, nowMs)}`;
|
|
102
106
|
}
|
|
103
107
|
const blocks = provider.accounts.map((account) => {
|
|
104
108
|
const labels = account.labels.map((label) => {
|
|
@@ -112,7 +116,11 @@ export function formatCapacityTable(view, nowMs, accounts = []) {
|
|
|
112
116
|
const fix = presented.command ? formatRefreshLine(provider.provider, presented.command) : "";
|
|
113
117
|
return fix ? `${title}\n${reason}\n${fix}` : `${title}\n${reason}`;
|
|
114
118
|
}
|
|
115
|
-
|
|
119
|
+
const creditNote = provider.provider === "codex"
|
|
120
|
+
&& account.limits.some((limit) => limit.state === "exhausted")
|
|
121
|
+
&& hasCodexCreditRoom(account.limits, nowMs)
|
|
122
|
+
? " — usable on Codex credits (ranked last)" : "";
|
|
123
|
+
return `${title}${creditNote}\n${formatLimitTable(account.limits, nowMs)}`;
|
|
116
124
|
});
|
|
117
125
|
return [head, ...blocks].join("\n");
|
|
118
126
|
}).join("\n\n");
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* hq daemon status (same as `hq daemon status`)
|
|
5
5
|
* hq daemon install detect the machine type, install the one OS unit
|
|
6
6
|
* hq daemon run what the OS unit runs; starts the default services
|
|
7
|
-
* hq daemon
|
|
7
|
+
* hq daemon start load the OS unit if needed and start it
|
|
8
|
+
* hq daemon status [--json] includes whether the service manager runs it
|
|
8
9
|
* hq daemon logs [unit] [--follow] [--lines N]
|
|
9
10
|
* hq daemon restart [unit] | trigger <unit>
|
|
10
11
|
* hq daemon enable|disable <service...> per-machine overrides
|
package/dist/commands/daemon.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* hq daemon status (same as `hq daemon status`)
|
|
5
5
|
* hq daemon install detect the machine type, install the one OS unit
|
|
6
6
|
* hq daemon run what the OS unit runs; starts the default services
|
|
7
|
-
* hq daemon
|
|
7
|
+
* hq daemon start load the OS unit if needed and start it
|
|
8
|
+
* hq daemon status [--json] includes whether the service manager runs it
|
|
8
9
|
* hq daemon logs [unit] [--follow] [--lines N]
|
|
9
10
|
* hq daemon restart [unit] | trigger <unit>
|
|
10
11
|
* hq daemon enable|disable <service...> per-machine overrides
|
|
@@ -24,7 +25,8 @@ import { DEFAULT_SERVICES, SERVICE_DESCRIPTIONS, enabledServices, hqCloudUnits }
|
|
|
24
25
|
import { applyServiceToggle, effectiveSyncOwner, readDaemonConfig, writeDaemonConfig } from "../lib/daemon/config.js";
|
|
25
26
|
import { daemonUnitInstalled } from "../lib/daemon/handoff.js";
|
|
26
27
|
import { adoptLegacyUnits, readAdoptedUnits, restoreLegacyUnits } from "../lib/daemon/migrate.js";
|
|
27
|
-
import { ROOT_REFUSAL, SYSTEM_UNIT_DIR, WINDOWS_START_WAIT_MS, daemonRootRefusal, installDaemonUnit, installScopeFor, classifyWindowsTask, legacyWindowsTask, readWindowsSid, uninstallDaemonUnit, windowsInteractiveSession, windowsTaskName, } from "../lib/daemon/install.js";
|
|
28
|
+
import { ROOT_REFUSAL, SYSTEM_UNIT_DIR, WINDOWS_START_WAIT_MS, daemonRootRefusal, installDaemonUnit, installScopeFor, classifyWindowsTask, daemonServiceStatus, describeSupervision, legacyWindowsTask, startDaemonUnit, readWindowsSid, uninstallDaemonUnit, windowsInteractiveSession, windowsTaskName, } from "../lib/daemon/install.js";
|
|
29
|
+
import { PLIST_REFRESH_MARKER } from "../lib/daemon/plist-refresh.js";
|
|
28
30
|
import { describeLegacyUnit, findLegacyUnits } from "../lib/daemon/legacy.js";
|
|
29
31
|
import { isDaemonProcess, liveDaemonLock, runningDaemonPid } from "../lib/daemon/lock.js";
|
|
30
32
|
import { bundledHqCloud, managedHqCloud, managedHqCloudRoot, readHqCloudCopy, readHqCloudUpdateState } from "../lib/daemon/hq-cloud.js";
|
|
@@ -438,28 +440,126 @@ function formatUnit(u) {
|
|
|
438
440
|
const restarts = u.restarts > 0 ? ` (restarted ${u.restarts}x)` : "";
|
|
439
441
|
return ` ${u.id.padEnd(40)} ${u.status.padEnd(12)} ${detail}${restarts}`;
|
|
440
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* True when the daemon holding the lock is the one the desktop app hosts.
|
|
445
|
+
* state.json outlives the daemon that wrote it, and a new daemon writes its own
|
|
446
|
+
* only after starting up, so a `host: "desktop"` left by an earlier app-hosted
|
|
447
|
+
* daemon counts only when its pid is the lock holder's.
|
|
448
|
+
*/
|
|
449
|
+
function hostedByDesktop(state, lockPid) {
|
|
450
|
+
return lockPid !== null && state?.host === "desktop" && state.pid === lockPid;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* The service manager's warnings, plus where the automatic plist rewrite
|
|
454
|
+
* logged when it was tried and the plist still needs it.
|
|
455
|
+
*/
|
|
456
|
+
function statusWarnings(service, paths) {
|
|
457
|
+
const warnings = [...(service.warnings ?? [])];
|
|
458
|
+
if (warnings.length > 0 && fs.existsSync(path.join(paths.root, PLIST_REFRESH_MARKER))) {
|
|
459
|
+
warnings.push(`The daemon tried to rewrite it automatically and did not finish. See: ${path.join(paths.root, "logs", "plist-refresh.log")}`);
|
|
460
|
+
}
|
|
461
|
+
return warnings;
|
|
462
|
+
}
|
|
441
463
|
async function runStatus(d, opts) {
|
|
442
464
|
const host = currentHost(d);
|
|
443
465
|
const { paths } = host;
|
|
444
466
|
const pid = runningDaemonPid(paths.lockPath, d.daemonAlive);
|
|
445
467
|
const state = pid ? readState(paths) : null;
|
|
446
468
|
const config = readDaemonConfig(paths.configPath);
|
|
469
|
+
// The pid lock alone says nothing about whether the daemon comes back after
|
|
470
|
+
// a crash or a login: ask the service manager too.
|
|
471
|
+
const service = daemonServiceStatus(hostFor(d, paths, false), d.run, pid);
|
|
472
|
+
// A daemon hosted by the desktop app is supervised by the app, not the OS.
|
|
473
|
+
const hosted = hostedByDesktop(state, pid);
|
|
447
474
|
if (opts.json) {
|
|
448
|
-
d.out(JSON.stringify({
|
|
475
|
+
d.out(JSON.stringify({
|
|
476
|
+
running: pid !== null,
|
|
477
|
+
pid,
|
|
478
|
+
supervision: hosted && service.supervision === "unmanaged" ? "desktop-app" : service.supervision,
|
|
479
|
+
serviceManager: {
|
|
480
|
+
manager: service.manager,
|
|
481
|
+
unitInstalled: service.unitInstalled,
|
|
482
|
+
loaded: service.loaded,
|
|
483
|
+
active: service.active,
|
|
484
|
+
pid: service.managerPid ?? null,
|
|
485
|
+
detail: service.detail,
|
|
486
|
+
},
|
|
487
|
+
warnings: statusWarnings(service, paths),
|
|
488
|
+
machineType: state?.machineType ?? config.machineType ?? null,
|
|
489
|
+
units: state?.units ?? [],
|
|
490
|
+
}, null, 2));
|
|
449
491
|
return;
|
|
450
492
|
}
|
|
493
|
+
const managerLine = hosted && service.supervision === "unmanaged"
|
|
494
|
+
? `Service manager: running as the HQ desktop app's child (${service.detail}).`
|
|
495
|
+
: `Service manager: ${describeSupervision(service)}.`;
|
|
496
|
+
const warnings = statusWarnings(service, paths).map((w) => `Warning: ${w}`);
|
|
451
497
|
if (!pid || !state) {
|
|
452
498
|
const { detected } = host;
|
|
453
499
|
d.out(`hq daemon is not running. Machine type: ${detected.type} (${detected.detail}).`);
|
|
500
|
+
d.out(managerLine);
|
|
501
|
+
for (const line of warnings)
|
|
502
|
+
d.out(line);
|
|
454
503
|
d.out("Default services for this machine:");
|
|
455
504
|
for (const line of describeServices(detected.type, config))
|
|
456
505
|
d.out(line);
|
|
457
506
|
return;
|
|
458
507
|
}
|
|
459
508
|
d.out(`hq daemon is running (pid ${pid}) on a ${state.machineType} machine.`);
|
|
509
|
+
d.out(managerLine);
|
|
510
|
+
for (const line of warnings)
|
|
511
|
+
d.out(line);
|
|
460
512
|
for (const u of state.units)
|
|
461
513
|
d.out(formatUnit(u));
|
|
462
514
|
}
|
|
515
|
+
async function runStart(d) {
|
|
516
|
+
const host = currentHost(d);
|
|
517
|
+
const { paths } = host;
|
|
518
|
+
const pid = runningDaemonPid(paths.lockPath, d.daemonAlive);
|
|
519
|
+
// The desktop app hosts this daemon and restarts it itself. Loading the OS
|
|
520
|
+
// unit now would start a second daemon that exits on the lock and is
|
|
521
|
+
// respawned until the app quits.
|
|
522
|
+
if (hostedByDesktop(readState(paths), pid)) {
|
|
523
|
+
d.out(`hq daemon is running (pid ${pid}) inside the desktop app, which restarts it. ` +
|
|
524
|
+
"Nothing to start. To run it under the service manager instead, quit the app, then run: hq daemon start");
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const before = daemonServiceStatus(hostFor(d, paths, false), d.run, pid);
|
|
528
|
+
if (!before.unitInstalled && before.manager !== "none") {
|
|
529
|
+
// Nothing to load: install writes the unit, loads it and starts it.
|
|
530
|
+
d.out("hq daemon is not installed here yet; installing it.");
|
|
531
|
+
await runInstall(d, {});
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const result = startDaemonUnit(hostFor(d, paths, false), {
|
|
535
|
+
run: d.run,
|
|
536
|
+
sleep: d.sleep,
|
|
537
|
+
daemonPid: () => runningDaemonPid(paths.lockPath, d.daemonAlive),
|
|
538
|
+
});
|
|
539
|
+
handOver(d, host);
|
|
540
|
+
const refresh = result.refresh;
|
|
541
|
+
if (refresh?.action === "rewritten" || refresh?.action === "reloaded") {
|
|
542
|
+
d.out(`Rewrote ${refresh.plistPath} so launchd loads the daemon at login without HQ.app.`);
|
|
543
|
+
}
|
|
544
|
+
if (refresh?.error)
|
|
545
|
+
d.err(`warning: ${refresh.error}`);
|
|
546
|
+
if (result.action === "already-running") {
|
|
547
|
+
d.out(`hq daemon is already running (pid ${result.status.daemonPid}) under the service manager.`);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (result.status.supervision !== "managed" || result.status.daemonPid === null) {
|
|
551
|
+
const what = result.action === "loaded"
|
|
552
|
+
? "Loaded the daemon's unit"
|
|
553
|
+
: result.action === "reloaded"
|
|
554
|
+
? "Reloaded the daemon's unit"
|
|
555
|
+
: "Asked the service manager to start the daemon";
|
|
556
|
+
const why = result.status.supervision === "managed"
|
|
557
|
+
? `no daemon took the lock within 15 seconds (${result.status.detail})`
|
|
558
|
+
: `it is ${describeSupervision(result.status)}`;
|
|
559
|
+
throw new DaemonCommandError(`${what}, but ${why}. See: hq daemon logs`);
|
|
560
|
+
}
|
|
561
|
+
d.out(`${result.action === "loaded" ? "Loaded and started" : result.action === "reloaded" ? "Reloaded and started" : "Started"} hq daemon (pid ${result.status.daemonPid}) under the service manager.`);
|
|
562
|
+
}
|
|
463
563
|
function tailLines(file, lines) {
|
|
464
564
|
const text = fs.readFileSync(file, "utf8");
|
|
465
565
|
return text.split("\n").filter((l, i, all) => i < all.length - 1 || l !== "").slice(-lines);
|
|
@@ -886,7 +986,26 @@ async function runMigrate(d, opts) {
|
|
|
886
986
|
if (!installed) {
|
|
887
987
|
throw new DaemonCommandError("hq daemon is not installed here yet. Run `hq daemon install` first, then `hq daemon migrate --apply`.");
|
|
888
988
|
}
|
|
889
|
-
|
|
989
|
+
// The daemon starts the adopted services, so its own unit must be loaded
|
|
990
|
+
// before the older units are booted out (an installed but unloaded plist
|
|
991
|
+
// would leave every adopted service off until the next manual start).
|
|
992
|
+
const ensureDaemonLoaded = (when) => {
|
|
993
|
+
const daemonPid = () => runningDaemonPid(host.paths.lockPath, d.daemonAlive);
|
|
994
|
+
const status = daemonServiceStatus(hostFor(d, host.paths, false), d.run, daemonPid());
|
|
995
|
+
if (status.loaded || status.manager === "none")
|
|
996
|
+
return;
|
|
997
|
+
try {
|
|
998
|
+
startDaemonUnit(hostFor(d, host.paths, false), { run: d.run, sleep: d.sleep, daemonPid, waitMs: 0 });
|
|
999
|
+
d.out(`Loaded the daemon's unit ${when}.`);
|
|
1000
|
+
}
|
|
1001
|
+
catch (err) {
|
|
1002
|
+
throw new DaemonCommandError(`The daemon's unit is not loaded and could not be loaded ${when}: ${err.message}`);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
ensureDaemonLoaded("before taking over the older units");
|
|
1006
|
+
const result = adoptLegacyUnits(units, { ...migrateDeps(d, host.paths, host.runAsUser), sleep: d.sleep });
|
|
1007
|
+
if (result.adopted.length > 0)
|
|
1008
|
+
ensureDaemonLoaded("after taking over the older units");
|
|
890
1009
|
handOver(d, host);
|
|
891
1010
|
for (const a of result.adopted)
|
|
892
1011
|
d.out(`Took over ${a.unitId} from ${describeLegacyUnit(a)}`);
|
|
@@ -1306,6 +1425,68 @@ async function runServiceCodexRemoteControl(d, opts) {
|
|
|
1306
1425
|
watch: codex,
|
|
1307
1426
|
});
|
|
1308
1427
|
}
|
|
1428
|
+
/**
|
|
1429
|
+
* DM the person signed in on this machine (the machine owner) through the
|
|
1430
|
+
* same route as `hq dm`. A machine identity (agent box) has no person to tell.
|
|
1431
|
+
*/
|
|
1432
|
+
/**
|
|
1433
|
+
* Which engine vaults the self-check reports, and their names: a vault skipped
|
|
1434
|
+
* in ~/.hq/sync-core/skip.json or not on the rollout recorded in rollout.json
|
|
1435
|
+
* is kept on the old sync on purpose, so a hold left over for it is not an outage.
|
|
1436
|
+
*/
|
|
1437
|
+
async function selfCheckVaults(home) {
|
|
1438
|
+
const [{ readSyncCoreSkips }, { readSyncCoreRolloutState }] = await Promise.all([
|
|
1439
|
+
import("../lib/sync-core/skip.js"),
|
|
1440
|
+
import("../lib/sync-core/rollout-state.js"),
|
|
1441
|
+
]);
|
|
1442
|
+
let skipped = new Set();
|
|
1443
|
+
try {
|
|
1444
|
+
skipped = new Set(readSyncCoreSkips(home).map((s) => s.uid));
|
|
1445
|
+
}
|
|
1446
|
+
catch {
|
|
1447
|
+
// An unreadable skip list skips nothing here; the reconcile reports it.
|
|
1448
|
+
}
|
|
1449
|
+
let rollout = null;
|
|
1450
|
+
try {
|
|
1451
|
+
rollout = readSyncCoreRolloutState(home);
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
// No rollout file: every vault counts.
|
|
1455
|
+
}
|
|
1456
|
+
const onRollout = rollout ? new Map(rollout.vaults.map((v) => [v.uid, v])) : null;
|
|
1457
|
+
return {
|
|
1458
|
+
wanted: (uid) => !skipped.has(uid) && (onRollout === null || onRollout.has(uid)),
|
|
1459
|
+
vaultName: (uid) => {
|
|
1460
|
+
const v = onRollout?.get(uid);
|
|
1461
|
+
if (!v)
|
|
1462
|
+
return undefined;
|
|
1463
|
+
return v.kind === "personal" ? "your personal vault" : v.slug;
|
|
1464
|
+
},
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
async function sendDmToMachineOwner(message) {
|
|
1468
|
+
const [{ ensureCognitoToken }, { agentUidFromToken, resolveCallerPersonUid, vaultApiFetch }] = await Promise.all([
|
|
1469
|
+
import("../utils/cognito-session.js"),
|
|
1470
|
+
import("../utils/vault-api.js"),
|
|
1471
|
+
]);
|
|
1472
|
+
const token = await ensureCognitoToken();
|
|
1473
|
+
// An agent box has no person signed in, and the server does not deliver a
|
|
1474
|
+
// DM an agent sends itself: there is no owner to reach from here.
|
|
1475
|
+
if (agentUidFromToken(token))
|
|
1476
|
+
return null;
|
|
1477
|
+
const personUid = await resolveCallerPersonUid(token, undefined, { command: "hq daemon service sync-core-selfcheck" });
|
|
1478
|
+
const res = await vaultApiFetch({
|
|
1479
|
+
token,
|
|
1480
|
+
path: "/v1/notify/dm",
|
|
1481
|
+
method: "POST",
|
|
1482
|
+
body: { toPersonUid: personUid, body: message.body, details: message.details },
|
|
1483
|
+
});
|
|
1484
|
+
if (!res.ok) {
|
|
1485
|
+
const err = (await res.json().catch(() => ({})));
|
|
1486
|
+
throw new DaemonCommandError(`could not send the self-check DM: HTTP ${res.status} ${err.code ?? ""} ${err.error ?? err.message ?? ""}`.trim());
|
|
1487
|
+
}
|
|
1488
|
+
return personUid;
|
|
1489
|
+
}
|
|
1309
1490
|
export function registerDaemonCommand(program, over = {}) {
|
|
1310
1491
|
let resolved = null;
|
|
1311
1492
|
const deps = () => (resolved ??= loadDefaults(over));
|
|
@@ -1330,6 +1511,10 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
1330
1511
|
.option("--hq-root <path>", "HQ folder the services work against")
|
|
1331
1512
|
.option("--user <login>", "on a box: the login user the daemon runs as (never root)")
|
|
1332
1513
|
.action(wrap((d, opts) => runInstall(d, opts)));
|
|
1514
|
+
daemon
|
|
1515
|
+
.command("start")
|
|
1516
|
+
.description("Load the daemon's OS unit if the service manager does not have it, and start it")
|
|
1517
|
+
.action(wrap((d) => runStart(d)));
|
|
1333
1518
|
daemon
|
|
1334
1519
|
.command("uninstall")
|
|
1335
1520
|
.description("Stop the daemon and remove its OS unit")
|
|
@@ -1601,6 +1786,42 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
1601
1786
|
err: d.err,
|
|
1602
1787
|
});
|
|
1603
1788
|
}));
|
|
1789
|
+
service
|
|
1790
|
+
.command("sync-core-selfcheck")
|
|
1791
|
+
.description("DM the person signed in here when a sync engine unit has been held or failing for over 30 minutes (at most daily per unit set)")
|
|
1792
|
+
.action(wrap(async (d) => {
|
|
1793
|
+
const [{ runSelfCheck }, { readUnitHolds }] = await Promise.all([
|
|
1794
|
+
import("../lib/daemon/selfcheck.js"),
|
|
1795
|
+
import("../lib/daemon/unit-holds.js"),
|
|
1796
|
+
]);
|
|
1797
|
+
const { paths } = currentHost(d);
|
|
1798
|
+
await runSelfCheck({
|
|
1799
|
+
statePath: paths.statePath,
|
|
1800
|
+
holdsPath: paths.holdsPath,
|
|
1801
|
+
recordPath: path.join(paths.root, "selfcheck.json"),
|
|
1802
|
+
now: Date.now,
|
|
1803
|
+
machine: os.hostname(),
|
|
1804
|
+
readState: () => {
|
|
1805
|
+
try {
|
|
1806
|
+
return readState(paths);
|
|
1807
|
+
}
|
|
1808
|
+
catch {
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1811
|
+
},
|
|
1812
|
+
readHolds: () => {
|
|
1813
|
+
try {
|
|
1814
|
+
return readUnitHolds(paths.holdsPath);
|
|
1815
|
+
}
|
|
1816
|
+
catch {
|
|
1817
|
+
return {};
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
sendToOwner: (message) => sendDmToMachineOwner(message),
|
|
1821
|
+
...(await selfCheckVaults(d.home)),
|
|
1822
|
+
log: (line) => d.out(line),
|
|
1823
|
+
});
|
|
1824
|
+
}));
|
|
1604
1825
|
service
|
|
1605
1826
|
.command("sync-core-manifest")
|
|
1606
1827
|
.description("Upload the sync-audit manifest for every vault the new sync engine owns on this machine")
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Sentry live in exactly one place. `integrations.ts` re-exports this module's
|
|
9
9
|
* public surface, so existing importers keep working unchanged.
|
|
10
10
|
*/
|
|
11
|
+
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
11
12
|
export { bareProvider } from "../lib/integrations/provider-slug.js";
|
|
12
13
|
/** Write policy on a connection — mirrors hq-pro's `WritePolicy`. */
|
|
13
14
|
export type WritePolicy = "auto-allow" | "confirm" | "deny";
|
|
@@ -415,4 +416,6 @@ export declare function selectAccountConnections(connections: readonly AdminConn
|
|
|
415
416
|
account: string;
|
|
416
417
|
provider?: string;
|
|
417
418
|
}): AdminConnection[];
|
|
419
|
+
/** A runner-selected session must never fall back to a cached human identity. */
|
|
420
|
+
export declare function ensureIntegrationIdToken(options?: Parameters<typeof ensureCognitoIdToken>[0]): Promise<string>;
|
|
418
421
|
//# sourceMappingURL=integrations-core.d.ts.map
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* public surface, so existing importers keep working unchanged.
|
|
10
10
|
*/
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { delegatedMachineSessionFilePath, loadDelegatedMachineSession, getValidMachineTokens } from "@indigoai-us/hq-cloud";
|
|
13
|
+
import { DEFAULT_COGNITO, ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
12
14
|
import chalk from "chalk";
|
|
13
15
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
14
16
|
import { AuthError } from "../utils/auth-error.js";
|
|
@@ -991,4 +993,13 @@ export function selectAccountConnections(connections, opts) {
|
|
|
991
993
|
}
|
|
992
994
|
return matches;
|
|
993
995
|
}
|
|
996
|
+
/** A runner-selected session must never fall back to a cached human identity. */
|
|
997
|
+
export async function ensureIntegrationIdToken(options) {
|
|
998
|
+
if (delegatedMachineSessionFilePath() !== null) {
|
|
999
|
+
if (!loadDelegatedMachineSession())
|
|
1000
|
+
throw new Error("HQ_MACHINE_SESSION_FILE is not a usable delegated session; the job runner must rewrite it.");
|
|
1001
|
+
return (await getValidMachineTokens(DEFAULT_COGNITO)).idToken;
|
|
1002
|
+
}
|
|
1003
|
+
return options === undefined ? ensureCognitoIdToken() : ensureCognitoIdToken(options);
|
|
1004
|
+
}
|
|
994
1005
|
//# sourceMappingURL=integrations-core.js.map
|
|
@@ -71,10 +71,10 @@ export function parsePrincipal(raw) {
|
|
|
71
71
|
return { granteeType: "group", granteeId: value };
|
|
72
72
|
// Agents are people to the ACL: they hold an identity and a membership, so a
|
|
73
73
|
// grant to `agt_…` is a person grant, not a separate grantee kind.
|
|
74
|
-
if (/^(prs|psn|agt)_[A-Za-z0-9_-]+$/i.test(value)) {
|
|
74
|
+
if (/^(prs|psn|agt|out)_[A-Za-z0-9_-]+$/i.test(value)) {
|
|
75
75
|
return { granteeType: "person", granteeId: value };
|
|
76
76
|
}
|
|
77
|
-
throw new IntegrationsCliError(`Could not tell what '${raw}' refers to. Use an email address, a uid (prs_… / agt_… / grp_…), \`everyone\`, or an explicit \`person:<id>\` / \`group:<id>\` form.`, { expected: true });
|
|
77
|
+
throw new IntegrationsCliError(`Could not tell what '${raw}' refers to. Use an email address, a uid (prs_… / agt_… / out_… / grp_…), \`everyone\`, or an explicit \`person:<id>\` / \`group:<id>\` form.`, { expected: true });
|
|
78
78
|
}
|
|
79
79
|
/** `--with` / `--to` / `--from` help. Groups resolve membership at call time. */
|
|
80
80
|
export const PRINCIPAL_HELP = "Email, uid, vault group (grp_…), or `everyone`";
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
import { randomUUID } from "node:crypto";
|
|
54
54
|
import { readFile } from "node:fs/promises";
|
|
55
55
|
import chalk from "chalk";
|
|
56
|
-
import {
|
|
56
|
+
import { ensureIntegrationIdToken } from "./integrations-core.js";
|
|
57
57
|
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
58
58
|
import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
|
|
59
59
|
import { IntegrationsCliError, bareProvider, callGateway, parseCallTimeoutMs, fetchConnections, fetchUsableApps, isBuiltInGatewayProvider, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, connectionAccountLabel, connectionOwnerLabel, selectAccountConnections, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, accountCandidateFor, accountSelectionMessage, ACCOUNT_REQUIRED_CODE, } from "./integrations-core.js";
|
|
@@ -361,7 +361,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
361
361
|
.option("--usable", "Only apps you can use right now (connected and shared with you), with the --provider value to call them")
|
|
362
362
|
.option("--no-login", "Fail instead of opening a browser sign-in when there is no HQ session")
|
|
363
363
|
.action(async (opts) => {
|
|
364
|
-
const token = await
|
|
364
|
+
const token = await ensureIntegrationIdToken(opts.login === false ? { interactive: false } : {});
|
|
365
365
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
366
366
|
if (opts.usable) {
|
|
367
367
|
const result = await fetchUsableApps(token, companyUid);
|
|
@@ -424,7 +424,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
424
424
|
.option("--describe <tool>", "Print the full input schema for one tool")
|
|
425
425
|
.option("--json", "Machine-readable output")
|
|
426
426
|
.action(async (opts) => {
|
|
427
|
-
const token = await
|
|
427
|
+
const token = await ensureIntegrationIdToken();
|
|
428
428
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
429
429
|
const connections = await fetchConnections(token, companyUid);
|
|
430
430
|
const { connection, accountMatches } = selectToolConnection(connections, opts);
|
|
@@ -516,7 +516,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
516
516
|
.action(async (tool, opts) => {
|
|
517
517
|
const waitMs = parseCallTimeoutMs(opts.timeout);
|
|
518
518
|
const parsedArgs = await resolveCallArgs(opts);
|
|
519
|
-
const token = await
|
|
519
|
+
const token = await ensureIntegrationIdToken();
|
|
520
520
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
521
521
|
const { connection, accountMatches } = selectToolConnection(await fetchConnections(token, companyUid), opts);
|
|
522
522
|
if (reportRevokedConnection(connection, { ...opts, fail: true }))
|
|
@@ -605,7 +605,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
605
605
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
606
606
|
.option("--json", "Machine-readable output")
|
|
607
607
|
.action(async (queueId, opts) => {
|
|
608
|
-
const token = await
|
|
608
|
+
const token = await ensureIntegrationIdToken();
|
|
609
609
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
610
610
|
const connection = selectConnection(await fetchConnections(token, companyUid), opts);
|
|
611
611
|
const res = await vaultApiFetch({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { ensureSyncCoreBinary } from "../lib/sync-core/release.js";
|
|
4
|
+
import { ensureSyncCoreBinary, readSyncCorePin } from "../lib/sync-core/release.js";
|
|
5
5
|
import { readCompanyRolloutFlag } from "../lib/sync-core/flag.js";
|
|
6
6
|
import { reconcileSyncCore } from "../lib/sync-core/reconcile.js";
|
|
7
7
|
import { engineOwnedManifestScopes, manifestScopeArg } from "../lib/sync-core/manifest-scopes.js";
|
|
@@ -59,6 +59,7 @@ export async function runSyncCoreReconcile(opts, d = defaultDeps()) {
|
|
|
59
59
|
}),
|
|
60
60
|
readRolloutFlag: (companyUid) => readCompanyRolloutFlag(companyUid, token, { env: d.env }),
|
|
61
61
|
ensureBinary: (release) => ensureSyncCoreBinary({ home: d.home, platform: d.platform, arch: d.arch, release, env: d.env }),
|
|
62
|
+
readPin: () => readSyncCorePin({ env: d.env }),
|
|
62
63
|
log: opts.json ? () => { } : d.out,
|
|
63
64
|
});
|
|
64
65
|
if (opts.json)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BotConfig } from "./config.js";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function executiveEnabled(home?: string): boolean;
|
|
3
3
|
export declare function isExecutiveBot(config: Pick<BotConfig, "name" | "kind" | "workerId">, home?: string): boolean;
|
|
4
4
|
export declare function executiveStatus(config: Pick<BotConfig, "name" | "kind" | "workerId">, dir: string, home?: string): {
|
|
5
5
|
is_executive: boolean;
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
* Shared Executive identity and its opt-in switch.
|
|
3
3
|
*
|
|
4
4
|
* The Executive (meeting rider, signal-to-lane, source report DMs) is off
|
|
5
|
-
* unless ~/.hq/menubar.json sets
|
|
6
|
-
* stays internal until the live end-to-end proof passes.
|
|
5
|
+
* unless ~/.hq/menubar.json sets executive.enabled to true. It
|
|
6
|
+
* stays internal until the live end-to-end proof passes. The older
|
|
7
|
+
* executiveMeetingRider key is ignored whatever its value: the desktop beta
|
|
8
|
+
* saved executiveMeetingRider.enabled: true for users who never chose it.
|
|
7
9
|
*/
|
|
8
10
|
import * as fs from "node:fs";
|
|
9
11
|
import * as path from "node:path";
|
|
10
12
|
import * as os from "node:os";
|
|
11
13
|
import { effectiveBotKind } from "./config.js";
|
|
12
14
|
import { tailBotEvents } from "./events.js";
|
|
13
|
-
export function
|
|
15
|
+
export function executiveEnabled(home = os.homedir()) {
|
|
14
16
|
try {
|
|
15
17
|
const settings = JSON.parse(fs.readFileSync(path.join(home, ".hq/menubar.json"), "utf8"));
|
|
16
|
-
return settings?.
|
|
18
|
+
return settings?.executive?.enabled === true;
|
|
17
19
|
}
|
|
18
20
|
catch {
|
|
19
21
|
return false;
|
|
@@ -22,7 +24,7 @@ export function executiveRiderEnabled(home = os.homedir()) {
|
|
|
22
24
|
export function isExecutiveBot(config, home = os.homedir()) {
|
|
23
25
|
if (effectiveBotKind(config) !== "personal")
|
|
24
26
|
return false;
|
|
25
|
-
if (!
|
|
27
|
+
if (!executiveEnabled(home))
|
|
26
28
|
return false;
|
|
27
29
|
let name = "setup";
|
|
28
30
|
try {
|
|
@@ -42,7 +44,7 @@ export function isExecutiveBot(config, home = os.homedir()) {
|
|
|
42
44
|
export function executiveStatus(config, dir, home = os.homedir()) {
|
|
43
45
|
const last = tailBotEvents(dir, (event) => event.event === "meeting.detected").at(-1);
|
|
44
46
|
return {
|
|
45
|
-
is_executive: isExecutiveBot(config, home), rider_enabled:
|
|
47
|
+
is_executive: isExecutiveBot(config, home), rider_enabled: executiveEnabled(home),
|
|
46
48
|
// The first desktop producer did not set source.
|
|
47
49
|
detect_source_last: last ? (last.payload.source === "poll" ? "poll" : "desktop") : null,
|
|
48
50
|
last_detected_at: last?.at ?? null,
|
|
@@ -9,7 +9,7 @@ import { getCompanyUid } from "../../../utils/vault-api.js";
|
|
|
9
9
|
import { assertSafeSegment } from "../../hot-channels/landing.js";
|
|
10
10
|
import { readManifestCompanies } from "../company-bind.js";
|
|
11
11
|
import { emitBotEvent } from "../events.js";
|
|
12
|
-
import {
|
|
12
|
+
import { executiveEnabled, isExecutiveBot } from "../executive-settings.js";
|
|
13
13
|
const RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
14
14
|
const ACTIVE = new Set(["joining", "recording", "in_call"]);
|
|
15
15
|
export function detectIntervalSeconds(hqRoot) {
|
|
@@ -161,7 +161,7 @@ async function untilAborted(signal, work) {
|
|
|
161
161
|
export function defaultMeetingDetectDeps(config, dir, log) {
|
|
162
162
|
return {
|
|
163
163
|
hqRoot: config.hqRoot, dir, log,
|
|
164
|
-
enabled: () => isExecutiveBot(config) &&
|
|
164
|
+
enabled: () => isExecutiveBot(config) && executiveEnabled(),
|
|
165
165
|
companies: () => config.companySlug ? [config.companySlug] : readManifestCompanies(config.hqRoot),
|
|
166
166
|
intervalSeconds: () => detectIntervalSeconds(config.hqRoot),
|
|
167
167
|
list: async (company, signal) => {
|
|
@@ -12,7 +12,7 @@ import { meetingSignalSourceRef, cursorPath } from "../../signals/incremental.js
|
|
|
12
12
|
import { readAllLanes } from "../../lanes/store.js";
|
|
13
13
|
import { runSignals } from "../../signals/engine.js";
|
|
14
14
|
import { emitBotEvent, EVENT_PAYLOAD_BYTES } from "../events.js";
|
|
15
|
-
import {
|
|
15
|
+
import { executiveEnabled } from "../executive-settings.js";
|
|
16
16
|
export { isExecutiveBot } from "../executive-settings.js";
|
|
17
17
|
import { readLocalJobsStrict, withJobsLock, writeLocalJobs } from "../local-jobs.js";
|
|
18
18
|
import { readBuiltinJobsMarker, writeBuiltinJobsMarker } from "./hot-signal-sync.js";
|
|
@@ -56,7 +56,7 @@ export function ensureMeetingRiderJob(dir) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
export function meetingRiderSettings(hqRoot, home = os.homedir()) {
|
|
59
|
-
const enabled =
|
|
59
|
+
const enabled = executiveEnabled(home);
|
|
60
60
|
let interval = 20;
|
|
61
61
|
try {
|
|
62
62
|
const settings = yaml.load(fs.readFileSync(path.join(hqRoot, "core/settings/signals.yaml"), "utf8"));
|