@indigoai-us/hq-cli 5.345.198 → 5.345.200
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 +71 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +150 -6
- package/dist/command-catalog.generated.js +182 -6
- package/dist/commands/bot-events.d.ts +19 -0
- package/dist/commands/bot-events.js +46 -0
- package/dist/commands/bot-jobs.d.ts +2 -0
- package/dist/commands/bot-jobs.js +43 -2
- package/dist/commands/bot-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +11 -0
- package/dist/commands/bot.js +72 -11
- package/dist/commands/creators.js +2 -1
- package/dist/commands/lanes-report.d.ts +2 -1
- package/dist/commands/lanes-report.js +44 -1
- package/dist/commands/lanes.d.ts +9 -0
- package/dist/commands/lanes.js +95 -9
- package/dist/commands/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/commands/sync-held.js +2 -2
- package/dist/lanes-drain-hook.js +9 -0
- package/dist/lib/bot/api.d.ts +5 -0
- package/dist/lib/bot/claude-login.d.ts +31 -0
- package/dist/lib/bot/claude-login.js +160 -0
- package/dist/lib/bot/events.d.ts +18 -0
- package/dist/lib/bot/events.js +160 -0
- package/dist/lib/bot/executive-settings.d.ts +10 -0
- package/dist/lib/bot/executive-settings.js +43 -0
- package/dist/lib/bot/fleet-cron.js +4 -0
- package/dist/lib/bot/fleet-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +51 -2
- package/dist/lib/bot/fleet-local.js +179 -76
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- package/dist/lib/bot/job-run.js +1 -1
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
- package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
- package/dist/lib/bot/jobs/meeting-detect.js +176 -0
- package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
- package/dist/lib/bot/jobs/meeting-rider.js +284 -0
- package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
- package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
- package/dist/lib/bot/local-jobs.d.ts +15 -2
- package/dist/lib/bot/local-jobs.js +24 -7
- package/dist/lib/bot/run.d.ts +7 -49
- package/dist/lib/bot/run.js +100 -4
- package/dist/lib/bot/status.d.ts +21 -0
- package/dist/lib/doctor/checks/bots.d.ts +6 -0
- package/dist/lib/doctor/checks/bots.js +47 -1
- package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
- package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
- package/dist/lib/hot-channels/schedule.js +2 -2
- package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
- package/dist/lib/hot-channels/signals-pass.js +51 -17
- package/dist/lib/lanes/admission.d.ts +2 -0
- package/dist/lib/lanes/admission.js +4 -0
- package/dist/lib/lanes/approval-gate.js +4 -1
- package/dist/lib/lanes/links.d.ts +8 -1
- package/dist/lib/lanes/links.js +84 -3
- package/dist/lib/lanes/mesh-events.js +3 -1
- package/dist/lib/lanes/outcome.d.ts +154 -0
- package/dist/lib/lanes/outcome.js +151 -0
- package/dist/lib/lanes/reconcile.js +1 -0
- package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
- package/dist/lib/lanes/signal-brief-template.js +40 -0
- package/dist/lib/lanes/signal-lane.d.ts +24 -0
- package/dist/lib/lanes/signal-lane.js +99 -0
- package/dist/lib/lanes/source-report.d.ts +37 -0
- package/dist/lib/lanes/source-report.js +66 -0
- package/dist/lib/lanes/spawn.d.ts +6 -0
- package/dist/lib/lanes/spawn.js +21 -0
- package/dist/lib/lanes/store.d.ts +3 -0
- package/dist/lib/lanes/store.js +64 -0
- package/dist/lib/lanes/story-ledger.d.ts +3 -0
- package/dist/lib/lanes/story-ledger.js +1 -0
- package/dist/lib/lanes/types.d.ts +137 -0
- package/dist/lib/lanes/types.js +17 -2
- package/dist/lib/lanes/watcher.js +4 -0
- package/dist/lib/live-meetings.d.ts +19 -0
- package/dist/lib/live-meetings.js +23 -0
- package/dist/lib/mesh/api.js +2 -1
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- package/dist/lib/mesh/live/spool.d.ts +8 -0
- package/dist/lib/mesh/live/spool.js +59 -40
- package/dist/lib/plan-limit-nag.d.ts +30 -4
- package/dist/lib/plan-limit-nag.js +68 -17
- package/dist/lib/signals/engine.d.ts +4 -0
- package/dist/lib/signals/engine.js +25 -5
- package/dist/lib/signals/incremental.d.ts +42 -0
- package/dist/lib/signals/incremental.js +191 -0
- package/dist/lib/signals/prior-signals.d.ts +11 -0
- package/dist/lib/signals/prior-signals.js +44 -0
- package/dist/lib/signals/reply.js +6 -1
- package/dist/lib/signals/settings.d.ts +5 -0
- package/dist/lib/signals/settings.js +17 -0
- package/dist/lib/signals/steps/extract.d.ts +23 -2
- package/dist/lib/signals/steps/extract.js +142 -9
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +17 -0
- package/dist/utils/cognito-callback-port.js +56 -0
- package/dist/utils/cognito-config.js +7 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +19 -14
- package/dist/utils/interactive-caller.d.ts +23 -0
- package/dist/utils/interactive-caller.js +80 -0
- package/dist/utils/login-browser.js +5 -2
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +7 -7
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* back when it checks what is due.
|
|
17
17
|
*/
|
|
18
18
|
import chalk from "chalk";
|
|
19
|
-
import { localJobsClient } from "../lib/bot/local-jobs.js";
|
|
19
|
+
import { addLocalJob, localJobsClient, readLocalJobs, toJobRecord } from "../lib/bot/local-jobs.js";
|
|
20
20
|
import { BotApiError } from "../lib/bot/api.js";
|
|
21
21
|
import { effectiveBotKind, HQ_FLEET_RUNTIME_ENGINE_ID } from "../lib/bot/config.js";
|
|
22
22
|
import { fleetCronOps, fleetOwnerOrigin, fleetScheduleFromLegacy } from "../lib/bot/fleet-cron.js";
|
|
@@ -318,6 +318,10 @@ export function fleetJobsClient(config, ops, timezone) {
|
|
|
318
318
|
// ─── Reading jobs back ───────────────────────────────────────────────────────
|
|
319
319
|
/** A schedule in plain words, falling back to the raw expression. */
|
|
320
320
|
export function describeSchedule(job) {
|
|
321
|
+
if (job.trigger) {
|
|
322
|
+
const filters = Object.entries(job.trigger.filter ?? {}).map(([key, value]) => `${key}=${value}`).join(", ");
|
|
323
|
+
return `on ${job.trigger.event}${filters ? ` (${filters})` : ""}`;
|
|
324
|
+
}
|
|
321
325
|
const schedule = job.schedule;
|
|
322
326
|
if (!schedule)
|
|
323
327
|
return "on a schedule";
|
|
@@ -391,7 +395,7 @@ export function formatJobsList(botName, jobs, showQuietOption = false) {
|
|
|
391
395
|
lines.push(` runs ${describeSchedule(job)}`);
|
|
392
396
|
if (showQuietOption && isQuietJobContext(job.context))
|
|
393
397
|
lines.push(" delivery: quiet after successful runs");
|
|
394
|
-
lines.push(` next run: ${job.nextRunAt ? `${utcMinute(job.nextRunAt)} UTC` : "not scheduled yet"}`);
|
|
398
|
+
lines.push(` next run: ${job.trigger ? "when a matching event arrives" : job.nextRunAt ? `${utcMinute(job.nextRunAt)} UTC` : "not scheduled yet"}`);
|
|
395
399
|
lines.push(` last run: ${describeLastRun(job)}`);
|
|
396
400
|
}
|
|
397
401
|
return lines.join("\n");
|
|
@@ -454,6 +458,11 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
454
458
|
if (opts.quiet && opts.add === undefined) {
|
|
455
459
|
refuse("Use --quiet with --add to set a scheduled job's delivery option.");
|
|
456
460
|
}
|
|
461
|
+
if (opts.on !== undefined && (opts.add === undefined || opts.every !== undefined || opts.cron !== undefined || opts.tz !== undefined)) {
|
|
462
|
+
refuse("Use --on with --add and without --every, --cron, or --tz.");
|
|
463
|
+
}
|
|
464
|
+
if (opts.filter?.length && opts.on === undefined)
|
|
465
|
+
refuse("Use --filter with --on.");
|
|
457
466
|
// Shared by --add and --reschedule: one schedule from --every or --cron, in
|
|
458
467
|
// the stored form, held to the same floor.
|
|
459
468
|
const scheduleFromOptions = () => {
|
|
@@ -516,6 +525,36 @@ export async function runBotJobs(nameArg, opts, deps) {
|
|
|
516
525
|
if (opts.quiet && !(await quietOptionEnabled())) {
|
|
517
526
|
refuse(`--quiet requires the ${BOT_JOB_QUIET_OPTION_FLAG} flag, which is currently disabled.`);
|
|
518
527
|
}
|
|
528
|
+
if (opts.on !== undefined) {
|
|
529
|
+
if (fleet)
|
|
530
|
+
refuse("Event jobs require a local CLI bot; HQ Fleet Runtime does not drain this event journal.");
|
|
531
|
+
const event = opts.on.trim();
|
|
532
|
+
if (!event || event.length > 256 || /[\r\n]/.test(event))
|
|
533
|
+
refuse("Event name must be 1–256 characters without newlines.");
|
|
534
|
+
const prompt = opts.add.trim();
|
|
535
|
+
if (!prompt || Buffer.byteLength(prompt, "utf8") > MAX_PROMPT_BYTES)
|
|
536
|
+
refuse("Job instruction must be non-empty and at most 16 KB.");
|
|
537
|
+
const filter = Object.create(null);
|
|
538
|
+
for (const entry of opts.filter ?? []) {
|
|
539
|
+
const split = entry.indexOf("=");
|
|
540
|
+
if (split < 1 || !entry.slice(0, split).trim())
|
|
541
|
+
refuse("Each --filter must be key=value.");
|
|
542
|
+
const key = entry.slice(0, split).trim();
|
|
543
|
+
if (Object.hasOwn(filter, key))
|
|
544
|
+
refuse(`Duplicate filter key: ${key}`);
|
|
545
|
+
filter[key] = entry.slice(split + 1);
|
|
546
|
+
}
|
|
547
|
+
if (readLocalJobs(dir).length >= MAX_ACTIVE_JOBS_PER_AGENT)
|
|
548
|
+
refuse(`${name} already has the maximum of ${MAX_ACTIVE_JOBS_PER_AGENT} jobs. Remove one first.`);
|
|
549
|
+
const created = toJobRecord(addLocalJob(dir, {
|
|
550
|
+
prompt,
|
|
551
|
+
timezone: localZone,
|
|
552
|
+
trigger: { kind: "event", event, ...(Object.keys(filter).length ? { filter } : {}) },
|
|
553
|
+
...(opts.quiet ? { context: { [JOB_RUN_OPTIONS_CONTEXT_KEY]: { quiet: true } } } : {}),
|
|
554
|
+
}));
|
|
555
|
+
out(opts.json ? JSON.stringify({ ok: true, job: created }, null, 2) : `Added ${created.jobId}: ${name} runs it ${describeSchedule(created)}.`);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
519
558
|
const schedule = scheduleFromOptions();
|
|
520
559
|
const body = buildCreateJobBody({ prompt: opts.add, cron: schedule.cron, timezone: schedule.timezone, quiet: opts.quiet });
|
|
521
560
|
const api = await jobsApi();
|
|
@@ -558,6 +597,8 @@ export function registerBotJobsCommand(bot, deps) {
|
|
|
558
597
|
.option("--add <what>", "Schedule new work, in plain language")
|
|
559
598
|
.option("--every <when>", 'How often, in plain language (e.g. "every weekday at 9am")')
|
|
560
599
|
.option("--cron <expr>", 'How often, as a cron expression (e.g. "0 9 * * 1-5")')
|
|
600
|
+
.option("--on <event>", "Run when this event is emitted")
|
|
601
|
+
.option("--filter <key=value>", "Match an event payload field (repeatable)", (value, values) => [...values, value], [])
|
|
561
602
|
.option("--tz <zone>", "IANA timezone for the schedule (default: this computer's)")
|
|
562
603
|
.option("--quiet", "Skip owner DMs after successful runs (requires rollout flag)")
|
|
563
604
|
.option("--rm <jobId>", "Remove a scheduled job")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type FleetInstallProgress, type FleetInstallProgressListener, type FleetLocalTargetRelease } from "../lib/bot/fleet-local.js";
|
|
2
|
+
export type FleetProgressMode = "text" | "json" | "silent";
|
|
3
|
+
interface ProgressOutput {
|
|
4
|
+
write(text: string): void;
|
|
5
|
+
isTTY: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** The sentence for a phase, for people. */
|
|
8
|
+
export declare function describeFleetInstallPhase(event: FleetInstallProgress): string;
|
|
9
|
+
/**
|
|
10
|
+
* Progress for a terminal (stderr, one line that rewrites itself on a TTY and
|
|
11
|
+
* prints at each tenth otherwise), for a program (one JSON object per line on
|
|
12
|
+
* stdout), or nothing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fleetInstallProgressReporter(mode: FleetProgressMode, output?: ProgressOutput, stdout?: (line: string) => void): FleetInstallProgressListener;
|
|
15
|
+
export interface FleetRuntimeStatus {
|
|
16
|
+
ready: boolean;
|
|
17
|
+
release: {
|
|
18
|
+
version: string;
|
|
19
|
+
source: FleetLocalTargetRelease["source"];
|
|
20
|
+
};
|
|
21
|
+
steps: {
|
|
22
|
+
distribution: boolean;
|
|
23
|
+
python: boolean;
|
|
24
|
+
runtime: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export declare function fleetRuntimeStatus(): Promise<FleetRuntimeStatus>;
|
|
28
|
+
export declare function runBotRuntimeInstall(opts: {
|
|
29
|
+
json?: boolean;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
export declare function runBotRuntimeStatus(opts: {
|
|
32
|
+
json?: boolean;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=bot-runtime.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ensureFleetRuntimeInstalled, fleetLocalDistributionInstalled, fleetRuntimeReady, installTargetFleetLocalDistribution, resolveFleetLocalTargetRelease, } from "../lib/bot/fleet-local.js";
|
|
3
|
+
import { installedFleetPython } from "../lib/bot/fleet-python.js";
|
|
4
|
+
function megabytes(bytes) {
|
|
5
|
+
return `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
6
|
+
}
|
|
7
|
+
/** The sentence for a phase, for people. */
|
|
8
|
+
export function describeFleetInstallPhase(event) {
|
|
9
|
+
switch (event.phase) {
|
|
10
|
+
case "download": {
|
|
11
|
+
const name = event.file.startsWith("hq-fleet-runtime-") ? "the HQ Fleet Runtime" : event.file;
|
|
12
|
+
if (event.total === null)
|
|
13
|
+
return `Downloading ${name}: ${megabytes(event.received)}`;
|
|
14
|
+
const percent = Math.min(100, Math.floor((event.received / Math.max(event.total, 1)) * 100));
|
|
15
|
+
return `Downloading ${name}: ${megabytes(event.received)} of ${megabytes(event.total)} (${percent}%)`;
|
|
16
|
+
}
|
|
17
|
+
case "verify":
|
|
18
|
+
return "Checking the download against its signed release";
|
|
19
|
+
case "python":
|
|
20
|
+
return "Preparing the runtime's private Python";
|
|
21
|
+
case "extract":
|
|
22
|
+
return "Unpacking the runtime";
|
|
23
|
+
case "environment":
|
|
24
|
+
return "Creating the runtime environment";
|
|
25
|
+
case "packages":
|
|
26
|
+
return "Installing the runtime's packages (a minute or two)";
|
|
27
|
+
case "ready":
|
|
28
|
+
if (event.step === "download")
|
|
29
|
+
return event.cached ? "The runtime download is already on this computer" : "The runtime download is complete";
|
|
30
|
+
return event.cached ? "The runtime is already installed on this computer" : "The runtime is ready";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Progress for a terminal (stderr, one line that rewrites itself on a TTY and
|
|
35
|
+
* prints at each tenth otherwise), for a program (one JSON object per line on
|
|
36
|
+
* stdout), or nothing.
|
|
37
|
+
*/
|
|
38
|
+
export function fleetInstallProgressReporter(mode, output = { write: (text) => { process.stderr.write(text); }, isTTY: process.stderr.isTTY === true }, stdout = (line) => { process.stdout.write(`${line}\n`); }) {
|
|
39
|
+
if (mode === "silent")
|
|
40
|
+
return () => undefined;
|
|
41
|
+
if (mode === "json") {
|
|
42
|
+
return (event) => {
|
|
43
|
+
const percent = event.phase === "download" && event.total !== null
|
|
44
|
+
? Math.min(100, Math.floor((event.received / Math.max(event.total, 1)) * 100))
|
|
45
|
+
: null;
|
|
46
|
+
stdout(JSON.stringify({ event: "progress", ...event, percent, message: describeFleetInstallPhase(event) }));
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
let lastTenth = -1;
|
|
50
|
+
let lineOpen = false;
|
|
51
|
+
return (event) => {
|
|
52
|
+
const text = describeFleetInstallPhase(event);
|
|
53
|
+
if (event.phase === "download" && event.total !== null) {
|
|
54
|
+
const tenth = Math.floor((event.received / Math.max(event.total, 1)) * 10);
|
|
55
|
+
if (output.isTTY) {
|
|
56
|
+
output.write(`\r${text}${event.received >= event.total ? "\n" : ""}`);
|
|
57
|
+
lineOpen = event.received < event.total;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (tenth === lastTenth && event.received < event.total)
|
|
61
|
+
return;
|
|
62
|
+
lastTenth = tenth;
|
|
63
|
+
output.write(`${text}\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (lineOpen) {
|
|
67
|
+
output.write("\n");
|
|
68
|
+
lineOpen = false;
|
|
69
|
+
}
|
|
70
|
+
output.write(`${text}\n`);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export async function fleetRuntimeStatus() {
|
|
74
|
+
const target = await resolveFleetLocalTargetRelease();
|
|
75
|
+
const distribution = fleetLocalDistributionInstalled(target.release);
|
|
76
|
+
const python = installedFleetPython();
|
|
77
|
+
const runtime = distribution && python ? fleetRuntimeReady({ release: target.release }, { python }) : false;
|
|
78
|
+
return {
|
|
79
|
+
ready: distribution && python !== null && runtime,
|
|
80
|
+
release: { version: target.release.version, source: target.source },
|
|
81
|
+
steps: { distribution, python: python !== null, runtime },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function runBotRuntimeInstall(opts) {
|
|
85
|
+
const report = fleetInstallProgressReporter(opts.json ? "json" : "text");
|
|
86
|
+
const target = await resolveFleetLocalTargetRelease();
|
|
87
|
+
const installation = await installTargetFleetLocalDistribution({ target, onProgress: report });
|
|
88
|
+
const result = await ensureFleetRuntimeInstalled(installation, { onProgress: report });
|
|
89
|
+
if (opts.json) {
|
|
90
|
+
console.log(JSON.stringify({
|
|
91
|
+
ok: true,
|
|
92
|
+
event: "done",
|
|
93
|
+
release: { version: result.release.version, source: target.source },
|
|
94
|
+
distributionDir: result.distributionDir,
|
|
95
|
+
runtimeDir: result.runtimeDir,
|
|
96
|
+
}));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(`${chalk.green("HQ Fleet Runtime " + result.release.version + " is ready on this computer.")} ${chalk.dim(result.runtimeDir)}`);
|
|
100
|
+
}
|
|
101
|
+
export async function runBotRuntimeStatus(opts) {
|
|
102
|
+
const current = await fleetRuntimeStatus();
|
|
103
|
+
if (opts.json) {
|
|
104
|
+
console.log(JSON.stringify({ ok: true, ...current }, null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const mark = (done) => (done ? chalk.green("ready") : chalk.yellow("missing"));
|
|
108
|
+
console.log(`HQ Fleet Runtime ${current.release.version} ${current.ready ? chalk.green("ready") : chalk.yellow("not installed yet")}`);
|
|
109
|
+
console.log(` download: ${mark(current.steps.distribution)}`);
|
|
110
|
+
console.log(` python: ${mark(current.steps.python)}`);
|
|
111
|
+
console.log(` environment: ${mark(current.steps.runtime)}`);
|
|
112
|
+
if (!current.ready)
|
|
113
|
+
console.log(chalk.dim(" Run `hq bot runtime install` to get it ready before creating a bot."));
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=bot-runtime.js.map
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { type RuntimeSignInCheck, type RuntimeSignInIssue } from "../lib/bot/runtime-sign-in.js";
|
|
3
|
+
import { type ClaudeLoginCheck } from "../lib/bot/claude-login.js";
|
|
3
4
|
import type { BotStatusFile } from "../lib/bot/status.js";
|
|
4
5
|
import { probeBotRuntime, type RuntimeProbeResult } from "../lib/bot/probe.js";
|
|
5
6
|
import { type SpawnInteractive } from "../lib/bot/runtime-auth.js";
|
|
@@ -100,6 +101,12 @@ export interface BotRow {
|
|
|
100
101
|
export declare function listBotNames(root: string): string[];
|
|
101
102
|
/** Bot-identity token supplier: per-bot creds + per-bot token cache dir. */
|
|
102
103
|
export declare function botTokenSupplier(dir: string): () => Promise<string>;
|
|
104
|
+
/**
|
|
105
|
+
* Whether `hq bot` may open a browser sign-in for its owner: a TTY on stdin,
|
|
106
|
+
* and the shared interactive-caller gate (no lane, daemon, bot runtime, or
|
|
107
|
+
* HQ_NO_BROWSER_LOGIN child).
|
|
108
|
+
*/
|
|
109
|
+
export declare function ownerSignInAllowed(): boolean;
|
|
103
110
|
/**
|
|
104
111
|
* The bot's word on its coding tool's sign-in. Only a running bot's report
|
|
105
112
|
* counts: a stopped bot's last check may be from before a sign-in or a
|
|
@@ -115,6 +122,10 @@ export declare function spawnDetachedRun(name: string, dir: string): number;
|
|
|
115
122
|
* "unreadable output: trailing characters").
|
|
116
123
|
*/
|
|
117
124
|
/** Seams for the self-stop path, replaced in tests. */
|
|
125
|
+
export declare const botCreateSeams: {
|
|
126
|
+
/** The Claude Code login read the way the HQ Fleet Runtime reads it. */
|
|
127
|
+
claudeLogin: () => ClaudeLoginCheck;
|
|
128
|
+
};
|
|
118
129
|
export declare const botStopSeams: {
|
|
119
130
|
/** True when this process runs inside the bot process `botPid` (one of its turns). */
|
|
120
131
|
isInsideBot: (botPid: number) => boolean;
|
package/dist/commands/bot.js
CHANGED
|
@@ -43,6 +43,8 @@ import { createVaultObjectDownloader } from "../lib/agent-kit/attachment-downloa
|
|
|
43
43
|
import { addBotToCompanies, describeCompanyFailures, removeBotFromCompanies } from "./bot-companies.js";
|
|
44
44
|
import { registerBotContinuityCommand } from "./bot-continuity.js";
|
|
45
45
|
import { registerBotJobsCommand } from "./bot-jobs.js";
|
|
46
|
+
import { registerBotEventsCommand } from "./bot-events.js";
|
|
47
|
+
import { executiveStatus } from "../lib/bot/executive-settings.js";
|
|
46
48
|
import { ensureFleetHotSignalSync, fleetCronOps, migrateLegacyJobsToFleet } from "../lib/bot/fleet-cron.js";
|
|
47
49
|
import { resolvePackSetupPrompt } from "../lib/bot/pack-setup.js";
|
|
48
50
|
import { detectCodingToolActivity } from "../lib/bot/coding-tool-activity.js";
|
|
@@ -63,10 +65,14 @@ import { daemonPaths } from "../lib/daemon/paths.js";
|
|
|
63
65
|
import { launchdLabel } from "../lib/bot/paths.js";
|
|
64
66
|
import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
65
67
|
import { runtimeDisplayName, signInCommand } from "../lib/bot/runtime-sign-in.js";
|
|
68
|
+
import { checkClaudeCodeLogin, claudeLoginFix, describeClaudeLoginSources } from "../lib/bot/claude-login.js";
|
|
69
|
+
import { unresolvedLastFailure } from "../lib/doctor/checks/bots.js";
|
|
66
70
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
67
71
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
68
72
|
import { BOT_LOGIN_RUNTIMES, botAuthEnv, botRuntimeAuth, capacityAccountHome, describeBotAuth, formatBotAuth, isBotLoginRuntime, restoreBotSessions, setAsideBotSessions, signInBotRuntime, signOutBotRuntime, withRuntimeAuth, } from "../lib/bot/runtime-auth.js";
|
|
69
73
|
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, runtimeCrashSinceStart, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, HQ_FLEET_LOCAL_RELEASE, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, fleetLocalProfileNeedsAdoption, fleetLocalServiceInstalled, fleetProviderForRuntime, importFleetLocalMachineCredential, fleetProviderNeedsSignIn, fleetOwnerCompanies, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, installTargetFleetLocalDistribution, resolveFleetLocalPointerTarget, resolveFleetLocalTargetRelease, fleetLocalProfileDir, fleetProfileOwnerCompanies, fleetLocalChannels, readFleetLocalGatewayState, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
74
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
75
|
+
import { fleetInstallProgressReporter, runBotRuntimeInstall, runBotRuntimeStatus } from "./bot-runtime.js";
|
|
70
76
|
function collectRepeated(value, previous) {
|
|
71
77
|
return [...previous, value];
|
|
72
78
|
}
|
|
@@ -328,7 +334,15 @@ export function botTokenSupplier(dir) {
|
|
|
328
334
|
process.env.HQ_MACHINE_TOKEN_STATE_DIR = botTokenStateDir(dir);
|
|
329
335
|
return () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
330
336
|
}
|
|
331
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Whether `hq bot` may open a browser sign-in for its owner: a TTY on stdin,
|
|
339
|
+
* and the shared interactive-caller gate (no lane, daemon, bot runtime, or
|
|
340
|
+
* HQ_NO_BROWSER_LOGIN child).
|
|
341
|
+
*/
|
|
342
|
+
export function ownerSignInAllowed() {
|
|
343
|
+
return gatedInteractiveLogin(process.stdin.isTTY === true);
|
|
344
|
+
}
|
|
345
|
+
async function ownerToken(interactive = ownerSignInAllowed()) {
|
|
332
346
|
if (peekHqApiKey()) {
|
|
333
347
|
throw Object.assign(new Error("hq bot needs your HQ login session (HQ_API_KEY is not supported here). Run `hq login`."), {
|
|
334
348
|
expected: true,
|
|
@@ -731,9 +745,10 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
731
745
|
return null;
|
|
732
746
|
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: target.release }))
|
|
733
747
|
return null;
|
|
748
|
+
const progress = opts.progress ?? fleetInstallProgressReporter("silent");
|
|
734
749
|
let installation;
|
|
735
750
|
try {
|
|
736
|
-
installation = await installTargetFleetLocalDistribution({ target });
|
|
751
|
+
installation = await installTargetFleetLocalDistribution({ target, onProgress: progress });
|
|
737
752
|
}
|
|
738
753
|
catch (err) {
|
|
739
754
|
if (target.source !== "pointer")
|
|
@@ -745,8 +760,9 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
745
760
|
createBotLogger(dir)("warn", `could not install HQ Fleet Local ${target.release.version} (${err instanceof Error ? err.message : String(err)}); staying on ${HQ_FLEET_LOCAL_RELEASE.version}`);
|
|
746
761
|
if (!opts.force && !fleetLocalProfileNeedsAdoption(config.agentUid, { release: HQ_FLEET_LOCAL_RELEASE }))
|
|
747
762
|
return null;
|
|
748
|
-
installation = await installPinnedFleetLocalDistribution();
|
|
763
|
+
installation = await installPinnedFleetLocalDistribution({ onProgress: progress });
|
|
749
764
|
}
|
|
765
|
+
progress({ phase: "python" });
|
|
750
766
|
const python = await ensureFleetPython();
|
|
751
767
|
const companyScope = effectiveBotKind(config) === "company"
|
|
752
768
|
? effectiveBotCompanies(config)
|
|
@@ -761,7 +777,7 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
761
777
|
provider: fleetProviderForRuntime(config.runtime, config.model),
|
|
762
778
|
ownerCompanies: companyScope,
|
|
763
779
|
...(config.autoApprove === true ? { autoApprove: true } : {}),
|
|
764
|
-
}, { python, start: false });
|
|
780
|
+
}, { python, start: false, onProgress: progress });
|
|
765
781
|
const credentials = botCredsPath(dir);
|
|
766
782
|
if (fs.existsSync(credentials))
|
|
767
783
|
importFleetLocalMachineCredential(config.agentUid, credentials, {}, { keepSource: opts.keepCredentials === true });
|
|
@@ -774,6 +790,22 @@ async function installFleetRuntimeProfile(name, dir, config, opts = {}) {
|
|
|
774
790
|
* "unreadable output: trailing characters").
|
|
775
791
|
*/
|
|
776
792
|
/** Seams for the self-stop path, replaced in tests. */
|
|
793
|
+
export const botCreateSeams = {
|
|
794
|
+
/** The Claude Code login read the way the HQ Fleet Runtime reads it. */
|
|
795
|
+
claudeLogin: () => checkClaudeCodeLogin(),
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* A Claude bot on the HQ Fleet Runtime answers with the owner's Claude Code
|
|
799
|
+
* login. Creating one on a computer with no readable login provisions an
|
|
800
|
+
* identity that can only ever answer "I'm away for a bit", so refuse first
|
|
801
|
+
* and say which sources were checked.
|
|
802
|
+
*/
|
|
803
|
+
function requireClaudeCodeLoginForCreate(name) {
|
|
804
|
+
const check = botCreateSeams.claudeLogin();
|
|
805
|
+
if (check.signedIn)
|
|
806
|
+
return;
|
|
807
|
+
throw Object.assign(new Error(`Claude Code is not signed in on this computer, so ${name} could not answer. ${describeClaudeLoginSources(check)} ${claudeLoginFix()}`.replace(/\s+/g, " ").trim()), { expected: true });
|
|
808
|
+
}
|
|
777
809
|
export const botStopSeams = {
|
|
778
810
|
/** True when this process runs inside the bot process `botPid` (one of its turns). */
|
|
779
811
|
isInsideBot: (botPid) => isAncestorProcess(botPid),
|
|
@@ -1114,6 +1146,8 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1114
1146
|
}
|
|
1115
1147
|
const fleetProvider = fleetProviderForRuntime(runtime, model);
|
|
1116
1148
|
const fleetAuthPending = fleetProviderNeedsSignIn(fleetProvider);
|
|
1149
|
+
if (fleetProvider.id === "anthropic")
|
|
1150
|
+
requireClaudeCodeLoginForCreate(name);
|
|
1117
1151
|
const token = await ownerToken();
|
|
1118
1152
|
const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
|
|
1119
1153
|
if (!owner)
|
|
@@ -1206,7 +1240,9 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1206
1240
|
// of sending another create that the entity store will reject.
|
|
1207
1241
|
if (isSetupBot && (await adoptOwnedSetupBot(true)))
|
|
1208
1242
|
return;
|
|
1209
|
-
const
|
|
1243
|
+
const progress = fleetInstallProgressReporter(opts.json ? "silent" : "text");
|
|
1244
|
+
const fleetInstallation = await installTargetFleetLocalDistribution({ onProgress: progress });
|
|
1245
|
+
progress({ phase: "python" });
|
|
1210
1246
|
const fleetPython = await ensureFleetPython();
|
|
1211
1247
|
const ownerCompanies = kind === "personal" ? await fleetOwnerCompanies() : companies;
|
|
1212
1248
|
// A company bot's companies are resolved first (a typo must not cost an
|
|
@@ -1366,7 +1402,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
1366
1402
|
firstStart: toolOffer
|
|
1367
1403
|
? { intro: setupToolOfferText(toolOffer, setupWelcomeOpening(intro)) }
|
|
1368
1404
|
: { intro: introText, ...(kickoff ? { kickoff } : {}) },
|
|
1369
|
-
}, { start: opts.start !== false && !fleetAuthPending, python: fleetPython });
|
|
1405
|
+
}, { start: opts.start !== false && !fleetAuthPending, python: fleetPython, onProgress: progress });
|
|
1370
1406
|
syncFleetRuntimeJobs(name, dir, config);
|
|
1371
1407
|
const fleetProfile = fleetBootstrap.profile;
|
|
1372
1408
|
const runtimeLabel = HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
@@ -1664,7 +1700,7 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1664
1700
|
const creds = readBotCredsIdentity(dir);
|
|
1665
1701
|
const channels = fleet ? fleetLocalChannels(readFleetLocalGatewayState(config.agentUid), row.processAlive) : null;
|
|
1666
1702
|
if (opts.json) {
|
|
1667
|
-
console.log(JSON.stringify({ ...row, ...(fleet ? { engine: config.engine, channels } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
|
|
1703
|
+
console.log(JSON.stringify({ ...row, executive: executiveStatus(config, dir), ...(fleet ? { engine: config.engine, channels } : {}), credsPresent: fleet || !!creds, login: describeBotAuth(dir, config), status }, null, 2));
|
|
1668
1704
|
return;
|
|
1669
1705
|
}
|
|
1670
1706
|
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
@@ -1733,6 +1769,15 @@ async function runBotStatus(nameArg, opts) {
|
|
|
1733
1769
|
const cls = classifyRuntimeFailure(status.lastError);
|
|
1734
1770
|
console.log(` last problem: ${chalk.yellow(`${cls}: ${runtimeFailureReason(cls, row.runtime)}`)} ${chalk.dim(`(details: hq bot logs ${name})`)}`);
|
|
1735
1771
|
}
|
|
1772
|
+
if (status.lastFailure) {
|
|
1773
|
+
const failure = status.lastFailure;
|
|
1774
|
+
const resolved = unresolvedLastFailure(status) === null;
|
|
1775
|
+
const paint = resolved ? chalk.dim : chalk.yellow;
|
|
1776
|
+
console.log(` last failure: ${paint(`${failure.at} (${failure.class}${resolved ? ", answered since" : ""})`)}`);
|
|
1777
|
+
console.log(` cause: ${paint(failure.cause)}`);
|
|
1778
|
+
console.log(` fix: ${paint(failure.fix)}`);
|
|
1779
|
+
console.log(` report: ${failure.uploaded && failure.reportId ? `${failure.reportId} (in HQ, with the bot's last log lines)` : chalk.red("not uploaded to HQ")} ${chalk.dim(`owner DM ${failure.ownerNotified ? "sent" : "not sent"}`)}`);
|
|
1780
|
+
}
|
|
1736
1781
|
}
|
|
1737
1782
|
console.log(chalk.dim(` state dir: ${dir}`));
|
|
1738
1783
|
}
|
|
@@ -2745,7 +2790,10 @@ async function adoptRemoteBotLocked(input) {
|
|
|
2745
2790
|
...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
|
|
2746
2791
|
});
|
|
2747
2792
|
// A bot made on the HQ Fleet Runtime comes back on it, on the release this CLI pins.
|
|
2748
|
-
const
|
|
2793
|
+
const progress = input.progress ?? fleetInstallProgressReporter("silent");
|
|
2794
|
+
const fleetInstallation = fleetEngine ? await installPinnedFleetLocalDistribution({ onProgress: progress }) : null;
|
|
2795
|
+
if (fleetInstallation)
|
|
2796
|
+
progress({ phase: "python" });
|
|
2749
2797
|
const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
|
|
2750
2798
|
if (fleetInstallation)
|
|
2751
2799
|
config.engine = fleetInstallation.engine;
|
|
@@ -2774,7 +2822,7 @@ async function adoptRemoteBotLocked(input) {
|
|
|
2774
2822
|
},
|
|
2775
2823
|
}
|
|
2776
2824
|
: {}),
|
|
2777
|
-
}, { python: fleetPython, start: false });
|
|
2825
|
+
}, { python: fleetPython, start: false, onProgress: progress });
|
|
2778
2826
|
}
|
|
2779
2827
|
createBotLogger(dir)("info", `adopted ${name} (${bot.agentUid}) on ${localMachineId()} kind=${config.kind}${local ? "" : " (no saved settings; defaults)"} runtime=${config.runtime}`);
|
|
2780
2828
|
let daemonMessage = null;
|
|
@@ -2839,6 +2887,7 @@ export async function runBotAdopt(nameArg, opts) {
|
|
|
2839
2887
|
name,
|
|
2840
2888
|
api,
|
|
2841
2889
|
hqRoot,
|
|
2890
|
+
progress: fleetInstallProgressReporter(opts.json ? "silent" : "text"),
|
|
2842
2891
|
overrides: {
|
|
2843
2892
|
...(isBotRuntimeId(opts.runtime) ? { runtime: opts.runtime } : {}),
|
|
2844
2893
|
...(model ? { model } : {}),
|
|
@@ -2952,7 +3001,7 @@ export async function runBotRestore(opts) {
|
|
|
2952
3001
|
if (here) {
|
|
2953
3002
|
return { name, agentUid: bot.agentUid, action: "repaired", detail: await repairLocalBot(name, api, bot, opts.daemon !== false), ...shown };
|
|
2954
3003
|
}
|
|
2955
|
-
const { result, config } = await adoptRemoteBot({ bot, name, api, hqRoot, daemon: opts.daemon !== false });
|
|
3004
|
+
const { result, config } = await adoptRemoteBot({ bot, name, api, hqRoot, daemon: opts.daemon !== false, progress: fleetInstallProgressReporter(opts.json ? "silent" : "text") });
|
|
2956
3005
|
// Silent: this command's own JSON has to be the only document on stdout.
|
|
2957
3006
|
const started = opts.start === false ? null : await startBot(name, result.dir, config, "silent").catch(() => null);
|
|
2958
3007
|
return {
|
|
@@ -3052,6 +3101,7 @@ export function registerBotCommand(program) {
|
|
|
3052
3101
|
.description("Personal local bots that run on this computer with your own model login");
|
|
3053
3102
|
registerBotContinuityCommand(bot);
|
|
3054
3103
|
registerBotJobsCommand(bot, { requireBot, botTokenSupplier });
|
|
3104
|
+
registerBotEventsCommand(bot, { requireBot });
|
|
3055
3105
|
bot.command("promote <name>")
|
|
3056
3106
|
.description("Move this local bot to cloud compute, preserving its identity and private context")
|
|
3057
3107
|
.requiredOption("--company <uid>", "Destination company identity")
|
|
@@ -3141,7 +3191,7 @@ export function registerBotCommand(program) {
|
|
|
3141
3191
|
const b = requireBot(name);
|
|
3142
3192
|
if (isFleetRuntimeBot(b.config)) {
|
|
3143
3193
|
refuseHeldFleetBot(b.dir);
|
|
3144
|
-
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config);
|
|
3194
|
+
const adopted = await adoptFleetRuntimeProfile(b.name, b.dir, b.config, { progress: fleetInstallProgressReporter(opts.json ? "silent" : "text") });
|
|
3145
3195
|
runFleetRuntimeControl(b.name, b.config, adopted ? "install" : "start", opts.json === true);
|
|
3146
3196
|
return;
|
|
3147
3197
|
}
|
|
@@ -3239,6 +3289,17 @@ export function registerBotCommand(program) {
|
|
|
3239
3289
|
.command("intro <name>")
|
|
3240
3290
|
.description("Have the bot re-send its introduction DM to you")
|
|
3241
3291
|
.action((name) => wrap(() => runBotIntro(name))());
|
|
3292
|
+
const runtime = bot.command("runtime").description("The HQ Fleet Runtime every local bot on this computer shares");
|
|
3293
|
+
runtime
|
|
3294
|
+
.command("install")
|
|
3295
|
+
.description("Download and build the runtime once for this computer, with progress, so creating a bot takes seconds. Safe to run again: a finished install returns at once, an interrupted download continues where it stopped")
|
|
3296
|
+
.option("--json", "Print one JSON object per line: progress events, then { ok, event: \"done\", release, runtimeDir }")
|
|
3297
|
+
.action((opts) => wrap(() => runBotRuntimeInstall(opts))());
|
|
3298
|
+
runtime
|
|
3299
|
+
.command("status")
|
|
3300
|
+
.description("Say whether the runtime is installed on this computer, step by step")
|
|
3301
|
+
.option("--json", "Print machine-readable JSON")
|
|
3302
|
+
.action((opts) => wrap(() => runBotRuntimeStatus(opts))());
|
|
3242
3303
|
const daemon = bot.command("daemon").description("Manage the bot's launchd user agent (macOS)");
|
|
3243
3304
|
daemon
|
|
3244
3305
|
.command("install <name>")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
4
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
4
5
|
/**
|
|
5
6
|
* Testable core. Submits the application and maps the server response to a
|
|
6
7
|
* single human-readable message. Throws on hard failures.
|
|
@@ -46,7 +47,7 @@ export function registerCreatorsCommand(program) {
|
|
|
46
47
|
.action(async (options) => {
|
|
47
48
|
try {
|
|
48
49
|
const result = await runCreatorApply({ reason: options.reason, handle: options.handle }, {
|
|
49
|
-
getAccessToken: () => ensureCognitoToken({ interactive:
|
|
50
|
+
getAccessToken: () => ensureCognitoToken({ interactive: gatedInteractiveLogin() }),
|
|
50
51
|
post: (token, body) => vaultApiFetch({
|
|
51
52
|
token,
|
|
52
53
|
path: "/v1/creators/request-access",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import { type QuestionBody } from "../lib/lanes/questions.js";
|
|
3
|
-
import type { LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
|
|
3
|
+
import type { LaneCompanyRef, LaneRecord, QuestionRecord } from "../lib/lanes/types.js";
|
|
4
4
|
import { type LaneChecksDeps } from "./lanes-checks.js";
|
|
5
5
|
type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
|
|
6
6
|
export interface LaneReportsDeps extends LaneChecksDeps {
|
|
7
|
+
laneRunsHere: (lane: LaneRecord) => boolean;
|
|
7
8
|
readQuestionBody: (root: string, question: QuestionRecord) => QuestionBody;
|
|
8
9
|
focusDashboardEnabled: (company: LaneCompanyRef) => Promise<boolean>;
|
|
9
10
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
3
4
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
4
|
-
import { questionRecordPath } from "../lib/lanes/store.js";
|
|
5
|
+
import { listLanesBySource, questionRecordPath } from "../lib/lanes/store.js";
|
|
5
6
|
import { normalizeIsoTimestamp } from "../lib/lanes/timestamp.js";
|
|
6
7
|
import { mapSourceStoryStatus } from "../lib/mesh/story-status.js";
|
|
7
8
|
import { buildTrackerOwnerReport, buildTrackerTeamPage, renderTrackerOwnerReportText, renderTrackerTeamPageText, } from "../lib/lanes/tracker-report.js";
|
|
8
9
|
import { FIRST_TRACKER_REPORT_SINCE, readTrackerReportState, writeTrackerReportState, } from "../lib/lanes/tracker-report-state.js";
|
|
9
10
|
import { listTrackerRecordsTolerant, loadCompanyQuestionRecords, loadProjectMappings, mappingIncludesStory, pathForTrackerRecord, readProject, resolveTrackerCompany, } from "./lanes-checks.js";
|
|
11
|
+
import { buildSourceReport, renderSourceReportText } from "../lib/lanes/source-report.js";
|
|
12
|
+
import { readLaneStatusRowFields } from "../lib/lanes/status-fields.js";
|
|
10
13
|
function fail(error, message, details = {}) {
|
|
11
14
|
throw new LanesError(error, message, details);
|
|
12
15
|
}
|
|
@@ -331,10 +334,50 @@ export function registerLaneReportCommands(parent, deps, wrap) {
|
|
|
331
334
|
parent.command("report")
|
|
332
335
|
.description("Generate the owner report for tracker teams")
|
|
333
336
|
.option("--company <slug>", "Select the company by slug")
|
|
337
|
+
.option("--source <ref>", "Report lanes for a source (requires --company; excludes --since)")
|
|
334
338
|
.option("--since <iso>", "Report activity from this ISO timestamp")
|
|
335
339
|
.option("--json", "Emit JSON instead of the owner report text")
|
|
336
340
|
.action(wrap(async (options) => {
|
|
341
|
+
if (options.source !== undefined && (!options.company?.trim() || options.since !== undefined || !options.source.trim())) {
|
|
342
|
+
fail("source_report_options_invalid", "--source requires --company and a non-empty ref, and cannot be combined with --since");
|
|
343
|
+
}
|
|
337
344
|
const company = await resolveTrackerCompany(deps, options.company);
|
|
345
|
+
if (options.source !== undefined) {
|
|
346
|
+
const root = deps.hqRoot();
|
|
347
|
+
const lanes = listLanesBySource(company.slug, options.source, root)
|
|
348
|
+
.filter(lane => lane.company?.uid === company.uid);
|
|
349
|
+
const ids = new Set(lanes.map(lane => lane.lane_id));
|
|
350
|
+
const questions = new Map();
|
|
351
|
+
if (lanes.length) {
|
|
352
|
+
const pending = loadCompanyQuestionRecords(root, company)
|
|
353
|
+
.filter(question => ids.has(question.lane_id) && question.status === "pending")
|
|
354
|
+
.sort((a, b) => a.asked_at.localeCompare(b.asked_at) || a.question_id.localeCompare(b.question_id));
|
|
355
|
+
for (const question of pending) {
|
|
356
|
+
if (questions.has(question.lane_id))
|
|
357
|
+
continue;
|
|
358
|
+
const body = deps.readQuestionBody(root, question);
|
|
359
|
+
assertQuestionBodySecretSafe(body);
|
|
360
|
+
questions.set(question.lane_id, { question_id: question.question_id, question: body.question });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const progress = new Map(lanes.filter(lane => lane.state === "running" || lane.state === "queued")
|
|
364
|
+
.map(lane => [lane.lane_id, readLaneStatusRowFields(root, lane, deps.now().getTime(), deps.laneRunsHere(lane)).last_line ?? "No progress recorded yet."]));
|
|
365
|
+
const report = buildSourceReport(company.slug, options.source, lanes, { questions, progress });
|
|
366
|
+
if (options.json)
|
|
367
|
+
console.log(JSON.stringify(report, null, 2));
|
|
368
|
+
else {
|
|
369
|
+
let template;
|
|
370
|
+
try {
|
|
371
|
+
template = fs.readFileSync(path.join(root, "personal/settings/executive/report.md"), "utf8");
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
if (error.code !== "ENOENT")
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
console.log(renderSourceReportText(report, template));
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
338
381
|
const root = deps.hqRoot();
|
|
339
382
|
const generatedAt = deps.now().toISOString();
|
|
340
383
|
const focusDashboardEnabled = await deps.focusDashboardEnabled(company);
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -165,6 +165,15 @@ export declare function pickupFailureUpdater(reasonRaw: string, details: Record<
|
|
|
165
165
|
*/
|
|
166
166
|
export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
|
|
167
167
|
export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
|
|
168
|
+
/** In-process signal entry point sharing CLI admission, dedupe and conductor rules. */
|
|
169
|
+
export declare function createLaneFromSignal(input: {
|
|
170
|
+
hqRoot: string;
|
|
171
|
+
company: string;
|
|
172
|
+
signal_id: string;
|
|
173
|
+
}, deps?: LanesCommandDeps): Promise<{
|
|
174
|
+
lane_id: string;
|
|
175
|
+
existing: boolean;
|
|
176
|
+
}>;
|
|
168
177
|
/** @internal Exported for regression tests of the profile-refresh company check. */
|
|
169
178
|
export declare function applyResumeProfileAndEffort(hqRoot: string, current: LaneRecord, next: LaneRecord, input: {
|
|
170
179
|
profile?: ResolvedResumeProfile;
|