@indigoai-us/hq-cli 5.345.178 → 5.345.180

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 (117) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/command-catalog.generated.d.ts +197 -1
  3. package/dist/command-catalog.generated.js +241 -1
  4. package/dist/commands/bot-jobs.d.ts +11 -9
  5. package/dist/commands/bot-jobs.js +56 -21
  6. package/dist/commands/bot.d.ts +37 -1
  7. package/dist/commands/bot.js +289 -13
  8. package/dist/commands/channels.d.ts +18 -1
  9. package/dist/commands/channels.js +185 -1
  10. package/dist/commands/daemon.d.ts +2 -0
  11. package/dist/commands/daemon.js +15 -4
  12. package/dist/commands/integrations-core.d.ts +2 -0
  13. package/dist/commands/integrations-core.js +1 -0
  14. package/dist/commands/lanes.d.ts +1 -1
  15. package/dist/commands/lanes.js +6 -2
  16. package/dist/commands/mesh.js +19 -0
  17. package/dist/commands/sources.d.ts +89 -0
  18. package/dist/commands/sources.js +363 -1
  19. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2285 -1743
  20. package/dist/hq-cloud-sync-runner/package.json +1 -1
  21. package/dist/lib/bot/config.d.ts +8 -0
  22. package/dist/lib/bot/fleet-local.d.ts +1 -1
  23. package/dist/lib/bot/fleet-local.js +82 -30
  24. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +109 -0
  25. package/dist/lib/bot/jobs/hot-signal-sync.js +177 -0
  26. package/dist/lib/bot/local-jobs.d.ts +40 -4
  27. package/dist/lib/bot/local-jobs.js +72 -5
  28. package/dist/lib/bot/run.d.ts +15 -0
  29. package/dist/lib/bot/run.js +99 -1
  30. package/dist/lib/bot/runtime-auth.d.ts +119 -0
  31. package/dist/lib/bot/runtime-auth.js +251 -0
  32. package/dist/lib/daemon/catalog.d.ts +1 -0
  33. package/dist/lib/daemon/catalog.js +1 -1
  34. package/dist/lib/daemon/handoff.d.ts +4 -0
  35. package/dist/lib/daemon/handoff.js +6 -2
  36. package/dist/lib/daemon/install.d.ts +70 -2
  37. package/dist/lib/daemon/install.js +185 -27
  38. package/dist/lib/daemon/legacy.d.ts +9 -3
  39. package/dist/lib/daemon/legacy.js +11 -4
  40. package/dist/lib/daemon/migrate.js +1 -1
  41. package/dist/lib/daemon/run.d.ts +5 -2
  42. package/dist/lib/daemon/run.js +74 -22
  43. package/dist/lib/doctor/checks/anywhere.d.ts +6 -0
  44. package/dist/lib/doctor/checks/anywhere.js +35 -6
  45. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  46. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  47. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.d.ts +33 -0
  48. package/dist/lib/hot-channels/adapters/__fixtures__/dummy.js +53 -0
  49. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.d.ts +38 -0
  50. package/dist/lib/hot-channels/adapters/__fixtures__/meeting-providers.js +64 -0
  51. package/dist/lib/hot-channels/adapters/attio.d.ts +67 -0
  52. package/dist/lib/hot-channels/adapters/attio.js +374 -0
  53. package/dist/lib/hot-channels/adapters/gmail.d.ts +111 -0
  54. package/dist/lib/hot-channels/adapters/gmail.js +413 -0
  55. package/dist/lib/hot-channels/adapters/index.d.ts +23 -0
  56. package/dist/lib/hot-channels/adapters/index.js +39 -0
  57. package/dist/lib/hot-channels/adapters/linear.d.ts +79 -0
  58. package/dist/lib/hot-channels/adapters/linear.js +321 -0
  59. package/dist/lib/hot-channels/adapters/meeting-providers.d.ts +86 -0
  60. package/dist/lib/hot-channels/adapters/meeting-providers.js +253 -0
  61. package/dist/lib/hot-channels/adapters/meetings.d.ts +81 -0
  62. package/dist/lib/hot-channels/adapters/meetings.js +525 -0
  63. package/dist/lib/hot-channels/adapters/slack.d.ts +70 -0
  64. package/dist/lib/hot-channels/adapters/slack.js +277 -0
  65. package/dist/lib/hot-channels/alert.d.ts +78 -0
  66. package/dist/lib/hot-channels/alert.js +179 -0
  67. package/dist/lib/hot-channels/audience.d.ts +15 -0
  68. package/dist/lib/hot-channels/audience.js +25 -0
  69. package/dist/lib/hot-channels/contract.d.ts +146 -0
  70. package/dist/lib/hot-channels/contract.js +20 -0
  71. package/dist/lib/hot-channels/cursors.d.ts +41 -0
  72. package/dist/lib/hot-channels/cursors.js +84 -0
  73. package/dist/lib/hot-channels/index.d.ts +10 -0
  74. package/dist/lib/hot-channels/index.js +10 -0
  75. package/dist/lib/hot-channels/landing.d.ts +83 -0
  76. package/dist/lib/hot-channels/landing.js +175 -0
  77. package/dist/lib/hot-channels/ledger.d.ts +101 -0
  78. package/dist/lib/hot-channels/ledger.js +203 -0
  79. package/dist/lib/hot-channels/loop.d.ts +74 -0
  80. package/dist/lib/hot-channels/loop.js +221 -0
  81. package/dist/lib/hot-channels/prompts/signals-pass.md +22 -0
  82. package/dist/lib/hot-channels/registry.d.ts +28 -0
  83. package/dist/lib/hot-channels/registry.js +121 -0
  84. package/dist/lib/hot-channels/run.d.ts +137 -0
  85. package/dist/lib/hot-channels/run.js +324 -0
  86. package/dist/lib/hot-channels/signals-pass.d.ts +150 -0
  87. package/dist/lib/hot-channels/signals-pass.js +581 -0
  88. package/dist/lib/hot-channels/sync-lock.d.ts +51 -0
  89. package/dist/lib/hot-channels/sync-lock.js +224 -0
  90. package/dist/lib/install/hook-target.d.ts +6 -2
  91. package/dist/lib/install/hook-target.js +9 -4
  92. package/dist/lib/lanes/accounts.d.ts +1 -17
  93. package/dist/lib/lanes/accounts.js +5 -45
  94. package/dist/lib/lanes/loop.d.ts +10 -0
  95. package/dist/lib/lanes/loop.js +18 -1
  96. package/dist/lib/lanes/provider-home.d.ts +24 -0
  97. package/dist/lib/lanes/provider-home.js +52 -0
  98. package/dist/lib/lanes/spawn.d.ts +12 -0
  99. package/dist/lib/lanes/spawn.js +27 -0
  100. package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
  101. package/dist/lib/mesh/live/daemon/doctor.js +13 -0
  102. package/dist/lib/mesh/live/daemon/owner-inbox-push.d.ts +94 -0
  103. package/dist/lib/mesh/live/daemon/owner-inbox-push.js +273 -0
  104. package/dist/lib/mesh/live/daemon/run.js +31 -0
  105. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +14 -0
  106. package/dist/lib/mesh/live/daemon/work-subscribe.js +210 -3
  107. package/dist/lib/mesh/live/paths.d.ts +2 -0
  108. package/dist/lib/mesh/live/paths.js +2 -0
  109. package/dist/lib/sync-core/flag.d.ts +1 -1
  110. package/dist/lib/sync-core/flag.js +1 -1
  111. package/dist/lib/sync-core/reconcile.d.ts +1 -1
  112. package/dist/lib/sync-core/reconcile.js +9 -7
  113. package/dist/lib/sync-core/release.d.ts +3 -3
  114. package/dist/lib/sync-core/release.js +4 -4
  115. package/dist/lib/sync-core/rollout-state.d.ts +1 -0
  116. package/dist/lib/sync-core/rollout-state.js +1 -0
  117. package/package.json +8 -8
@@ -23,6 +23,7 @@ import { botsRoot } from "../bot/paths.js";
23
23
  import { CLAUDE_RUNNER_SCRIPT, CODEX_RUNNER_SCRIPT, enabledServices, planUnits } from "./catalog.js";
24
24
  import { effectiveSyncOwner, readDaemonConfig } from "./config.js";
25
25
  import { inspectEngineHome } from "../sync-core/engine-home.js";
26
+ import { fullModeHold } from "../sync-core/reconcile.js";
26
27
  import { readSyncCoreRolloutState } from "../sync-core/rollout-state.js";
27
28
  import { assertPersonalServiceSessionCapability, readRemoteControlPoolsConfig, remoteControlPoolSecretsEnv, } from "./remote-control-pools.js";
28
29
  import { loadLanesConfig } from "../lanes/config.js";
@@ -32,16 +33,16 @@ import { SUPERVISOR_HANDOFF_FLAG, readDaemonFlag } from "./flag.js";
32
33
  import { createBoundedDaemonFlagMonitor } from "./flag-monitor.js";
33
34
  import { DAEMON_CHILD_BOOTSTRAP_HANDOFF_WATCH_FLAG, DAEMON_LANES_RECONCILE_FILE_WATCH_FLAG, DAEMON_SEARCH_INDEX_RUNNER_OWNS_INDEX_FLAG, DAEMON_SYNC_PROCESS_SCAN_ON_MISSING_FLAG, } from "./kill-switch-flags.js";
34
35
  import { createLanesReconcileWatcher } from "./lanes-reconcile-watcher.js";
35
- import { findLegacyUnits, legacyMeshUnitsToRetire, legacyOwnerLookup } from "./legacy.js";
36
+ import { findLegacyUnits, legacyMeshUnitsToRetire, legacyOwnerLookup, } from "./legacy.js";
36
37
  import { readLegacySyncEnv } from "./legacy-sync-env.js";
37
- import { readVaultSyncSwitches, unreadableSwitchesWarning } from "../vault-sync-switches.js";
38
+ import { readVaultSyncSwitches, unreadableSwitchesWarning, writeVaultSyncSwitches } from "../vault-sync-switches.js";
38
39
  import { acquireDaemonLock, isProcessAlive, processCommandLine, releaseDaemonLock } from "./lock.js";
39
40
  import { retireLegacyMeshUnits } from "./migrate.js";
40
41
  import { daemonDir as meshDaemonDir, daemonPidPath as meshDaemonPidPath } from "../mesh/live/daemon/paths.js";
41
42
  import { readPidLock } from "../mesh/live/daemon/pid-lock.js";
42
43
  import { registryDir, registryFile } from "../registry/registry.js";
43
44
  import { updateAnywhereRuntimeRevocationMarker } from "../anywhere-runtime-revocation-marker.js";
44
- import { daemonRootRefusal } from "./install.js";
45
+ import { classifyWindowsTask, currentWindowsIdentity, daemonRootRefusal } from "./install.js";
45
46
  import { detectMachineType } from "./machine-type.js";
46
47
  import { appendPrivate, daemonPaths, ensurePrivateDir, openPrivateAppend } from "./paths.js";
47
48
  import { hqdSocketPath } from "./protocol.js";
@@ -65,24 +66,29 @@ export const LOG_ROTATE_BYTES = 5 * 1024 * 1024;
65
66
  export const RESTART_EXIT_CODE = 75;
66
67
  const NON_SYNC_SHUTDOWN_CAP_MS = STOP_ESCALATION_MS + 5_000;
67
68
  const SYNC_RUNNER_SHUTDOWN_CAP_MS = STOP_ESCALATION_MS + SYNC_RUNNER_PERSIST_GRACE_MS + 5_000;
68
- function syncCoreVaultPlans(home, hqRoot, platform) {
69
+ export function syncCoreVaultPlans(home, hqRoot, platform) {
69
70
  const state = readSyncCoreRolloutState(home);
70
71
  if (!state)
71
72
  return [];
72
- return state.vaults.map((vault) => inspectEngineHome(home, hqRoot, {
73
- uid: vault.uid,
74
- kind: vault.kind,
75
- ...(vault.slug ? { slug: vault.slug } : {}),
76
- phase: vault.phase,
77
- engineHome: vault.engineHome,
78
- binaryPath: state.binaryPath,
79
- release: state.release,
80
- ...(vault.legacyOffSince ? { legacyOffSince: vault.legacyOffSince } : {}),
81
- }, platform));
73
+ return state.vaults.map((vault) => {
74
+ const plan = inspectEngineHome(home, hqRoot, {
75
+ uid: vault.uid,
76
+ kind: vault.kind,
77
+ ...(vault.slug ? { slug: vault.slug } : {}),
78
+ phase: vault.phase,
79
+ engineHome: vault.engineHome,
80
+ binaryPath: state.binaryPath,
81
+ release: state.release,
82
+ ...(vault.legacyOffSince ? { legacyOffSince: vault.legacyOffSince } : {}),
83
+ }, platform);
84
+ const releaseHold = vault.phase === "full" ? fullModeHold(vault, state.release) : undefined;
85
+ return releaseHold ? { ...plan, blockedReason: releaseHold } : plan;
86
+ });
82
87
  }
83
88
  const rememberedSyncCoreVaults = new Map();
84
89
  export function planUnitsWithSyncCoreInterlock(services, context, supervisor) {
85
90
  const companies = new Set();
91
+ const runningVaults = new Set();
86
92
  let personal = false;
87
93
  for (const vault of context.syncCoreVaults ?? []) {
88
94
  rememberedSyncCoreVaults.set(vault.uid, { kind: vault.kind, slug: vault.slug, full: vault.phase === "full" });
@@ -90,7 +96,9 @@ export function planUnitsWithSyncCoreInterlock(services, context, supervisor) {
90
96
  for (const unit of supervisor?.state().units ?? []) {
91
97
  if (!unit.id.startsWith("sync-core:") || supervisor?.runningPlanKey(unit.id) === undefined)
92
98
  continue;
93
- const vault = rememberedSyncCoreVaults.get(unit.id.slice("sync-core:".length));
99
+ const uid = unit.id.slice("sync-core:".length);
100
+ runningVaults.add(uid);
101
+ const vault = rememberedSyncCoreVaults.get(uid);
94
102
  if (!vault?.full)
95
103
  continue;
96
104
  if (vault.kind === "personal")
@@ -98,13 +106,53 @@ export function planUnitsWithSyncCoreInterlock(services, context, supervisor) {
98
106
  else if (vault.slug)
99
107
  companies.add(vault.slug);
100
108
  }
101
- const transitionContext = {
109
+ let vaultSync = context.vaultSync;
110
+ let fallbackPersistenceFailed = false;
111
+ if (context.vaultSync && !context.vaultSync.allOff) {
112
+ let personalOff = context.vaultSync.personalOff;
113
+ const offCompanies = new Set(context.vaultSync.offCompanies);
114
+ let restorePersonal = false;
115
+ const restoreCompanies = {};
116
+ for (const vault of context.syncCoreVaults ?? []) {
117
+ if (vault.phase !== "full" || runningVaults.has(vault.uid) || !fullModeHold(vault, vault.release))
118
+ continue;
119
+ if (vault.kind === "personal") {
120
+ restorePersonal = personalOff;
121
+ personalOff = false;
122
+ }
123
+ else if (vault.slug) {
124
+ if (offCompanies.has(vault.slug))
125
+ restoreCompanies[vault.slug] = true;
126
+ offCompanies.delete(vault.slug);
127
+ }
128
+ }
129
+ if (restorePersonal || Object.keys(restoreCompanies).length > 0) {
130
+ try {
131
+ writeVaultSyncSwitches(context.home, {
132
+ ...(restorePersonal ? { personal: true } : {}),
133
+ companies: restoreCompanies,
134
+ });
135
+ }
136
+ catch {
137
+ fallbackPersistenceFailed = true;
138
+ }
139
+ }
140
+ vaultSync = { ...context.vaultSync, personalOff, offCompanies };
141
+ }
142
+ let transitionContext = {
102
143
  ...context,
144
+ vaultSync,
103
145
  syncCoreRunning: { personal, companies },
104
146
  legacySyncReadyForCore: false,
105
147
  };
106
- const waiting = planUnits(services, transitionContext);
107
- const legacy = waiting.find((unit) => unit.id === "sync");
148
+ let waiting = planUnits(services, transitionContext);
149
+ let legacy = waiting.find((unit) => unit.id === "sync");
150
+ if (fallbackPersistenceFailed && (legacy === undefined || legacy.blocked !== undefined)) {
151
+ vaultSync = context.vaultSync;
152
+ transitionContext = { ...transitionContext, vaultSync };
153
+ waiting = planUnits(services, transitionContext);
154
+ legacy = waiting.find((unit) => unit.id === "sync");
155
+ }
108
156
  const legacySyncReadyForCore = legacy !== undefined
109
157
  && (legacy.blocked !== undefined || supervisor?.runningPlanKey("sync") === planKey(legacy, CLI_VERSION));
110
158
  return planUnits(services, { ...transitionContext, legacySyncReadyForCore });
@@ -310,9 +358,13 @@ export function listLocalBots(root = botsRoot()) {
310
358
  .map((d) => d.name)
311
359
  .sort();
312
360
  }
313
- export function windowsTaskExists(taskName) {
314
- const r = spawnSync("schtasks.exe", ["/Query", "/TN", taskName], { encoding: "utf8", windowsHide: true });
315
- return r.status === 0;
361
+ /** Whose task `taskName` is: only the current user's own task may be taken over. */
362
+ export function windowsTaskOwner(taskName) {
363
+ const run = (cmd, args) => {
364
+ const r = spawnSync(cmd, args, { encoding: "utf8", windowsHide: true });
365
+ return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
366
+ };
367
+ return classifyWindowsTask(run, taskName, currentWindowsIdentity(run)).state;
316
368
  }
317
369
  export function probeLegacyUnits(platform, home) {
318
370
  return findLegacyUnits({
@@ -331,7 +383,7 @@ export function probeLegacyUnits(platform, home) {
331
383
  });
332
384
  return result.status === 0;
333
385
  },
334
- windowsTaskExists,
386
+ windowsTaskOwner,
335
387
  });
336
388
  }
337
389
  /** Absolute path of `name` on PATH, or null. */
@@ -9,6 +9,8 @@
9
9
  * on this machine the family reports a single NA line.
10
10
  */
11
11
  import { type HqdHealthResult } from "../../daemon/protocol.js";
12
+ import { type DaemonHost } from "../../daemon/install.js";
13
+ import type { RunCommand } from "../../service-manager/launchd.js";
12
14
  import type { CheckFamily } from "../types.js";
13
15
  export declare const REMEDIATION: {
14
16
  readonly installClaude: "hq install --global --runtime claude";
@@ -31,6 +33,10 @@ export interface DaemonServiceProbeOptions {
31
33
  launchdProbe?: (target: string) => boolean;
32
34
  /** Test seam for the desktop app's live managed daemon state. */
33
35
  hostedDaemonProbe?: () => boolean;
36
+ /** Test seam for the schtasks calls the Windows probe makes. */
37
+ run?: RunCommand;
38
+ /** Test seam for the Windows account the task name is built from. */
39
+ windowsIdentity?: Pick<DaemonHost, "windowsSid" | "windowsUser">;
34
40
  }
35
41
  export interface AnywhereDeps {
36
42
  home?: string;
@@ -20,7 +20,7 @@ import { CHATGPT_MODE } from "../../install/chatgpt-targets.js";
20
20
  import { GROK_MODE } from "../../install/grok-targets.js";
21
21
  import { HqdClientError, hqdRequest } from "../../daemon/protocol.js";
22
22
  import { daemonPresenceLabel, heartbeatFile, readHeartbeat } from "../../daemon/heartbeat.js";
23
- import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK } from "../../daemon/install.js";
23
+ import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, LEGACY_DAEMON_WINDOWS_TASK, currentWindowsIdentity, legacyWindowsTask, windowsTaskName, } from "../../daemon/install.js";
24
24
  import { hostedDaemonRunning } from "../../daemon/handoff.js";
25
25
  import { daemonPaths } from "../../daemon/paths.js";
26
26
  import { runningDaemonPid } from "../../daemon/lock.js";
@@ -40,6 +40,15 @@ export const REMEDIATION = {
40
40
  daemonStatus: "hq daemon status",
41
41
  link: "hq link <repo-dir> <company>",
42
42
  };
43
+ /** schtasks with the same 5s cap the probe always had, so a hung query cannot stall the doctor. */
44
+ const runSchtasksWithTimeout = (cmd, args) => {
45
+ const r = spawnSync(cmd, args, { encoding: "utf8", timeout: 5_000, windowsHide: true });
46
+ return {
47
+ status: r.status,
48
+ stdout: typeof r.stdout === "string" ? r.stdout : "",
49
+ stderr: r.error ? r.error.message : (typeof r.stderr === "string" ? r.stderr : ""),
50
+ };
51
+ };
43
52
  export function createAnywhereFamily(deps = {}) {
44
53
  return { id: ANYWHERE_FAMILY_ID, title: "HQ anywhere runtime", run: (context) => runAnywhereChecks(context, deps) };
45
54
  }
@@ -451,11 +460,31 @@ export function defaultServiceState(home, options = {}) {
451
460
  return { installed: true, loaded: probe.status === 0, detail: `systemd ${systemScope ? "system" : "user"} ${DAEMON_SYSTEMD_UNIT}` };
452
461
  }
453
462
  if (platform === "win32") {
454
- const probe = spawnSync("schtasks.exe", ["/Query", "/TN", DAEMON_WINDOWS_TASK, "/FO", "CSV", "/NH"], { encoding: "utf8", timeout: 5_000 });
455
- if (probe.status !== 0)
456
- return { installed: false, loaded: false, detail: `no Scheduled Task ${DAEMON_WINDOWS_TASK}` };
457
- const loaded = /,\s*"?Running"?\s*$/i.test(probe.stdout.trim());
458
- return { installed: true, loaded, detail: `Scheduled Task ${DAEMON_WINDOWS_TASK}${loaded ? " is running" : " is not running"}` };
463
+ // Each Windows user has their own task. The shared task an older install
464
+ // registered counts only when it runs as this user.
465
+ const run = options.run ?? runSchtasksWithTimeout;
466
+ const identity = options.windowsIdentity ?? currentWindowsIdentity(run);
467
+ const task = windowsTaskName(identity);
468
+ const probe = run("schtasks.exe", ["/Query", "/TN", task, "/FO", "CSV", "/NH"]);
469
+ if (probe.status !== 0) {
470
+ const legacy = legacyWindowsTask(run, identity);
471
+ if (legacy.state === "mine") {
472
+ // Still this user's daemon until install moves it to the per-user name.
473
+ const legacyProbe = run("schtasks.exe", ["/Query", "/TN", LEGACY_DAEMON_WINDOWS_TASK, "/FO", "CSV", "/NH"]);
474
+ const legacyLoaded = legacyProbe.status === 0 && /,\s*"?Running"?\s*$/i.test(legacyProbe.stdout.replace(/\0/g, "").trim());
475
+ return {
476
+ installed: true,
477
+ loaded: legacyLoaded,
478
+ detail: `your daemon runs from the shared task ${LEGACY_DAEMON_WINDOWS_TASK} from an older install${legacyLoaded ? " (running)" : " (not running)"}. Run hq daemon install to move it to your own task ${task}`,
479
+ };
480
+ }
481
+ const someoneElse = legacy.state === "other" || legacy.state === "unreadable"
482
+ ? ` (${LEGACY_DAEMON_WINDOWS_TASK} belongs to another Windows user and is not yours)`
483
+ : "";
484
+ return { installed: false, loaded: false, detail: `no Scheduled Task ${task}${someoneElse}` };
485
+ }
486
+ const loaded = /,\s*"?Running"?\s*$/i.test(probe.stdout.replace(/\0/g, "").trim());
487
+ return { installed: true, loaded, detail: `Scheduled Task ${task}${loaded ? " is running" : " is not running"}` };
459
488
  }
460
489
  return { installed: false, loaded: false, detail: `${platform}: service probe not available` };
461
490
  }