@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
  3. package/dist/command-catalog.generated.d.ts +150 -6
  4. package/dist/command-catalog.generated.js +182 -6
  5. package/dist/commands/bot-events.d.ts +19 -0
  6. package/dist/commands/bot-events.js +46 -0
  7. package/dist/commands/bot-jobs.d.ts +2 -0
  8. package/dist/commands/bot-jobs.js +43 -2
  9. package/dist/commands/bot-runtime.d.ts +35 -0
  10. package/dist/commands/bot-runtime.js +115 -0
  11. package/dist/commands/bot.d.ts +11 -0
  12. package/dist/commands/bot.js +72 -11
  13. package/dist/commands/creators.js +2 -1
  14. package/dist/commands/lanes-report.d.ts +2 -1
  15. package/dist/commands/lanes-report.js +44 -1
  16. package/dist/commands/lanes.d.ts +9 -0
  17. package/dist/commands/lanes.js +95 -9
  18. package/dist/commands/library-remove.js +2 -1
  19. package/dist/commands/library.js +5 -4
  20. package/dist/commands/meetings.js +1 -21
  21. package/dist/commands/mesh.d.ts +0 -7
  22. package/dist/commands/mesh.js +5 -1
  23. package/dist/commands/signals-run.d.ts +2 -0
  24. package/dist/commands/signals-run.js +6 -1
  25. package/dist/commands/sync-held.js +2 -2
  26. package/dist/lanes-drain-hook.js +9 -0
  27. package/dist/lib/bot/api.d.ts +5 -0
  28. package/dist/lib/bot/claude-login.d.ts +31 -0
  29. package/dist/lib/bot/claude-login.js +160 -0
  30. package/dist/lib/bot/events.d.ts +18 -0
  31. package/dist/lib/bot/events.js +160 -0
  32. package/dist/lib/bot/executive-settings.d.ts +10 -0
  33. package/dist/lib/bot/executive-settings.js +43 -0
  34. package/dist/lib/bot/fleet-cron.js +4 -0
  35. package/dist/lib/bot/fleet-download.d.ts +27 -0
  36. package/dist/lib/bot/fleet-download.js +162 -0
  37. package/dist/lib/bot/fleet-local.d.ts +51 -2
  38. package/dist/lib/bot/fleet-local.js +179 -76
  39. package/dist/lib/bot/fleet-python.d.ts +2 -0
  40. package/dist/lib/bot/fleet-python.js +29 -0
  41. package/dist/lib/bot/job-run.js +1 -1
  42. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
  43. package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
  44. package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
  45. package/dist/lib/bot/jobs/meeting-detect.js +176 -0
  46. package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
  47. package/dist/lib/bot/jobs/meeting-rider.js +284 -0
  48. package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
  49. package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
  50. package/dist/lib/bot/local-jobs.d.ts +15 -2
  51. package/dist/lib/bot/local-jobs.js +24 -7
  52. package/dist/lib/bot/run.d.ts +7 -49
  53. package/dist/lib/bot/run.js +100 -4
  54. package/dist/lib/bot/status.d.ts +21 -0
  55. package/dist/lib/doctor/checks/bots.d.ts +6 -0
  56. package/dist/lib/doctor/checks/bots.js +47 -1
  57. package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
  58. package/dist/lib/hot-channels/adapters/meetings.js +3 -1
  59. package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
  60. package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
  61. package/dist/lib/hot-channels/schedule.js +2 -2
  62. package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
  63. package/dist/lib/hot-channels/signals-pass.js +51 -17
  64. package/dist/lib/lanes/admission.d.ts +2 -0
  65. package/dist/lib/lanes/admission.js +4 -0
  66. package/dist/lib/lanes/approval-gate.js +4 -1
  67. package/dist/lib/lanes/links.d.ts +8 -1
  68. package/dist/lib/lanes/links.js +84 -3
  69. package/dist/lib/lanes/mesh-events.js +3 -1
  70. package/dist/lib/lanes/outcome.d.ts +154 -0
  71. package/dist/lib/lanes/outcome.js +151 -0
  72. package/dist/lib/lanes/reconcile.js +1 -0
  73. package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
  74. package/dist/lib/lanes/signal-brief-template.js +40 -0
  75. package/dist/lib/lanes/signal-lane.d.ts +24 -0
  76. package/dist/lib/lanes/signal-lane.js +99 -0
  77. package/dist/lib/lanes/source-report.d.ts +37 -0
  78. package/dist/lib/lanes/source-report.js +66 -0
  79. package/dist/lib/lanes/spawn.d.ts +6 -0
  80. package/dist/lib/lanes/spawn.js +21 -0
  81. package/dist/lib/lanes/store.d.ts +3 -0
  82. package/dist/lib/lanes/store.js +64 -0
  83. package/dist/lib/lanes/story-ledger.d.ts +3 -0
  84. package/dist/lib/lanes/story-ledger.js +1 -0
  85. package/dist/lib/lanes/types.d.ts +137 -0
  86. package/dist/lib/lanes/types.js +17 -2
  87. package/dist/lib/lanes/watcher.js +4 -0
  88. package/dist/lib/live-meetings.d.ts +19 -0
  89. package/dist/lib/live-meetings.js +23 -0
  90. package/dist/lib/mesh/api.js +2 -1
  91. package/dist/lib/mesh/live/daemon/doctor.js +6 -7
  92. package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
  93. package/dist/lib/mesh/live/daemon/run.js +3 -0
  94. package/dist/lib/mesh/live/emit.d.ts +2 -11
  95. package/dist/lib/mesh/live/emit.js +20 -2
  96. package/dist/lib/mesh/live/flush.d.ts +2 -0
  97. package/dist/lib/mesh/live/flush.js +34 -3
  98. package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
  99. package/dist/lib/mesh/live/reporting-privacy.js +47 -0
  100. package/dist/lib/mesh/live/spool.d.ts +8 -0
  101. package/dist/lib/mesh/live/spool.js +59 -40
  102. package/dist/lib/plan-limit-nag.d.ts +30 -4
  103. package/dist/lib/plan-limit-nag.js +68 -17
  104. package/dist/lib/signals/engine.d.ts +4 -0
  105. package/dist/lib/signals/engine.js +25 -5
  106. package/dist/lib/signals/incremental.d.ts +42 -0
  107. package/dist/lib/signals/incremental.js +191 -0
  108. package/dist/lib/signals/prior-signals.d.ts +11 -0
  109. package/dist/lib/signals/prior-signals.js +44 -0
  110. package/dist/lib/signals/reply.js +6 -1
  111. package/dist/lib/signals/settings.d.ts +5 -0
  112. package/dist/lib/signals/settings.js +17 -0
  113. package/dist/lib/signals/steps/extract.d.ts +23 -2
  114. package/dist/lib/signals/steps/extract.js +142 -9
  115. package/dist/lib/signals/steps/types.d.ts +1 -0
  116. package/dist/lib/signals/write-guard.js +7 -0
  117. package/dist/lib/work-context/company.d.ts +2 -3
  118. package/dist/lib/work-context/company.js +4 -22
  119. package/dist/secrets-env-action.js +5 -2
  120. package/dist/utils/cognito-callback-port.d.ts +17 -0
  121. package/dist/utils/cognito-callback-port.js +56 -0
  122. package/dist/utils/cognito-config.js +7 -3
  123. package/dist/utils/cognito-session.d.ts +7 -6
  124. package/dist/utils/cognito-session.js +19 -14
  125. package/dist/utils/interactive-caller.d.ts +23 -0
  126. package/dist/utils/interactive-caller.js +80 -0
  127. package/dist/utils/login-browser.js +5 -2
  128. package/dist/utils/resident-package-preload.js +4 -0
  129. 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
@@ -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;
@@ -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
- async function ownerToken(interactive = process.stdin.isTTY === true) {
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 fleetInstallation = await installTargetFleetLocalDistribution();
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 fleetInstallation = fleetEngine ? await installPinnedFleetLocalDistribution() : null;
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: true }),
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);
@@ -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;