@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.
- package/CHANGELOG.md +431 -0
- package/assets/bot-workers/setup/worker.yaml +91 -34
- package/dist/command-catalog.generated.d.ts +145 -19
- package/dist/command-catalog.generated.js +184 -21
- package/dist/commands/bot-jobs.d.ts +21 -18
- package/dist/commands/bot-jobs.js +127 -39
- package/dist/commands/bot.d.ts +4 -3
- package/dist/commands/bot.js +265 -140
- package/dist/commands/core-jobs-schedule.js +10 -0
- package/dist/commands/daemon.js +37 -0
- package/dist/commands/files.d.ts +41 -6
- package/dist/commands/files.js +108 -27
- package/dist/commands/pack-start-bot.d.ts +4 -6
- package/dist/commands/pack-start-bot.js +4 -8
- package/dist/commands/signals-run.d.ts +46 -0
- package/dist/commands/signals-run.js +180 -0
- package/dist/commands/signals.d.ts +1 -0
- package/dist/commands/signals.js +3 -0
- package/dist/commands/sources.d.ts +58 -0
- package/dist/commands/sources.js +294 -26
- package/dist/commands/staff/reset-test-account.d.ts +10 -0
- package/dist/commands/staff/reset-test-account.js +18 -2
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +35 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/api.d.ts +3 -2
- package/dist/lib/bot/config.d.ts +2 -0
- package/dist/lib/bot/fleet-cron.d.ts +121 -0
- package/dist/lib/bot/fleet-cron.js +319 -0
- package/dist/lib/bot/fleet-local.d.ts +93 -1
- package/dist/lib/bot/fleet-local.js +546 -34
- package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
- package/dist/lib/bot/fleet-owner-companies.js +30 -0
- package/dist/lib/bot/fleet-watch.d.ts +25 -0
- package/dist/lib/bot/fleet-watch.js +51 -0
- package/dist/lib/bot/index.d.ts +1 -1
- package/dist/lib/bot/index.js +1 -1
- package/dist/lib/bot/job-run.d.ts +5 -4
- package/dist/lib/bot/job-run.js +5 -4
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
- package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
- package/dist/lib/bot/local-jobs.d.ts +13 -2
- package/dist/lib/bot/local-jobs.js +17 -4
- package/dist/lib/bot/progress.d.ts +14 -0
- package/dist/lib/bot/progress.js +47 -1
- package/dist/lib/bot/run.d.ts +55 -2
- package/dist/lib/bot/run.js +192 -10
- package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
- package/dist/lib/bot/setup-tool-offer.js +16 -0
- package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
- package/dist/lib/bot/signals-engine-flag.js +30 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +29 -9
- package/dist/lib/daemon/legacy.d.ts +6 -0
- package/dist/lib/daemon/legacy.js +16 -10
- package/dist/lib/daemon/run.d.ts +1 -0
- package/dist/lib/daemon/run.js +10 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.d.ts +13 -1
- package/dist/lib/flag-registry.js +46 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
- package/dist/lib/hot-channels/adapters/attio.js +367 -29
- package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
- package/dist/lib/hot-channels/adapters/gmail.js +59 -2
- package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
- package/dist/lib/hot-channels/adapters/linear.js +71 -1
- package/dist/lib/hot-channels/adapters/meetings.js +4 -1
- package/dist/lib/hot-channels/adapters/slack.js +7 -3
- package/dist/lib/hot-channels/alert.js +1 -1
- package/dist/lib/hot-channels/contract.d.ts +11 -0
- package/dist/lib/hot-channels/contract.js +1 -0
- package/dist/lib/hot-channels/cursors.js +1 -1
- package/dist/lib/hot-channels/landing.d.ts +6 -2
- package/dist/lib/hot-channels/landing.js +13 -6
- package/dist/lib/hot-channels/ledger.js +2 -0
- package/dist/lib/hot-channels/loop.d.ts +6 -0
- package/dist/lib/hot-channels/loop.js +19 -4
- package/dist/lib/hot-channels/registry.js +9 -1
- package/dist/lib/hot-channels/run.d.ts +18 -0
- package/dist/lib/hot-channels/run.js +15 -4
- package/dist/lib/hot-channels/signals-pass.d.ts +34 -2
- package/dist/lib/hot-channels/signals-pass.js +149 -38
- package/dist/lib/hot-channels/signals-redo.d.ts +60 -0
- package/dist/lib/hot-channels/signals-redo.js +207 -0
- package/dist/lib/hot-channels/sync-lock.js +22 -6
- package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
- package/dist/lib/mesh/live/daemon/install.js +117 -30
- package/dist/lib/signals/adapters-catalog.d.ts +77 -0
- package/dist/lib/signals/adapters-catalog.js +194 -0
- package/dist/lib/signals/engine.d.ts +202 -0
- package/dist/lib/signals/engine.js +504 -0
- package/dist/lib/signals/failure-alert.d.ts +89 -0
- package/dist/lib/signals/failure-alert.js +175 -0
- package/dist/lib/signals/local-date.d.ts +3 -0
- package/dist/lib/signals/local-date.js +6 -0
- package/dist/lib/signals/projection.d.ts +42 -0
- package/dist/lib/signals/projection.js +67 -0
- package/dist/lib/signals/reply.d.ts +112 -0
- package/dist/lib/signals/reply.js +368 -0
- package/dist/lib/signals/run-lock.d.ts +36 -0
- package/dist/lib/signals/run-lock.js +148 -0
- package/dist/lib/signals/run-record.d.ts +24 -0
- package/dist/lib/signals/run-record.js +82 -0
- package/dist/lib/signals/sanitize.d.ts +105 -0
- package/dist/lib/signals/sanitize.js +439 -0
- package/dist/lib/signals/settings.d.ts +10 -0
- package/dist/lib/signals/settings.js +48 -0
- package/dist/lib/signals/steps/adapters.d.ts +14 -0
- package/dist/lib/signals/steps/adapters.js +156 -0
- package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
- package/dist/lib/signals/steps/entity-similarity.js +47 -0
- package/dist/lib/signals/steps/extract.d.ts +118 -0
- package/dist/lib/signals/steps/extract.js +793 -0
- package/dist/lib/signals/steps/garden.d.ts +24 -0
- package/dist/lib/signals/steps/garden.js +250 -0
- package/dist/lib/signals/steps/types.d.ts +117 -0
- package/dist/lib/signals/steps/types.js +4 -0
- package/dist/lib/signals/steps/upload.d.ts +43 -0
- package/dist/lib/signals/steps/upload.js +324 -0
- package/dist/lib/signals/write-guard.d.ts +63 -0
- package/dist/lib/signals/write-guard.js +287 -0
- package/dist/utils/resident-package-preload.js +16 -0
- package/dist/utils/vault-api.d.ts +1 -0
- package/dist/utils/vault-api.js +1 -1
- package/package.json +9 -9
- package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
- 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.
|
|
14
|
-
*
|
|
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
|
|
49
|
+
* Standard 5-field cron → the six-field `cron(...)` string a job is stored as.
|
|
47
50
|
*
|
|
48
|
-
*
|
|
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
|
-
//
|
|
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:
|
|
63
|
-
// year, Quartz's first is leading seconds. Guessing wrong
|
|
64
|
-
// every run. Make the caller say which they mean.
|
|
65
|
-
refuse(`"${trimmed}" has six fields, and I can't tell whether
|
|
66
|
-
`(minute hour day-of-month month day-of-week year
|
|
67
|
-
`reading it the wrong way would schedule the job
|
|
68
|
-
`("0 9 * * 1-5"), or wrap it yourself as cron(...) if it
|
|
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) →
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
401
|
+
* Turn a jobs failure into one plain line.
|
|
318
402
|
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
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
|
|
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() ||
|
|
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;
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -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;
|