@indigoai-us/hq-cli 5.345.174 → 5.345.176

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 (62) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/command-catalog.generated.d.ts +65 -0
  3. package/dist/command-catalog.generated.js +80 -0
  4. package/dist/commands/bot.d.ts +28 -2
  5. package/dist/commands/bot.js +32 -6
  6. package/dist/commands/daemon.js +16 -0
  7. package/dist/commands/dm.d.ts +7 -0
  8. package/dist/commands/dm.js +16 -4
  9. package/dist/commands/lanes-loop.d.ts +4 -1
  10. package/dist/commands/lanes-loop.js +23 -4
  11. package/dist/commands/lanes-shared.d.ts +5 -0
  12. package/dist/commands/lanes.d.ts +5 -7
  13. package/dist/commands/lanes.js +65 -5
  14. package/dist/commands/sync-core.d.ts +17 -0
  15. package/dist/commands/sync-core.js +46 -5
  16. package/dist/commands/sync-manifest.d.ts +6 -0
  17. package/dist/commands/sync-manifest.js +13 -0
  18. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +448 -226
  19. package/dist/hq-cloud-sync-runner/package.json +1 -1
  20. package/dist/lib/bot/cli-update.d.ts +36 -0
  21. package/dist/lib/bot/cli-update.js +160 -42
  22. package/dist/lib/bot/run.d.ts +23 -0
  23. package/dist/lib/bot/run.js +174 -23
  24. package/dist/lib/bot/runtime/claude.d.ts +7 -1
  25. package/dist/lib/bot/runtime/claude.js +29 -0
  26. package/dist/lib/bot/runtime/codex.d.ts +5 -0
  27. package/dist/lib/bot/runtime/codex.js +13 -0
  28. package/dist/lib/bot/runtime/index.js +18 -2
  29. package/dist/lib/bot/runtime/types.d.ts +23 -0
  30. package/dist/lib/bot/runtime/types.js +13 -0
  31. package/dist/lib/bot/runtime-failure.d.ts +17 -6
  32. package/dist/lib/bot/runtime-failure.js +54 -9
  33. package/dist/lib/bot/runtime-sign-in-check.d.ts +32 -0
  34. package/dist/lib/bot/runtime-sign-in-check.js +53 -0
  35. package/dist/lib/bot/runtime-sign-in.d.ts +32 -3
  36. package/dist/lib/bot/runtime-sign-in.js +17 -2
  37. package/dist/lib/bot/status.d.ts +35 -1
  38. package/dist/lib/bot/status.js +28 -0
  39. package/dist/lib/daemon/catalog.d.ts +4 -0
  40. package/dist/lib/daemon/catalog.js +47 -19
  41. package/dist/lib/daemon/run.js +1 -0
  42. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  43. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  44. package/dist/lib/lanes/loop-disposition.d.ts +44 -0
  45. package/dist/lib/lanes/loop-disposition.js +302 -0
  46. package/dist/lib/lanes/loop.d.ts +16 -3
  47. package/dist/lib/lanes/loop.js +167 -60
  48. package/dist/lib/lanes/reconcile.d.ts +5 -0
  49. package/dist/lib/lanes/reconcile.js +79 -4
  50. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +3 -0
  51. package/dist/lib/mesh/live/daemon/work-subscribe.js +41 -12
  52. package/dist/lib/sync-core/engine-home.d.ts +11 -4
  53. package/dist/lib/sync-core/engine-home.js +22 -21
  54. package/dist/lib/sync-core/manifest-scopes.d.ts +15 -0
  55. package/dist/lib/sync-core/manifest-scopes.js +26 -0
  56. package/dist/lib/sync-core/reconcile.d.ts +2 -2
  57. package/dist/lib/sync-core/reconcile.js +70 -46
  58. package/dist/lib/sync-core/release.d.ts +2 -2
  59. package/dist/lib/sync-core/release.js +31 -6
  60. package/dist/lib/sync-core/rollout-state.d.ts +6 -3
  61. package/dist/lib/sync-core/rollout-state.js +26 -22
  62. package/package.json +8 -8
@@ -1 +1 @@
1
- {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.103","bundleSha256":"3ed362bac2c75d32a61b0e26b4253813af96458579920a798fd38a9583e855f9","formatVersion":1}
1
+ {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.104","bundleSha256":"40c72e049db21ee4551fa737676d8022cd090149ede9e3c9e53a9fae3f28dadc","formatVersion":1}
@@ -23,6 +23,15 @@
23
23
  * that target for CLI_UPDATE_NOT_CONVERGED_COOLDOWN_MS instead of restarting
24
24
  * again. Restarts are also capped per window, so an install that keeps
25
25
  * flipping cannot turn into a restart loop.
26
+ *
27
+ * The same watcher covers the macOS HQ.app launcher. A bot's LaunchAgent runs
28
+ * `HQ.app/Contents/MacOS/hq-sync-menubar --hq-launchd-task node hq bot run`, and
29
+ * that launcher is what macOS privacy (TCC) attributes the bot's file access
30
+ * to. When an HQ.app update replaces the launcher binary under a running bot,
31
+ * every coding-tool turn after that fails within milliseconds ("An unknown
32
+ * error occurred (Unexpected)") because ~/Documents access no longer resolves,
33
+ * until the bot is restarted under the new launcher. So a changed launcher
34
+ * binary is handled like an updated CLI: confirm, drain, exit for launchd.
26
35
  */
27
36
  import type { BotLogger } from "./log.js";
28
37
  /**
@@ -62,7 +71,18 @@ export interface CliUpdate {
62
71
  from: string;
63
72
  to: string;
64
73
  packageRoot: string;
74
+ /** "launcher": the HQ.app launcher binary was replaced; the CLI version is unchanged. */
75
+ reason?: "launcher";
76
+ }
77
+ /** One line for the log and the exit reason. */
78
+ export declare function describeCliUpdate(update: CliUpdate): string;
79
+ /** Identity of a file on disk: an app update replaces the inode or changes size/mtime. */
80
+ export interface LauncherStamp {
81
+ ino: number;
82
+ size: number;
83
+ mtimeMs: number;
65
84
  }
85
+ export declare function statLauncher(p: string): LauncherStamp | null;
66
86
  interface CliUpdateLedger {
67
87
  v: 1;
68
88
  /** Written just before an update restart; settled by the next process. */
@@ -70,6 +90,7 @@ interface CliUpdateLedger {
70
90
  from: string;
71
91
  to: string;
72
92
  at: string;
93
+ reason?: "launcher";
73
94
  };
74
95
  /** ISO times of recent update restarts (for the per-window cap). */
75
96
  restarts: string[];
@@ -95,6 +116,9 @@ export interface CliUpdateWatcherOptions {
95
116
  dir: string;
96
117
  /** The script this process was launched with (process.argv[1]); null = do not watch. */
97
118
  entry: string | null;
119
+ /** The HQ.app launcher binary that started this process (macOS LaunchAgent); null = do not watch. */
120
+ launcher?: string | null;
121
+ statLauncher?: (p: string) => LauncherStamp | null;
98
122
  runningVersion?: string;
99
123
  now?: () => number;
100
124
  read?: (entry: string) => InstalledCliRead;
@@ -107,6 +131,18 @@ export interface CliUpdateWatcherOptions {
107
131
  cooldownMs?: number;
108
132
  }
109
133
  export declare function createCliUpdateWatcher(opts: CliUpdateWatcherOptions): CliUpdateWatch;
134
+ /**
135
+ * The HQ.app launcher binary this process runs under, or null. A LaunchAgent
136
+ * written for HQ.app runs `<HQ.app>/Contents/MacOS/hq-sync-menubar
137
+ * --hq-launchd-task node hq ...`; the launcher spawns node and waits, so it is
138
+ * the direct parent. Only a parent whose command line carries the launcher
139
+ * flag counts.
140
+ */
141
+ export declare function detectHqAppLauncher(opts?: {
142
+ platform?: NodeJS.Platform;
143
+ ppid?: number;
144
+ parentCommand?: (pid: number) => string | null;
145
+ }): string | null;
110
146
  /**
111
147
  * Who starts this bot again after it exits.
112
148
  *
@@ -23,7 +23,17 @@
23
23
  * that target for CLI_UPDATE_NOT_CONVERGED_COOLDOWN_MS instead of restarting
24
24
  * again. Restarts are also capped per window, so an install that keeps
25
25
  * flipping cannot turn into a restart loop.
26
+ *
27
+ * The same watcher covers the macOS HQ.app launcher. A bot's LaunchAgent runs
28
+ * `HQ.app/Contents/MacOS/hq-sync-menubar --hq-launchd-task node hq bot run`, and
29
+ * that launcher is what macOS privacy (TCC) attributes the bot's file access
30
+ * to. When an HQ.app update replaces the launcher binary under a running bot,
31
+ * every coding-tool turn after that fails within milliseconds ("An unknown
32
+ * error occurred (Unexpected)") because ~/Documents access no longer resolves,
33
+ * until the bot is restarted under the new launcher. So a changed launcher
34
+ * binary is handled like an updated CLI: confirm, drain, exit for launchd.
26
35
  */
36
+ import { spawnSync } from "node:child_process";
27
37
  import * as fs from "node:fs";
28
38
  import * as path from "node:path";
29
39
  import semver from "semver";
@@ -98,6 +108,22 @@ export function readInstalledCli(entry, io = fs) {
98
108
  }
99
109
  return { ok: false, reason: `no ${CLI_NAME} package.json above ${real}` };
100
110
  }
111
+ /** One line for the log and the exit reason. */
112
+ export function describeCliUpdate(update) {
113
+ return update.reason === "launcher"
114
+ ? `HQ.app was updated (${update.packageRoot}); restarting under the new launcher`
115
+ : `hq-cli updated ${update.from} -> ${update.to}; restarting to load it`;
116
+ }
117
+ export function statLauncher(p) {
118
+ try {
119
+ const st = fs.statSync(p);
120
+ return { ino: st.ino, size: st.size, mtimeMs: st.mtimeMs };
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ const sameStamp = (a, b) => a.ino === b.ino && a.size === b.size && a.mtimeMs === b.mtimeMs;
101
127
  function ledgerPath(dir) {
102
128
  return path.join(dir, CLI_UPDATE_LEDGER_FILE);
103
129
  }
@@ -133,6 +159,19 @@ export function createCliUpdateWatcher(opts) {
133
159
  const restartWindowMs = opts.restartWindowMs ?? CLI_UPDATE_RESTART_WINDOW_MS;
134
160
  const cooldownMs = opts.cooldownMs ?? CLI_UPDATE_NOT_CONVERGED_COOLDOWN_MS;
135
161
  const { dir, entry } = opts;
162
+ const launcher = opts.launcher ?? null;
163
+ const stat = opts.statLauncher ?? statLauncher;
164
+ /** The launcher as it was when this process started; null = not watched. */
165
+ const launcherBaseline = launcher ? stat(launcher) : null;
166
+ let launcherCandidate = null;
167
+ let launcherSeen = 0;
168
+ let lastLauncherNote = null;
169
+ const launcherNote = (log, level, message) => {
170
+ if (message === lastLauncherNote)
171
+ return;
172
+ lastLauncherNote = message;
173
+ log(level, message);
174
+ };
136
175
  let nextCheckAt = now() + checkIntervalMs;
137
176
  let candidate = null;
138
177
  let seen = 0;
@@ -151,6 +190,10 @@ export function createCliUpdateWatcher(opts) {
151
190
  return {
152
191
  runningVersion,
153
192
  startup(log) {
193
+ if (launcher && launcherBaseline)
194
+ log("info", `watching ${launcher} for HQ.app updates`);
195
+ else if (launcher)
196
+ log("warn", `not watching for HQ.app updates: ${launcher} is not readable`);
154
197
  if (!entry) {
155
198
  log("info", "not watching for hq-cli updates (no entry script)");
156
199
  return;
@@ -159,7 +202,10 @@ export function createCliUpdateWatcher(opts) {
159
202
  const pending = ledger.pending;
160
203
  if (pending) {
161
204
  delete ledger.pending;
162
- if (runningVersion === pending.to) {
205
+ if (pending.reason === "launcher") {
206
+ log("info", `restarted after an HQ.app update; now running hq-cli ${runningVersion}`);
207
+ }
208
+ else if (runningVersion === pending.to) {
163
209
  log("info", `hq-cli update converged: now running ${runningVersion} (was ${pending.from})`);
164
210
  if (ledger.blocked?.version === runningVersion)
165
211
  delete ledger.blocked;
@@ -183,60 +229,132 @@ export function createCliUpdateWatcher(opts) {
183
229
  log("info", `watching ${entry} for hq-cli updates (running ${runningVersion})`);
184
230
  },
185
231
  poll(log) {
186
- if (!entry)
232
+ if (!entry && !launcherBaseline)
187
233
  return null;
188
234
  const at = now();
189
235
  if (at < nextCheckAt)
190
236
  return null;
191
237
  nextCheckAt = at + checkIntervalMs;
192
- const installed = read(entry);
193
- if (!installed.ok) {
194
- candidate = null;
195
- seen = 0;
196
- note(log, "warn", `hq-cli install not readable, not restarting: ${installed.reason}`);
197
- return null;
198
- }
199
- if (installed.version === runningVersion) {
200
- candidate = null;
201
- seen = 0;
202
- lastNote = null;
203
- return null;
204
- }
205
- if (rewriteInProgress(installed.packageRoot)) {
206
- candidate = null;
207
- seen = 0;
208
- note(log, "info", `hq-cli ${installed.version} is still being installed; waiting for it to finish`);
209
- return null;
210
- }
211
- if (candidate === installed.version)
212
- seen += 1;
213
- else {
214
- candidate = installed.version;
215
- seen = 1;
216
- }
217
- if (seen < confirmations) {
218
- note(log, "info", `hq-cli ${installed.version} is installed (running ${runningVersion}); confirming before restarting`);
219
- return null;
220
- }
221
- const ledger = readCliUpdateLedger(dir);
222
- if (ledger.blocked?.version === installed.version && at < Date.parse(ledger.blocked.until)) {
223
- note(log, "warn", `not restarting for hq-cli ${installed.version}: an earlier restart for it did not load it (retry after ${ledger.blocked.until})`);
224
- return null;
225
- }
226
- if (recentRestarts(ledger, at).length >= restartLimit) {
227
- note(log, "warn", `not restarting for hq-cli ${installed.version}: already restarted ${restartLimit} times for updates in the last ${Math.round(restartWindowMs / 60_000)} minutes`);
228
- return null;
229
- }
230
- return { from: runningVersion, to: installed.version, packageRoot: installed.packageRoot };
238
+ return pollCli(log, at) ?? pollLauncher(log, at);
231
239
  },
232
240
  recordRestart(update) {
233
241
  const at = now();
234
242
  const ledger = readCliUpdateLedger(dir);
235
243
  ledger.restarts = [...recentRestarts(ledger, at), new Date(at).toISOString()];
236
- ledger.pending = { from: update.from, to: update.to, at: new Date(at).toISOString() };
244
+ ledger.pending = {
245
+ from: update.from,
246
+ to: update.to,
247
+ at: new Date(at).toISOString(),
248
+ ...(update.reason ? { reason: update.reason } : {}),
249
+ };
237
250
  writeCliUpdateLedger(dir, ledger);
238
251
  },
239
252
  };
253
+ function underRestartCap(log, at, what, say = note) {
254
+ if (recentRestarts(readCliUpdateLedger(dir), at).length < restartLimit)
255
+ return true;
256
+ say(log, "warn", `not restarting for ${what}: already restarted ${restartLimit} times for updates in the last ${Math.round(restartWindowMs / 60_000)} minutes`);
257
+ return false;
258
+ }
259
+ function pollLauncher(log, at) {
260
+ if (!launcher || !launcherBaseline)
261
+ return null;
262
+ const current = stat(launcher);
263
+ if (!current) {
264
+ // Mid-replace: the old bundle is gone and the new one is not there yet.
265
+ launcherCandidate = null;
266
+ launcherSeen = 0;
267
+ launcherNote(log, "info", `${launcher} is missing (HQ.app being replaced?); waiting`);
268
+ return null;
269
+ }
270
+ if (sameStamp(current, launcherBaseline)) {
271
+ launcherCandidate = null;
272
+ launcherSeen = 0;
273
+ lastLauncherNote = null;
274
+ return null;
275
+ }
276
+ if (launcherCandidate && sameStamp(current, launcherCandidate))
277
+ launcherSeen += 1;
278
+ else {
279
+ launcherCandidate = current;
280
+ launcherSeen = 1;
281
+ }
282
+ if (launcherSeen < confirmations) {
283
+ launcherNote(log, "info", `${launcher} changed (HQ.app update); confirming before restarting`);
284
+ return null;
285
+ }
286
+ if (!underRestartCap(log, at, "the HQ.app update", launcherNote))
287
+ return null;
288
+ return { from: runningVersion, to: runningVersion, packageRoot: launcher, reason: "launcher" };
289
+ }
290
+ function pollCli(log, at) {
291
+ if (!entry)
292
+ return null;
293
+ const installed = read(entry);
294
+ if (!installed.ok) {
295
+ candidate = null;
296
+ seen = 0;
297
+ note(log, "warn", `hq-cli install not readable, not restarting: ${installed.reason}`);
298
+ return null;
299
+ }
300
+ if (installed.version === runningVersion) {
301
+ candidate = null;
302
+ seen = 0;
303
+ lastNote = null;
304
+ return null;
305
+ }
306
+ if (rewriteInProgress(installed.packageRoot)) {
307
+ candidate = null;
308
+ seen = 0;
309
+ note(log, "info", `hq-cli ${installed.version} is still being installed; waiting for it to finish`);
310
+ return null;
311
+ }
312
+ if (candidate === installed.version)
313
+ seen += 1;
314
+ else {
315
+ candidate = installed.version;
316
+ seen = 1;
317
+ }
318
+ if (seen < confirmations) {
319
+ note(log, "info", `hq-cli ${installed.version} is installed (running ${runningVersion}); confirming before restarting`);
320
+ return null;
321
+ }
322
+ const ledger = readCliUpdateLedger(dir);
323
+ if (ledger.blocked?.version === installed.version && at < Date.parse(ledger.blocked.until)) {
324
+ note(log, "warn", `not restarting for hq-cli ${installed.version}: an earlier restart for it did not load it (retry after ${ledger.blocked.until})`);
325
+ return null;
326
+ }
327
+ if (!underRestartCap(log, at, `hq-cli ${installed.version}`))
328
+ return null;
329
+ return { from: runningVersion, to: installed.version, packageRoot: installed.packageRoot };
330
+ }
331
+ }
332
+ /**
333
+ * The HQ.app launcher binary this process runs under, or null. A LaunchAgent
334
+ * written for HQ.app runs `<HQ.app>/Contents/MacOS/hq-sync-menubar
335
+ * --hq-launchd-task node hq ...`; the launcher spawns node and waits, so it is
336
+ * the direct parent. Only a parent whose command line carries the launcher
337
+ * flag counts.
338
+ */
339
+ export function detectHqAppLauncher(opts = {}) {
340
+ if ((opts.platform ?? process.platform) !== "darwin")
341
+ return null;
342
+ const ppid = opts.ppid ?? process.ppid;
343
+ if (!ppid || ppid <= 1)
344
+ return null;
345
+ const parentCommand = opts.parentCommand ??
346
+ ((pid) => {
347
+ const r = spawnSync("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", timeout: 5_000 });
348
+ return r.status === 0 ? r.stdout.trim() : null;
349
+ });
350
+ const command = parentCommand(ppid);
351
+ if (!command)
352
+ return null;
353
+ const flag = command.indexOf(" --hq-launchd-task ");
354
+ if (flag <= 0)
355
+ return null;
356
+ const binary = command.slice(0, flag);
357
+ return binary.endsWith("/Contents/MacOS/hq-sync-menubar") ? binary : null;
240
358
  }
241
359
  export function detectBotSupervisor(name, opts = {}) {
242
360
  const env = opts.env ?? process.env;
@@ -56,6 +56,7 @@ import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult } from "
56
56
  import { type BotSessionScope } from "./paths.js";
57
57
  import { type DecisionDmClient } from "./decisions.js";
58
58
  import type { VaultObjectDownloader } from "../agent-kit/attachment-download.js";
59
+ import { type RuntimeSignInCheck } from "./runtime-sign-in.js";
59
60
  import { type HeartbeatAgentUidSource, type HeartbeatCredsSource, type HeartbeatIdentityMismatchReporter } from "../heartbeat-identity.js";
60
61
  import { type BotState, type BotStatusFile } from "./status.js";
61
62
  import { type CliUpdate, type CliUpdateWatch } from "./cli-update.js";
@@ -85,6 +86,8 @@ export declare const FAILURE_WINDOW_MS: number;
85
86
  export declare const FAILURE_LIMIT = 5;
86
87
  /** Resumed-session failures with the model CLI's unknown error, in a row, before the session is cleared. */
87
88
  export declare const RESUMED_UNKNOWN_ERROR_LIMIT = 2;
89
+ /** Wait before the one automatic re-run of a turn whose runtime crashed before it started. */
90
+ export declare const RUNTIME_CRASH_RETRY_DELAY_MS = 3000;
88
91
  export declare const MAX_ATTEMPTS_PER_MESSAGE = 3;
89
92
  export declare const THINKING_EMOJI = "\uD83D\uDC40";
90
93
  export declare const ROOM_THINKING_STATUS = "is thinking\u2026";
@@ -195,6 +198,15 @@ export interface BotRunDeps {
195
198
  * thread are recorded through it. Unset leaves every owner DM on the model path.
196
199
  */
197
200
  decisions?: DecisionDmClient;
201
+ /**
202
+ * The bot's own sign-in check (runtime-sign-in-check.ts), run with the
203
+ * turn's environment at start, before each turn, and while signed out.
204
+ * Injected in tests; unset runs the runtime's status command. A runtime
205
+ * with no status command answers `signedIn: null` and turns run as before.
206
+ */
207
+ checkSignIn?: (env: NodeJS.ProcessEnv) => Promise<RuntimeSignInCheck>;
208
+ /** How often to ask the tool again while it is signed out (default SIGN_IN_CHECK_MS). */
209
+ signInCheckMs?: number;
198
210
  }
199
211
  export declare const KICKOFF_MESSAGE_ID = "kickoff";
200
212
  /**
@@ -217,6 +229,17 @@ export declare const KICKOFF_MESSAGE_ID = "kickoff";
217
229
  * Pure so the rule is unit-testable.
218
230
  */
219
231
  export declare function turnEnv(base: NodeJS.ProcessEnv, kind: BotKind, agentUid?: string, companySlug?: string): NodeJS.ProcessEnv;
232
+ /** The account name this process runs as, or null when the OS cannot say. */
233
+ export declare function osUserName(): string | null;
234
+ /**
235
+ * Claude Code keys its macOS Keychain login on `$USER` (falling back to the
236
+ * literal "unknown"), so a tool spawned from an environment without USER
237
+ * reads a different Keychain item than the owner's terminal and is signed
238
+ * out. launchd and the HQ app's launcher may leave it unset; fill it (and
239
+ * LOGNAME) from the account this process runs as. Never overrides a USER
240
+ * already set. Pure.
241
+ */
242
+ export declare function withOsUser(env: NodeJS.ProcessEnv, name?: string | null): NodeJS.ProcessEnv;
220
243
  /** The core worker HQ's setup bot runs. */
221
244
  export declare const SETUP_WORKER_ID = "setup";
222
245
  /**