@indigoai-us/hq-cli 5.345.191 → 5.345.194

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 (129) hide show
  1. package/CHANGELOG.md +431 -0
  2. package/assets/bot-workers/setup/worker.yaml +91 -34
  3. package/dist/command-catalog.generated.d.ts +145 -19
  4. package/dist/command-catalog.generated.js +184 -21
  5. package/dist/commands/bot-jobs.d.ts +21 -18
  6. package/dist/commands/bot-jobs.js +127 -39
  7. package/dist/commands/bot.d.ts +4 -3
  8. package/dist/commands/bot.js +265 -140
  9. package/dist/commands/core-jobs-schedule.js +10 -0
  10. package/dist/commands/daemon.js +37 -0
  11. package/dist/commands/files.d.ts +41 -6
  12. package/dist/commands/files.js +108 -27
  13. package/dist/commands/pack-start-bot.d.ts +4 -6
  14. package/dist/commands/pack-start-bot.js +4 -8
  15. package/dist/commands/signals-run.d.ts +46 -0
  16. package/dist/commands/signals-run.js +180 -0
  17. package/dist/commands/signals.d.ts +1 -0
  18. package/dist/commands/signals.js +3 -0
  19. package/dist/commands/sources.d.ts +58 -0
  20. package/dist/commands/sources.js +294 -26
  21. package/dist/commands/staff/reset-test-account.d.ts +10 -0
  22. package/dist/commands/staff/reset-test-account.js +18 -2
  23. package/dist/commands/whoami.d.ts +7 -0
  24. package/dist/commands/whoami.js +35 -2
  25. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
  26. package/dist/hq-cloud-sync-runner/package.json +1 -1
  27. package/dist/lib/bot/api.d.ts +3 -2
  28. package/dist/lib/bot/config.d.ts +2 -0
  29. package/dist/lib/bot/fleet-cron.d.ts +121 -0
  30. package/dist/lib/bot/fleet-cron.js +319 -0
  31. package/dist/lib/bot/fleet-local.d.ts +93 -1
  32. package/dist/lib/bot/fleet-local.js +546 -34
  33. package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
  34. package/dist/lib/bot/fleet-owner-companies.js +30 -0
  35. package/dist/lib/bot/fleet-watch.d.ts +25 -0
  36. package/dist/lib/bot/fleet-watch.js +51 -0
  37. package/dist/lib/bot/index.d.ts +1 -1
  38. package/dist/lib/bot/index.js +1 -1
  39. package/dist/lib/bot/job-run.d.ts +5 -4
  40. package/dist/lib/bot/job-run.js +5 -4
  41. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
  42. package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
  43. package/dist/lib/bot/local-jobs.d.ts +13 -2
  44. package/dist/lib/bot/local-jobs.js +17 -4
  45. package/dist/lib/bot/progress.d.ts +14 -0
  46. package/dist/lib/bot/progress.js +47 -1
  47. package/dist/lib/bot/run.d.ts +55 -2
  48. package/dist/lib/bot/run.js +192 -10
  49. package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
  50. package/dist/lib/bot/setup-tool-offer.js +16 -0
  51. package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
  52. package/dist/lib/bot/signals-engine-flag.js +30 -0
  53. package/dist/lib/daemon/catalog.d.ts +4 -0
  54. package/dist/lib/daemon/catalog.js +29 -9
  55. package/dist/lib/daemon/legacy.d.ts +6 -0
  56. package/dist/lib/daemon/legacy.js +16 -10
  57. package/dist/lib/daemon/run.d.ts +1 -0
  58. package/dist/lib/daemon/run.js +10 -0
  59. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  60. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  61. package/dist/lib/flag-registry.d.ts +13 -1
  62. package/dist/lib/flag-registry.js +46 -0
  63. package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
  64. package/dist/lib/hot-channels/adapters/attio.js +367 -29
  65. package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
  66. package/dist/lib/hot-channels/adapters/gmail.js +59 -2
  67. package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
  68. package/dist/lib/hot-channels/adapters/linear.js +71 -1
  69. package/dist/lib/hot-channels/adapters/meetings.js +4 -1
  70. package/dist/lib/hot-channels/adapters/slack.js +7 -3
  71. package/dist/lib/hot-channels/alert.js +1 -1
  72. package/dist/lib/hot-channels/contract.d.ts +11 -0
  73. package/dist/lib/hot-channels/contract.js +1 -0
  74. package/dist/lib/hot-channels/cursors.js +1 -1
  75. package/dist/lib/hot-channels/landing.d.ts +6 -2
  76. package/dist/lib/hot-channels/landing.js +13 -6
  77. package/dist/lib/hot-channels/ledger.js +2 -0
  78. package/dist/lib/hot-channels/loop.d.ts +6 -0
  79. package/dist/lib/hot-channels/loop.js +19 -4
  80. package/dist/lib/hot-channels/registry.js +9 -1
  81. package/dist/lib/hot-channels/run.d.ts +18 -0
  82. package/dist/lib/hot-channels/run.js +15 -4
  83. package/dist/lib/hot-channels/signals-pass.d.ts +34 -2
  84. package/dist/lib/hot-channels/signals-pass.js +149 -38
  85. package/dist/lib/hot-channels/signals-redo.d.ts +60 -0
  86. package/dist/lib/hot-channels/signals-redo.js +207 -0
  87. package/dist/lib/hot-channels/sync-lock.js +22 -6
  88. package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
  89. package/dist/lib/mesh/live/daemon/install.js +117 -30
  90. package/dist/lib/signals/adapters-catalog.d.ts +77 -0
  91. package/dist/lib/signals/adapters-catalog.js +194 -0
  92. package/dist/lib/signals/engine.d.ts +202 -0
  93. package/dist/lib/signals/engine.js +504 -0
  94. package/dist/lib/signals/failure-alert.d.ts +89 -0
  95. package/dist/lib/signals/failure-alert.js +175 -0
  96. package/dist/lib/signals/local-date.d.ts +3 -0
  97. package/dist/lib/signals/local-date.js +6 -0
  98. package/dist/lib/signals/projection.d.ts +42 -0
  99. package/dist/lib/signals/projection.js +67 -0
  100. package/dist/lib/signals/reply.d.ts +112 -0
  101. package/dist/lib/signals/reply.js +368 -0
  102. package/dist/lib/signals/run-lock.d.ts +36 -0
  103. package/dist/lib/signals/run-lock.js +148 -0
  104. package/dist/lib/signals/run-record.d.ts +24 -0
  105. package/dist/lib/signals/run-record.js +82 -0
  106. package/dist/lib/signals/sanitize.d.ts +105 -0
  107. package/dist/lib/signals/sanitize.js +439 -0
  108. package/dist/lib/signals/settings.d.ts +10 -0
  109. package/dist/lib/signals/settings.js +48 -0
  110. package/dist/lib/signals/steps/adapters.d.ts +14 -0
  111. package/dist/lib/signals/steps/adapters.js +156 -0
  112. package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
  113. package/dist/lib/signals/steps/entity-similarity.js +47 -0
  114. package/dist/lib/signals/steps/extract.d.ts +118 -0
  115. package/dist/lib/signals/steps/extract.js +793 -0
  116. package/dist/lib/signals/steps/garden.d.ts +24 -0
  117. package/dist/lib/signals/steps/garden.js +250 -0
  118. package/dist/lib/signals/steps/types.d.ts +117 -0
  119. package/dist/lib/signals/steps/types.js +4 -0
  120. package/dist/lib/signals/steps/upload.d.ts +43 -0
  121. package/dist/lib/signals/steps/upload.js +324 -0
  122. package/dist/lib/signals/write-guard.d.ts +63 -0
  123. package/dist/lib/signals/write-guard.js +287 -0
  124. package/dist/utils/resident-package-preload.js +16 -0
  125. package/dist/utils/vault-api.d.ts +1 -0
  126. package/dist/utils/vault-api.js +1 -1
  127. package/package.json +9 -9
  128. package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
  129. package/dist/lib/bot/fleet-default-flag.js +0 -127
@@ -10,13 +10,16 @@
10
10
  * The schedule lives with the bot: its jobs sit in the bot's own folder
11
11
  * (lib/bot/local-jobs.ts) and the bot's inbox loop fires them while it runs
12
12
  * (see lib/bot/job-run.ts). Nothing here writes a crontab, a launchd plist, or
13
- * a systemd unit — by rule. Schedules are still written in the EventBridge form
14
- * and held to the same limits, so a job reads the same wherever it runs.
13
+ * a systemd unit — by rule. A schedule is stored as a six-field
14
+ * `cron(m h dom mon dow year)` string (Sunday = 1, `?` in one day field), the
15
+ * same string format hosted agent jobs use, and lib/bot/local-jobs.ts reads it
16
+ * back when it checks what is due.
15
17
  */
16
18
  import chalk from "chalk";
17
19
  import { localJobsClient } from "../lib/bot/local-jobs.js";
18
20
  import { BotApiError } from "../lib/bot/api.js";
19
- import { effectiveBotKind } from "../lib/bot/config.js";
21
+ import { effectiveBotKind, HQ_FLEET_RUNTIME_ENGINE_ID } from "../lib/bot/config.js";
22
+ import { fleetCronOps, fleetOwnerOrigin, fleetScheduleFromLegacy } from "../lib/bot/fleet-cron.js";
20
23
  import { isQuietJobContext, JOB_RUN_OPTIONS_CONTEXT_KEY } from "../lib/bot/job-run.js";
21
24
  import { BOT_JOB_QUIET_OPTION_FLAG, botJobQuietOptionEnabled } from "../lib/bot/job-quiet-flag.js";
22
25
  import { runScheduleParse } from "./core-jobs-schedule.js";
@@ -43,9 +46,11 @@ export function machineTimezone() {
43
46
  }
44
47
  }
45
48
  /**
46
- * Standard 5-field cron → the EventBridge form the cloud stores.
49
+ * Standard 5-field cron → the six-field `cron(...)` string a job is stored as.
47
50
  *
48
- * EventBridge cron has SIX fields (a trailing year), numbers its days of week
51
+ * The stored form (the six-field cron syntax hosted agent jobs also use, so
52
+ * local and hosted jobs share one format; the function keeps its historical
53
+ * name) has SIX fields (a trailing year), numbers its days of week
49
54
  * 1–7 with SUNDAY = 1 (standard cron uses 0–6 with SUNDAY = 0), and refuses
50
55
  * `*` in BOTH the day-of-month and day-of-week fields — exactly one must be
51
56
  * `?`. An expression that is already `cron(...)` or `rate(...)` is passed
@@ -54,18 +59,19 @@ export function machineTimezone() {
54
59
  export function toEventBridgeCron(expr) {
55
60
  const trimmed = expr.trim();
56
61
  // An explicit cron(...) / rate(...) is the caller saying "this is already the
57
- // cloud's own syntax" — passed through untranslated, on purpose.
62
+ // stored syntax" — passed through untranslated, on purpose.
58
63
  if (/^(cron|rate)\s*\(/i.test(trimmed))
59
64
  return trimmed;
60
65
  const fields = trimmed.split(/\s+/).filter(Boolean);
61
66
  if (fields.length === 6) {
62
- // Six BARE fields are ambiguous: EventBridge's sixth field is a trailing
63
- // year, Quartz's first is leading seconds. Guessing wrong silently moves
64
- // every run. Make the caller say which they mean.
65
- refuse(`"${trimmed}" has six fields, and I can't tell whether that is an EventBridge expression ` +
66
- `(minute hour day-of-month month day-of-week year) or a Quartz one (leading seconds) — ` +
67
- `reading it the wrong way would schedule the job at the wrong time. Pass five fields ` +
68
- `("0 9 * * 1-5"), or wrap it yourself as cron(...) if it is already an EventBridge expression.`);
67
+ // Six BARE fields are ambiguous: the stored form's sixth field is a
68
+ // trailing year, Quartz's first is leading seconds. Guessing wrong
69
+ // silently moves every run. Make the caller say which they mean.
70
+ refuse(`"${trimmed}" has six fields, and I can't tell whether the last one is a year ` +
71
+ `(minute hour day-of-month month day-of-week year, the form bot jobs are stored in) or the ` +
72
+ `first one is seconds (a Quartz expression) — reading it the wrong way would schedule the job ` +
73
+ `at the wrong time. Pass five fields ("0 9 * * 1-5"), or wrap it yourself as cron(...) if it ` +
74
+ `already ends with a year field.`);
69
75
  }
70
76
  if (fields.length !== 5) {
71
77
  refuse(`"${trimmed}" is not a cron expression. A cron expression has five fields: ` +
@@ -87,7 +93,7 @@ export function toEventBridgeCron(expr) {
87
93
  return `cron(${minute} ${hour} ${dayOfMonth} ${month} ${dayOfWeek} *)`;
88
94
  }
89
95
  /**
90
- * Standard 0–6 (SUN=0, and 7 also SUN) → EventBridge 1–7 (SUN=1).
96
+ * Standard 0–6 (SUN=0, and 7 also SUN) → the stored form's 1–7 (SUN=1).
91
97
  *
92
98
  * Mapped per TERM, not per digit. Shifting digits individually turned a range
93
99
  * that wraps past Sunday — "6-0" (Sat–Sun), "5-1" (Fri–Mon) — into "7-1" /
@@ -155,7 +161,7 @@ export function recurringIntervalSeconds(expr) {
155
161
  const minute = cron[1];
156
162
  if (minute === "*" || minute === "?")
157
163
  return 60;
158
- // EventBridge also writes a step as "start/every" ("0/10" = every 10 minutes
164
+ // The stored form also writes a step as "start/every" ("0/10" = every 10 minutes
159
165
  // from :00), which the rate()-style regex alone rejected as unparseable.
160
166
  const step = minute.match(/^(?:\*|\d+|\d+-\d+)\/(\d+)$/);
161
167
  if (step) {
@@ -177,7 +183,7 @@ export function recurringIntervalSeconds(expr) {
177
183
  }
178
184
  return null;
179
185
  }
180
- /** Refuse a schedule the cloud would refuse, with the cloud's own reason. */
186
+ /** Refuse a schedule that fires more often than the recurring floor (the same floor hosted agent jobs use). */
181
187
  export function assertScheduleWithinFloor(expr) {
182
188
  const interval = recurringIntervalSeconds(expr);
183
189
  if (interval === null) {
@@ -212,7 +218,7 @@ export function parseScheduleText(text, opts) {
212
218
  ...(parsed.human?.trim() ? { human: parsed.human.trim() } : {}),
213
219
  };
214
220
  }
215
- /** The create body the cloud expects, with the client-side limits enforced first. */
221
+ /** The create body for a new job, with the schedule limits enforced first. */
216
222
  export function buildCreateJobBody(input) {
217
223
  const prompt = input.prompt.trim();
218
224
  if (!prompt)
@@ -236,6 +242,79 @@ export function buildCreateJobBody(input) {
236
242
  schedule: { kind: "recurring", cron, timezone: input.timezone },
237
243
  };
238
244
  }
245
+ export function fromEventBridgeCron(expr) {
246
+ try {
247
+ return fleetScheduleFromLegacy(expr);
248
+ }
249
+ catch (error) {
250
+ refuse(error instanceof Error ? error.message : String(error));
251
+ }
252
+ }
253
+ function fleetJobStatus(job) {
254
+ if (job.enabled === false)
255
+ return "paused";
256
+ if (!job.state || job.state === "scheduled")
257
+ return "active";
258
+ return job.state;
259
+ }
260
+ function fleetJobSchedule(job, timezone) {
261
+ const schedule = job.schedule ?? {};
262
+ if (schedule.kind === "once")
263
+ return { kind: "once", ...(schedule.run_at ? { at: schedule.run_at } : {}), timezone };
264
+ if (schedule.kind === "interval" && typeof schedule.minutes === "number") {
265
+ return { kind: "recurring", cron: `rate(${schedule.minutes} minutes)`, timezone };
266
+ }
267
+ const expr = schedule.expr ?? "";
268
+ try {
269
+ return { kind: "recurring", cron: toEventBridgeCron(expr), timezone };
270
+ }
271
+ catch {
272
+ return { kind: "recurring", cron: expr, timezone };
273
+ }
274
+ }
275
+ export function fleetJobRecord(job, timezone) {
276
+ return {
277
+ jobId: job.id,
278
+ prompt: job.prompt?.trim() || job.name || (job.script ? `runs ${job.script}` : ""),
279
+ status: fleetJobStatus(job),
280
+ schedule: fleetJobSchedule(job, timezone),
281
+ ...(job.script ? { script: job.script } : {}),
282
+ ...(job.created_at ? { createdAt: job.created_at } : {}),
283
+ ...(job.next_run_at ? { nextRunAt: job.next_run_at } : {}),
284
+ ...(job.last_run_at ? { lastRunAt: job.last_run_at } : {}),
285
+ ...(job.last_status ? { lastStatus: job.last_status } : {}),
286
+ ...(job.repeat?.completed ? { runCount: job.repeat.completed } : {}),
287
+ };
288
+ }
289
+ export function fleetJobsClient(config, ops, timezone) {
290
+ return {
291
+ async listJobs() {
292
+ return ops.list().map((job) => fleetJobRecord(job, timezone));
293
+ },
294
+ async createJob(_agentUid, input) {
295
+ if (input.schedule.kind !== "recurring") {
296
+ throw new BotApiError(400, "fleet jobs", "JOB_VALIDATION", "A local bot runs recurring jobs only.");
297
+ }
298
+ const schedule = fromEventBridgeCron(input.schedule.cron);
299
+ const jobs = ops.list();
300
+ if (jobs.filter((job) => job.enabled !== false).length >= MAX_ACTIVE_JOBS_PER_AGENT) {
301
+ throw new BotApiError(409, "fleet jobs", "JOB_QUOTA_EXCEEDED", `at most ${MAX_ACTIVE_JOBS_PER_AGENT} jobs`);
302
+ }
303
+ const job = ops.create({ prompt: input.prompt, schedule, origin: fleetOwnerOrigin(config.ownerUid, jobs), workdir: config.hqRoot });
304
+ return fleetJobRecord(job, timezone);
305
+ },
306
+ async cancelJob(_agentUid, jobId) {
307
+ if (!ops.remove(jobId))
308
+ throw new BotApiError(404, "fleet jobs", "JOB_NOT_FOUND", `Job '${jobId}' not found`);
309
+ },
310
+ async rescheduleJob(_agentUid, jobId, schedule) {
311
+ const job = ops.reschedule(jobId, fromEventBridgeCron(schedule.cron));
312
+ if (!job)
313
+ throw new BotApiError(404, "fleet jobs", "JOB_NOT_FOUND", `Job '${jobId}' not found`);
314
+ return fleetJobRecord(job, timezone);
315
+ },
316
+ };
317
+ }
239
318
  // ─── Reading jobs back ───────────────────────────────────────────────────────
240
319
  /** A schedule in plain words, falling back to the raw expression. */
241
320
  export function describeSchedule(job) {
@@ -277,7 +356,7 @@ export function cronInWords(expr) {
277
356
  return `on day ${dayOfMonth} of each month at ${at}`;
278
357
  return null;
279
358
  }
280
- // EventBridge days of week: SUNDAY = 1.
359
+ // Stored-form days of week: SUNDAY = 1.
281
360
  if (dayOfWeek === "2-6")
282
361
  return `every weekday at ${at}`;
283
362
  if (/^\d$/.test(dayOfWeek))
@@ -288,11 +367,16 @@ export function cronInWords(expr) {
288
367
  }
289
368
  return null;
290
369
  }
370
+ export function utcMinute(iso) {
371
+ const time = Date.parse(iso);
372
+ const value = Number.isNaN(time) ? iso : new Date(time).toISOString();
373
+ return value.slice(0, 16).replace("T", " ");
374
+ }
291
375
  /** "never run yet" / "succeeded 2026-09-15 09:00 UTC". */
292
376
  export function describeLastRun(job) {
293
377
  if (!job.lastStatus && !job.lastRunAt)
294
378
  return "not run yet";
295
- const when = job.lastRunAt ? ` ${job.lastRunAt.slice(0, 16).replace("T", " ")} UTC` : "";
379
+ const when = job.lastRunAt ? ` ${utcMinute(job.lastRunAt)} UTC` : "";
296
380
  return `${job.lastStatus ?? "ran"}${when}`;
297
381
  }
298
382
  export function formatJobsList(botName, jobs, showQuietOption = false) {
@@ -307,35 +391,23 @@ export function formatJobsList(botName, jobs, showQuietOption = false) {
307
391
  lines.push(` runs ${describeSchedule(job)}`);
308
392
  if (showQuietOption && isQuietJobContext(job.context))
309
393
  lines.push(" delivery: quiet after successful runs");
310
- lines.push(` next run: ${job.nextRunAt ? `${job.nextRunAt.slice(0, 16).replace("T", " ")} UTC` : "not scheduled yet"}`);
394
+ lines.push(` next run: ${job.nextRunAt ? `${utcMinute(job.nextRunAt)} UTC` : "not scheduled yet"}`);
311
395
  lines.push(` last run: ${describeLastRun(job)}`);
312
396
  }
313
397
  return lines.join("\n");
314
398
  }
315
399
  // ─── Errors a person can act on ──────────────────────────────────────────────
316
400
  /**
317
- * Turn a jobs-route failure into one plain line.
401
+ * Turn a jobs failure into one plain line.
318
402
  *
319
- * Personal bots are refused by the cloud today: their JWT carries the owner's
320
- * `per_…` company claim and the jobs routes require a `cmp_…`, so the fail-closed
321
- * guard answers 403 `CROSS_AGENT_JOB` (hq-pro-agents jobs/errors.ts
322
- * `CrossAgentJobError`). That server fix is a separate change; until it lands the
323
- * refusal must read as "not available yet for this kind of bot", not as a raw 403.
324
- * Once the server accepts personal bots the refusal simply stops happening — no
325
- * client change needed.
326
- *
327
- * ONLY that exact code is rewritten. Every other 403, and every 404, keeps its
328
- * real message: masking them would hide a genuinely missing job or a different
329
- * permission problem behind copy about the wrong thing.
403
+ * `hq bot jobs` answers from the bot's own jobs.json (localJobsClient), for
404
+ * personal and company bots alike, so there is no cloud refusal to explain
405
+ * here. Only the local client's own errors (quota, unknown job, validation)
406
+ * get rewritten; anything else keeps its real message.
330
407
  */
331
408
  export function jobsErrorMessage(err, ctx) {
332
409
  if (!(err instanceof BotApiError))
333
410
  return err instanceof Error ? err.message : String(err);
334
- if (ctx.kind === "personal" && err.status === 403 && err.code === "CROSS_AGENT_JOB") {
335
- return (`Scheduled jobs aren't available for personal bots yet — the cloud only accepts them from ` +
336
- `company bots. Create ${ctx.botName} as a company bot (hq bot create ${ctx.botName} --kind company ` +
337
- `--company <slug>) to schedule work, or wait for personal-bot support.`);
338
- }
339
411
  if (err.status === 401)
340
412
  return "Not authenticated — the bot's credentials were refused. Try: hq bot restart " + ctx.botName;
341
413
  if (err.code === "JOB_QUOTA_EXCEEDED" || err.status === 409) {
@@ -355,13 +427,25 @@ export async function runBotJobs(nameArg, opts, deps) {
355
427
  const out = deps.out ?? ((line) => console.log(line));
356
428
  const ctx = { botName: name, kind };
357
429
  const quietOptionEnabled = deps.quietOptionEnabled ?? botJobQuietOptionEnabled;
430
+ const fleet = config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
431
+ const localZone = (deps.timezone ?? machineTimezone)();
432
+ if (fleet && opts.quiet) {
433
+ refuse(`--quiet is not available for ${name}: it runs on the HQ Fleet Runtime, which posts every run's result to your HQ DM.`);
434
+ }
435
+ if (fleet && opts.tz?.trim() && opts.tz.trim() !== localZone) {
436
+ refuse(`${name} runs on the HQ Fleet Runtime, which runs jobs in this computer's time zone (${localZone}). Drop --tz.`);
437
+ }
358
438
  // The schedule lives with the bot: this bot runs on this computer, so its
359
439
  // jobs live in its folder and it fires them itself.
360
440
  let client;
361
441
  const jobsApi = async () => {
362
442
  if (client)
363
443
  return client;
364
- client = deps.apiFor ? deps.apiFor(dir) : localJobsClient(dir);
444
+ client = deps.apiFor
445
+ ? deps.apiFor(dir)
446
+ : fleet
447
+ ? fleetJobsClient(config, (deps.fleetCron ?? fleetCronOps)(config.agentUid), localZone)
448
+ : localJobsClient(dir);
365
449
  return client;
366
450
  };
367
451
  if ([opts.add, opts.rm, opts.reschedule].filter((value) => value !== undefined).length > 1) {
@@ -378,7 +462,7 @@ export async function runBotJobs(nameArg, opts, deps) {
378
462
  }
379
463
  if (opts.every && opts.cron)
380
464
  refuse("Use --every or --cron, not both.");
381
- const timezone = opts.tz?.trim() || (deps.timezone ?? machineTimezone)();
465
+ const timezone = opts.tz?.trim() || localZone;
382
466
  // An unknown zone would throw RangeError on every inbox poll; refuse it now.
383
467
  try {
384
468
  new Intl.DateTimeFormat("en-US", { timeZone: timezone });
@@ -448,6 +532,10 @@ export async function runBotJobs(nameArg, opts, deps) {
448
532
  }
449
533
  const words = schedule.human || describeSchedule(created);
450
534
  out(chalk.green(`Scheduled ${created.jobId}: ${name} will ${body.prompt.replace(/\s+/g, " ").slice(0, 120)} — ${words}.`));
535
+ if (fleet) {
536
+ out(chalk.gray(`It runs on this computer while ${name}'s HQ Fleet gateway is running, and posts the result to your HQ DM.`));
537
+ return;
538
+ }
451
539
  out(chalk.gray(`It runs on this computer while ${name} is running. If a run is due while the computer is asleep or ` +
452
540
  `${name} is stopped, it runs once, as a catch-up, the next time ${name} is running.`));
453
541
  return;
@@ -145,7 +145,6 @@ export interface BotCreateOptions {
145
145
  hqFleetRuntime?: boolean;
146
146
  legacyRuntime?: boolean;
147
147
  model?: string;
148
- effort?: string;
149
148
  autoApprove?: boolean;
150
149
  daemon?: boolean;
151
150
  start?: boolean;
@@ -157,8 +156,6 @@ export interface BotCreateOptions {
157
156
  memory?: string;
158
157
  kind?: string;
159
158
  company?: string[];
160
- /** Run the bot lean: no HQ hooks, MCP servers or setting sources (pack setup bots). */
161
- lean?: boolean;
162
159
  }
163
160
  /**
164
161
  * Which way `hq bot create` went. For HQ's setup assistant all three are a
@@ -243,6 +240,10 @@ export declare function runBotProbe(opts: {
243
240
  export declare function runBotIntro(nameArg: string): Promise<void>;
244
241
  /** True when a bot of this name exists on this computer (its bot.json reads). */
245
242
  export declare function botExists(name: string): boolean;
243
+ export type FleetRuntimeUpdateOutcome = "unchanged" | "updated" | "waiting";
244
+ export declare function updateFleetRuntimeBot(nameArg: string, opts?: {
245
+ busy?: () => string | null;
246
+ }): Promise<FleetRuntimeUpdateOutcome>;
246
247
  /** `hq bot restart <name>`: stop, then start (launchd when installed). */
247
248
  export declare function runBotRestart(nameArg: string, opts?: {
248
249
  json?: boolean;