@indigoai-us/hq-cli 5.345.50 → 5.345.52

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 (33) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/commands/core-hq-session-native.js +2 -2
  3. package/dist/commands/core-resolve-company-native.js +2 -2
  4. package/dist/commands/lanes.js +52 -11
  5. package/dist/commands/secrets.js +2 -2
  6. package/dist/commands/workers.js +2 -2
  7. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +40293 -13770
  8. package/dist/hq-cloud-sync-runner/package.json +1 -1
  9. package/dist/lib/bot/daemon.d.ts +1 -1
  10. package/dist/lib/bot/daemon.js +35 -29
  11. package/dist/lib/daemon/install.js +34 -3
  12. package/dist/lib/doctor/checks/bots.js +3 -8
  13. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  14. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  15. package/dist/lib/lanes/outpost-dispatch.d.ts +1 -0
  16. package/dist/lib/lanes/outpost-dispatch.js +2 -0
  17. package/dist/lib/mesh/live/daemon/install.js +28 -15
  18. package/dist/lib/service-manager/index.d.ts +3 -0
  19. package/dist/lib/service-manager/index.js +22 -6
  20. package/dist/lib/service-manager/launchd.d.ts +40 -3
  21. package/dist/lib/service-manager/launchd.js +106 -5
  22. package/dist/lib/service-manager/migration-state.d.ts +12 -0
  23. package/dist/lib/service-manager/migration-state.js +46 -0
  24. package/dist/lib/service-manager/migration.d.ts +22 -0
  25. package/dist/lib/service-manager/migration.js +85 -0
  26. package/dist/lib/service-manager/startup-migration.d.ts +24 -0
  27. package/dist/lib/service-manager/startup-migration.js +27 -0
  28. package/dist/lib/service-manager/types.d.ts +3 -0
  29. package/dist/main.js +22 -0
  30. package/dist/native-fast-route.d.ts +4 -0
  31. package/dist/native-fast-route.js +20 -0
  32. package/dist/utils/resident-package-preload.js +2 -1
  33. package/package.json +2 -2
@@ -1 +1 @@
1
- {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.50","bundleSha256":"cddd12bcd4c78ea32b54644772aae073c139b83f4c6cf44ea15141670ee3360f","formatVersion":1}
1
+ {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.54","bundleSha256":"3a2ccf7955d7a5be1ab8e9a54e810d55ec7041e1eda534b84eba75e2199ee2f6","formatVersion":1}
@@ -64,7 +64,7 @@ export interface BotDaemonDeps {
64
64
  platform?: BotDaemonPlatform;
65
65
  launchctl?: LaunchctlExec;
66
66
  uid?: number;
67
- fsImpl?: Pick<typeof fs, "existsSync" | "writeFileSync" | "mkdirSync" | "unlinkSync">;
67
+ fsImpl?: Pick<typeof fs, "existsSync" | "readFileSync" | "writeFileSync" | "mkdirSync" | "unlinkSync"> & Partial<Pick<typeof fs, "chmodSync" | "renameSync">>;
68
68
  /** Blocking sleep, injectable for tests. */
69
69
  sleepMs?: (ms: number) => void;
70
70
  /**
@@ -15,7 +15,7 @@ import * as path from "node:path";
15
15
  import { spawn } from "node:child_process";
16
16
  import { resolveHqBinary, resolveNodeBinary, preferStableHqBinary } from "../mesh/live/daemon/install.js";
17
17
  import { botLogPath, launchdLabel } from "./paths.js";
18
- import { reloadLaunchd } from "../service-manager/launchd.js";
18
+ import { discoverLaunchdIdentity, installLaunchdPlist, launchdProgramArguments, launchdPlistProgramArguments as parsePlistArguments, parseLaunchdCommand, reloadLaunchd } from "../service-manager/launchd.js";
19
19
  import { nodeBinaryProblem, preferStableNodeBinary } from "../node-binary.js";
20
20
  export function detectBotPlatform(platform = process.platform) {
21
21
  if (platform === "darwin")
@@ -66,6 +66,12 @@ function escapeXml(value) {
66
66
  .replace(/"/g, "&quot;");
67
67
  }
68
68
  export function renderBotLaunchdPlist(p) {
69
+ const identity = discoverLaunchdIdentity(p.home);
70
+ const args = launchdProgramArguments({
71
+ nodeBinary: p.nodeBinary,
72
+ hqBinary: p.hqBinary,
73
+ launchdLauncherBinary: identity.launcherBinary,
74
+ }, ["bot", "run", p.name]);
69
75
  const envRows = [
70
76
  ["HOME", p.home],
71
77
  ["PATH", p.pathEntries.join(":")],
@@ -80,13 +86,9 @@ export function renderBotLaunchdPlist(p) {
80
86
  <dict>
81
87
  <key>Label</key>
82
88
  <string>${escapeXml(p.label)}</string>
83
- <key>ProgramArguments</key>
89
+ ${identity.associatedBundleIdentifier ? ` <key>AssociatedBundleIdentifiers</key>\n <array>\n <string>${escapeXml(identity.associatedBundleIdentifier)}</string>\n </array>\n` : ""} <key>ProgramArguments</key>
84
90
  <array>
85
- <string>${escapeXml(p.nodeBinary)}</string>
86
- <string>${escapeXml(p.hqBinary)}</string>
87
- <string>bot</string>
88
- <string>run</string>
89
- <string>${escapeXml(p.name)}</string>
91
+ ${args.map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n")}
90
92
  </array>
91
93
  <key>WorkingDirectory</key>
92
94
  <string>${escapeXml(p.hqRoot ?? p.botDir)}</string>
@@ -190,16 +192,13 @@ function launchdLoaded(p, deps) {
190
192
  return deps.launchctl(["print", `${launchdDomain(deps.uid)}/${p.label}`]).ok;
191
193
  }
192
194
  export function launchdPlistProgramArguments(plist) {
193
- const block = plist.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
194
- if (!block)
195
- return [];
196
- return [...block[1].matchAll(/<string>([^<]*)<\/string>/g)].map((m) => m[1]);
195
+ return parsePlistArguments(plist);
197
196
  }
198
197
  /** The hq script path baked into an installed unit, or null if none. */
199
198
  export function installedDaemonHqBinary(p, io = fs) {
200
199
  if (io.existsSync(p.plistPath)) {
201
200
  const args = launchdPlistProgramArguments(io.readFileSync(p.plistPath, "utf8"));
202
- return args[1] ?? null;
201
+ return parseLaunchdCommand(args)?.hq ?? null;
203
202
  }
204
203
  if (io.existsSync(p.systemdUnitPath)) {
205
204
  const unit = io.readFileSync(p.systemdUnitPath, "utf8");
@@ -212,7 +211,7 @@ export function installedDaemonHqBinary(p, io = fs) {
212
211
  export function installedDaemonNodeBinary(p, io = fs) {
213
212
  if (io.existsSync(p.plistPath)) {
214
213
  const args = launchdPlistProgramArguments(io.readFileSync(p.plistPath, "utf8"));
215
- return args[0] ?? null;
214
+ return parseLaunchdCommand(args)?.node ?? null;
216
215
  }
217
216
  if (io.existsSync(p.systemdUnitPath)) {
218
217
  const unit = io.readFileSync(p.systemdUnitPath, "utf8");
@@ -383,19 +382,27 @@ export function installBotDaemon(p, deps = {}) {
383
382
  io.mkdirSync(path.dirname(p.logPath), { recursive: true, mode: 0o700 });
384
383
  if (platform === "darwin") {
385
384
  io.mkdirSync(path.dirname(p.plistPath), { recursive: true });
386
- // Replace rather than overwrite, so re-adopting a bot over the stale plist
387
- // a wiped ~/.hq left behind lands a fresh file at 0600 (writeFileSync
388
- // keeps the mode an existing file already has).
389
- if (io.existsSync(p.plistPath))
390
- io.unlinkSync(p.plistPath);
391
- io.writeFileSync(p.plistPath, renderBotLaunchdPlist(p), { mode: 0o600 });
392
- let loaded = false;
393
- if (deps.launchctl) {
394
- // bootout first so a re-install picks up a changed plist.
395
- const r = rebootstrapLaunchd(p, deps);
396
- if (!r.ok) {
397
- const detail = `launchctl bootstrap failed after ${r.bootstrapAttempts} attempts: ${(r.error ?? "").slice(0, 200)}`;
398
- if (r.loaded) {
385
+ const reloadState = { result: null };
386
+ const written = installLaunchdPlist(p.plistPath, p.label, renderBotLaunchdPlist(p), {
387
+ files: {
388
+ existsSync: io.existsSync,
389
+ readFileSync: (file) => io.readFileSync(file, "utf8"),
390
+ writeFileSync: io.writeFileSync,
391
+ ...(io.chmodSync ? { chmodSync: io.chmodSync } : {}),
392
+ ...(io.renameSync ? { renameSync: io.renameSync } : {}),
393
+ unlinkSync: io.unlinkSync,
394
+ },
395
+ activate: Boolean(deps.launchctl),
396
+ isLoaded: () => launchdLoaded(p, deps),
397
+ ...(deps.launchctl ? { reload: () => { reloadState.result = rebootstrapLaunchd(p, deps); return reloadState.result; } } : {}),
398
+ });
399
+ let loaded = written.loaded;
400
+ if (deps.launchctl && reloadState.result) {
401
+ const r = reloadState.result;
402
+ loaded = r.loaded;
403
+ if (!r?.ok) {
404
+ const detail = `launchctl bootstrap failed after ${r?.bootstrapAttempts ?? 0} attempts: ${(r?.error ?? "").slice(0, 200)}`;
405
+ if (r?.loaded) {
399
406
  // The previous definition survived the bootout; keep it running.
400
407
  deps.launchctl(["kickstart", "-k", `${launchdDomain(deps.uid)}/${p.label}`]);
401
408
  }
@@ -403,14 +410,13 @@ export function installBotDaemon(p, deps = {}) {
403
410
  platform,
404
411
  action: "install",
405
412
  installed: true,
406
- loaded: r.loaded,
413
+ loaded: r?.loaded ?? false,
407
414
  dest: p.plistPath,
408
- message: r.loaded
415
+ message: r?.loaded
409
416
  ? `Wrote ${p.plistPath} but ${detail}. The previous definition of ${p.label} is still loaded and was restarted; the rewritten plist is not active yet.`
410
417
  : `Wrote ${p.plistPath} but ${detail}. ${p.label} is NOT loaded. Load it with: launchctl bootstrap ${launchdDomain(deps.uid)} ${p.plistPath}`,
411
418
  };
412
419
  }
413
- loaded = true;
414
420
  }
415
421
  return {
416
422
  platform,
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import * as fs from "node:fs";
19
19
  import * as path from "node:path";
20
- import { activateLaunchd, deactivateLaunchd, launchdPlistPath, renderLaunchdPlist, } from "../service-manager/launchd.js";
20
+ import { activateLaunchd, deactivateLaunchd, defaultRunCommand, installLaunchdPlist, launchdIsLoaded, launchdPlistPath, renderLaunchdPlist, } from "../service-manager/launchd.js";
21
21
  import { activateSystemd, deactivateSystemd, renderSystemdUserUnit, systemdUnitPath, } from "../service-manager/systemd.js";
22
22
  import { escapeXml, shellQuote } from "../service-manager/types.js";
23
23
  import { readPidLock } from "../mesh/live/daemon/pid-lock.js";
@@ -251,12 +251,43 @@ export function installDaemonUnit(host, options, deps) {
251
251
  fs.mkdirSync(path.dirname(plan.unitPath), { recursive: true });
252
252
  if (plan.kind !== "systemd-system")
253
253
  fs.mkdirSync(path.join(host.daemonDir, "logs"), { recursive: true });
254
- if (plan.kind === "schtasks")
254
+ if (plan.kind === "launchd") {
255
+ let plist;
256
+ try {
257
+ plist = installLaunchdPlist(plan.unitPath, DAEMON_LAUNCHD_LABEL, plan.rendered, {
258
+ run: deps.run ?? defaultRunCommand,
259
+ uid: host.uid,
260
+ sleep: deps.sleep,
261
+ isLoaded: () => launchdIsLoaded(DAEMON_LAUNCHD_LABEL, deps.run ?? defaultRunCommand, host.uid),
262
+ files: {
263
+ existsSync: fs.existsSync,
264
+ readFileSync: (file) => fs.readFileSync(file, "utf8"),
265
+ writeFileSync: fs.writeFileSync,
266
+ chmodSync: fs.chmodSync,
267
+ renameSync: fs.renameSync,
268
+ unlinkSync: fs.unlinkSync,
269
+ },
270
+ });
271
+ }
272
+ catch (err) {
273
+ return {
274
+ kind: plan.kind,
275
+ unitPath: plan.unitPath,
276
+ installed: fs.existsSync(plan.unitPath),
277
+ running: launchdIsLoaded(DAEMON_LAUNCHD_LABEL, deps.run ?? defaultRunCommand, host.uid),
278
+ error: err instanceof Error ? err.message : String(err),
279
+ };
280
+ }
281
+ if (!plist.loaded)
282
+ return { kind: plan.kind, unitPath: plan.unitPath, installed: true, running: false };
283
+ }
284
+ else if (plan.kind === "schtasks")
255
285
  fs.writeFileSync(plan.unitPath, encodeWindowsTaskXml(plan.rendered));
256
286
  else
257
287
  fs.writeFileSync(plan.unitPath, plan.rendered, { mode: plan.kind === "systemd-system" ? 0o644 : 0o600 });
258
288
  try {
259
- activate(plan, host, deps.run, deps.sleep);
289
+ if (plan.kind !== "launchd")
290
+ activate(plan, host, deps.run, deps.sleep);
260
291
  return { kind: plan.kind, unitPath: plan.unitPath, installed: true, running: true };
261
292
  }
262
293
  catch (err) {
@@ -10,6 +10,7 @@ import { botsRoot, botDir, launchdLabel } from "../../bot/paths.js";
10
10
  import { readBotConfig } from "../../bot/config.js";
11
11
  import { isPidAlive, readBotStatus } from "../../bot/status.js";
12
12
  import { nodeBinaryProblem } from "../../node-binary.js";
13
+ import { launchdPlistProgramArguments, parseLaunchdCommand } from "../../service-manager/launchd.js";
13
14
  export const BOTS_FAMILY_ID = "bots";
14
15
  export const BOTS_FAMILY_TITLE = "Local bots";
15
16
  const DEFAULT_DEPS = {
@@ -35,14 +36,8 @@ export function checkBots(_context, deps = DEFAULT_DEPS) {
35
36
  ];
36
37
  }
37
38
  }
38
- function launchdPlistArgs(plist) {
39
- const block = plist.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
40
- if (!block)
41
- return [];
42
- return [...block[1].matchAll(/<string>([^<]*)<\/string>/g)].map((m) => m[1]);
43
- }
44
39
  function launchdPlistHqBinary(plist) {
45
- return launchdPlistArgs(plist)[1] ?? null;
40
+ return parseLaunchdCommand(launchdPlistProgramArguments(plist))?.hq ?? null;
46
41
  }
47
42
  function runChecks(deps) {
48
43
  const home = deps.home();
@@ -122,7 +117,7 @@ function runChecks(deps) {
122
117
  try {
123
118
  const text = fs.readFileSync(plistPath, "utf8");
124
119
  hqPath = launchdPlistHqBinary(text);
125
- nodePath = launchdPlistArgs(text)[0] ?? null;
120
+ nodePath = parseLaunchdCommand(launchdPlistProgramArguments(text))?.node ?? null;
126
121
  }
127
122
  catch {
128
123
  hqPath = null;