@indigoai-us/hq-cli 5.345.190 → 5.345.193

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 (122) hide show
  1. package/CHANGELOG.md +420 -0
  2. package/assets/bot-workers/setup/worker.yaml +91 -34
  3. package/dist/command-catalog.generated.d.ts +96 -19
  4. package/dist/command-catalog.generated.js +124 -21
  5. package/dist/commands/bot-jobs.d.ts +21 -18
  6. package/dist/commands/bot-jobs.js +127 -39
  7. package/dist/commands/bot.d.ts +7 -4
  8. package/dist/commands/bot.js +289 -140
  9. package/dist/commands/core-jobs-schedule.js +10 -0
  10. package/dist/commands/daemon.js +37 -0
  11. package/dist/commands/files.d.ts +41 -6
  12. package/dist/commands/files.js +108 -27
  13. package/dist/commands/pack-start-bot.d.ts +4 -6
  14. package/dist/commands/pack-start-bot.js +4 -8
  15. package/dist/commands/signals-run.d.ts +45 -0
  16. package/dist/commands/signals-run.js +175 -0
  17. package/dist/commands/signals.d.ts +1 -0
  18. package/dist/commands/signals.js +3 -0
  19. package/dist/commands/sources.d.ts +11 -0
  20. package/dist/commands/sources.js +137 -25
  21. package/dist/commands/staff/reset-test-account.d.ts +10 -0
  22. package/dist/commands/staff/reset-test-account.js +18 -2
  23. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
  24. package/dist/hq-cloud-sync-runner/package.json +1 -1
  25. package/dist/lib/bot/api.d.ts +3 -2
  26. package/dist/lib/bot/config.d.ts +2 -0
  27. package/dist/lib/bot/fleet-cron.d.ts +121 -0
  28. package/dist/lib/bot/fleet-cron.js +319 -0
  29. package/dist/lib/bot/fleet-local.d.ts +113 -2
  30. package/dist/lib/bot/fleet-local.js +597 -35
  31. package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
  32. package/dist/lib/bot/fleet-owner-companies.js +30 -0
  33. package/dist/lib/bot/fleet-watch.d.ts +25 -0
  34. package/dist/lib/bot/fleet-watch.js +51 -0
  35. package/dist/lib/bot/index.d.ts +1 -1
  36. package/dist/lib/bot/index.js +1 -1
  37. package/dist/lib/bot/job-run.d.ts +5 -4
  38. package/dist/lib/bot/job-run.js +5 -4
  39. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
  40. package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
  41. package/dist/lib/bot/local-jobs.d.ts +13 -2
  42. package/dist/lib/bot/local-jobs.js +17 -4
  43. package/dist/lib/bot/progress.d.ts +14 -0
  44. package/dist/lib/bot/progress.js +47 -1
  45. package/dist/lib/bot/run.d.ts +55 -2
  46. package/dist/lib/bot/run.js +192 -10
  47. package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
  48. package/dist/lib/bot/setup-tool-offer.js +16 -0
  49. package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
  50. package/dist/lib/bot/signals-engine-flag.js +30 -0
  51. package/dist/lib/daemon/catalog.d.ts +4 -0
  52. package/dist/lib/daemon/catalog.js +29 -9
  53. package/dist/lib/daemon/legacy.d.ts +6 -0
  54. package/dist/lib/daemon/legacy.js +16 -10
  55. package/dist/lib/daemon/run.d.ts +1 -0
  56. package/dist/lib/daemon/run.js +10 -0
  57. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  58. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  59. package/dist/lib/flag-registry.d.ts +13 -1
  60. package/dist/lib/flag-registry.js +46 -0
  61. package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
  62. package/dist/lib/hot-channels/adapters/attio.js +367 -29
  63. package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
  64. package/dist/lib/hot-channels/adapters/gmail.js +59 -2
  65. package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
  66. package/dist/lib/hot-channels/adapters/linear.js +71 -1
  67. package/dist/lib/hot-channels/adapters/meetings.js +4 -1
  68. package/dist/lib/hot-channels/adapters/slack.js +7 -3
  69. package/dist/lib/hot-channels/alert.js +1 -1
  70. package/dist/lib/hot-channels/contract.d.ts +11 -0
  71. package/dist/lib/hot-channels/contract.js +1 -0
  72. package/dist/lib/hot-channels/cursors.js +1 -1
  73. package/dist/lib/hot-channels/landing.d.ts +6 -2
  74. package/dist/lib/hot-channels/landing.js +13 -6
  75. package/dist/lib/hot-channels/ledger.js +2 -0
  76. package/dist/lib/hot-channels/loop.js +18 -4
  77. package/dist/lib/hot-channels/registry.js +9 -1
  78. package/dist/lib/hot-channels/run.d.ts +18 -0
  79. package/dist/lib/hot-channels/run.js +15 -4
  80. package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
  81. package/dist/lib/hot-channels/signals-pass.js +7 -5
  82. package/dist/lib/hot-channels/sync-lock.js +22 -6
  83. package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
  84. package/dist/lib/mesh/live/daemon/install.js +117 -30
  85. package/dist/lib/signals/adapters-catalog.d.ts +77 -0
  86. package/dist/lib/signals/adapters-catalog.js +194 -0
  87. package/dist/lib/signals/engine.d.ts +196 -0
  88. package/dist/lib/signals/engine.js +495 -0
  89. package/dist/lib/signals/failure-alert.d.ts +89 -0
  90. package/dist/lib/signals/failure-alert.js +175 -0
  91. package/dist/lib/signals/local-date.d.ts +3 -0
  92. package/dist/lib/signals/local-date.js +6 -0
  93. package/dist/lib/signals/projection.d.ts +42 -0
  94. package/dist/lib/signals/projection.js +67 -0
  95. package/dist/lib/signals/reply.d.ts +112 -0
  96. package/dist/lib/signals/reply.js +368 -0
  97. package/dist/lib/signals/run-lock.d.ts +36 -0
  98. package/dist/lib/signals/run-lock.js +148 -0
  99. package/dist/lib/signals/run-record.d.ts +24 -0
  100. package/dist/lib/signals/run-record.js +82 -0
  101. package/dist/lib/signals/sanitize.d.ts +105 -0
  102. package/dist/lib/signals/sanitize.js +439 -0
  103. package/dist/lib/signals/settings.d.ts +10 -0
  104. package/dist/lib/signals/settings.js +48 -0
  105. package/dist/lib/signals/steps/adapters.d.ts +14 -0
  106. package/dist/lib/signals/steps/adapters.js +156 -0
  107. package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
  108. package/dist/lib/signals/steps/entity-similarity.js +47 -0
  109. package/dist/lib/signals/steps/extract.d.ts +113 -0
  110. package/dist/lib/signals/steps/extract.js +765 -0
  111. package/dist/lib/signals/steps/garden.d.ts +24 -0
  112. package/dist/lib/signals/steps/garden.js +250 -0
  113. package/dist/lib/signals/steps/types.d.ts +112 -0
  114. package/dist/lib/signals/steps/types.js +4 -0
  115. package/dist/lib/signals/steps/upload.d.ts +43 -0
  116. package/dist/lib/signals/steps/upload.js +324 -0
  117. package/dist/lib/signals/write-guard.d.ts +63 -0
  118. package/dist/lib/signals/write-guard.js +287 -0
  119. package/dist/utils/resident-package-preload.js +16 -0
  120. package/package.json +9 -9
  121. package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
  122. package/dist/lib/bot/fleet-default-flag.js +0 -127
@@ -1 +1 @@
1
- {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.107","bundleSha256":"4ffaf3a579e9222aa2e9a1850e355d4a152d6d3323cf0583154cf28e1d45459a","formatVersion":1}
1
+ {"name":"hq-cli-daemon-sync-runner","type":"module","version":"6.18.108","bundleSha256":"b908c99715d125c651043d1a67e0549d7b05e9d6f7ab42b6963b60789df1f2ba","formatVersion":1}
@@ -39,8 +39,9 @@ export interface InboxItem {
39
39
  memberCount?: number;
40
40
  senderTrust?: "member";
41
41
  /**
42
- * Scheduled-job run items only (channel === "job"). The cloud fires the job
43
- * on its own schedule and writes this durable item; the run carries its own
42
+ * Scheduled-job run items only (channel === "job"). A local bot's own loop
43
+ * builds them from its jobs.json (lib/bot/local-jobs.ts); for a hosted agent
44
+ * the cloud fires the job and writes this durable item. The run carries its own
44
45
  * prompt instead of `text` (server: hq-pro-agents jobs/run-record.ts
45
46
  * `JobRunInboxItem`).
46
47
  */
@@ -93,6 +93,8 @@ export interface BotConfig {
93
93
  engine?: BotEngineMetadata;
94
94
  fleetAuthPending?: boolean;
95
95
  fleetServiceInstallPending?: boolean;
96
+ /** A newer HQ Fleet Local release is installed for this bot; the gateway restarts once the bot is idle. */
97
+ fleetRestartPending?: boolean;
96
98
  /**
97
99
  * "personal" (acts as its owner everywhere) or "company" (acts as itself,
98
100
  * member of `companies`). Missing on bots created before kinds existed:
@@ -0,0 +1,121 @@
1
+ import { type BotConfig } from "./config.js";
2
+ import { type FleetLocalControlDeps } from "./fleet-local.js";
3
+ export interface FleetCronSchedule {
4
+ kind?: string;
5
+ expr?: string;
6
+ minutes?: number;
7
+ run_at?: string;
8
+ display?: string;
9
+ }
10
+ export interface FleetCronJob {
11
+ id: string;
12
+ name?: string;
13
+ prompt?: string;
14
+ script?: string | null;
15
+ no_agent?: boolean;
16
+ schedule?: FleetCronSchedule;
17
+ schedule_display?: string;
18
+ enabled?: boolean;
19
+ state?: string;
20
+ created_at?: string;
21
+ next_run_at?: string | null;
22
+ last_run_at?: string | null;
23
+ last_status?: string | null;
24
+ repeat?: {
25
+ times?: number | null;
26
+ completed?: number;
27
+ };
28
+ deliver?: string;
29
+ origin?: Record<string, unknown> | null;
30
+ workdir?: string | null;
31
+ fire_claim?: unknown;
32
+ }
33
+ export interface FleetCronOrigin {
34
+ platform: "hqdm";
35
+ chat_id: string;
36
+ user_id: string;
37
+ chat_name?: string;
38
+ }
39
+ export interface FleetCronCreateInput {
40
+ prompt: string;
41
+ schedule: string;
42
+ origin: FleetCronOrigin;
43
+ name?: string;
44
+ script?: string;
45
+ noAgent?: boolean;
46
+ workdir?: string;
47
+ }
48
+ export interface FleetCronOps {
49
+ list(): FleetCronJob[];
50
+ create(input: FleetCronCreateInput): FleetCronJob;
51
+ remove(id: string): boolean;
52
+ reschedule(id: string, schedule: string): FleetCronJob | null;
53
+ }
54
+ export type FleetCronRunner = (executable: string, args: string[], options: Record<string, unknown>) => Buffer | string;
55
+ export interface FleetCronDeps extends Pick<FleetLocalControlDeps, "fleetHome" | "platform"> {
56
+ run?: FleetCronRunner;
57
+ env?: NodeJS.ProcessEnv;
58
+ }
59
+ export declare const FLEET_CRON_BRIDGE: string;
60
+ export declare function runFleetCronRequest(agentUid: string, request: Record<string, unknown>, deps?: FleetCronDeps): unknown;
61
+ export declare function fleetCronOps(agentUid: string, deps?: FleetCronDeps): FleetCronOps;
62
+ export declare function fleetOwnerOrigin(ownerUid: string, jobs?: readonly FleetCronJob[]): FleetCronOrigin;
63
+ export declare function fleetScheduleFromLegacy(expr: string): string;
64
+ export interface FleetJobsMigrationResult {
65
+ imported: number;
66
+ alreadyPresent: number;
67
+ skipped: string[];
68
+ /** Where the old list went. Null when some jobs could not move: jobs.json then stays. */
69
+ renamedTo: string | null;
70
+ }
71
+ export declare function migrateLegacyJobsToFleet(dir: string, config: Pick<BotConfig, "ownerUid" | "hqRoot">, ops: FleetCronOps, now?: Date): FleetJobsMigrationResult | null;
72
+ export declare const FLEET_HOT_SIGNAL_SYNC_NAME = "hot-signal-sync";
73
+ export declare const FLEET_HOT_SIGNAL_SYNC_SCHEDULE = "30 6 * * *";
74
+ export declare function fleetHotSignalSyncScript(company: string): string;
75
+ export declare function companyOfHotSignalSyncJob(job: FleetCronJob): string | null;
76
+ export declare function renderFleetHotSignalSyncScript(input: {
77
+ nodeBinary: string;
78
+ hqBinary: string;
79
+ company: string;
80
+ hqRoot: string;
81
+ logFile: string;
82
+ /**
83
+ * cli.bot-signals-engine-job. On (the default here): the script runs the
84
+ * signals engine. Off: it runs the plain hot-channel sync (`hq sources sync`),
85
+ * the job these bots ran before the engine.
86
+ */
87
+ signalsEngine?: boolean;
88
+ }): string;
89
+ export interface EnsureFleetHotSignalSyncInput {
90
+ dir: string;
91
+ config: Pick<BotConfig, "ownerUid" | "hqRoot" | "companySlug" | "kind" | "companies" | "workerId">;
92
+ ops: FleetCronOps;
93
+ profileDir: string;
94
+ activeCompany: string | null;
95
+ ownerCompanies?: readonly string[];
96
+ otherBotDirs: readonly string[];
97
+ nodeBinary: string;
98
+ hqBinary: string;
99
+ /**
100
+ * cli.bot-signals-engine-job (default off). Every company still gets the
101
+ * daily 06:30 hot-signal-sync job; the flag picks what its script runs: the
102
+ * signals engine when true, the plain hot-channel sync when false. The script
103
+ * is rewritten on every call, so flipping the flag takes effect on the next
104
+ * bot start without re-registering the job.
105
+ */
106
+ signalsJobEnabled: boolean;
107
+ now?: Date;
108
+ }
109
+ export interface FleetHotSignalSyncCompanyResult {
110
+ company: string;
111
+ created: boolean;
112
+ job: FleetCronJob | null;
113
+ heldBy?: string;
114
+ }
115
+ export interface EnsureFleetHotSignalSyncResult {
116
+ companies: FleetHotSignalSyncCompanyResult[];
117
+ removed: string[];
118
+ }
119
+ export declare function fleetHotSignalSyncCompanies(config: EnsureFleetHotSignalSyncInput["config"], activeCompany: string | null, ownerCompanies?: readonly string[]): string[];
120
+ export declare function ensureFleetHotSignalSync(input: EnsureFleetHotSignalSyncInput): EnsureFleetHotSignalSyncResult;
121
+ //# sourceMappingURL=fleet-cron.d.ts.map
@@ -0,0 +1,319 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { effectiveBotCompanies, effectiveBotKind } from "./config.js";
5
+ import { fleetLocalRuntimePython, stderrTail } from "./fleet-local.js";
6
+ import { readLocalJobs, withJobsLock, writeLocalJobs } from "./local-jobs.js";
7
+ import { currentHotSignalSyncCompany, forgetHotSignalSyncRegistrationsExcept, hotSignalSyncJobCompany, fleetHotSignalSyncRegistered, markFleetHotSignalSyncRegistered, otherBotWithHotSignalSync, } from "./jobs/hot-signal-sync.js";
8
+ const RESULT_MARKER = "@@HQ_FLEET_CRON@@";
9
+ export const FLEET_CRON_BRIDGE = [
10
+ "import json, os, sys",
11
+ "sys.path.insert(0, sys.argv[1])",
12
+ "request = json.loads(sys.stdin.read())",
13
+ "def emit(value):",
14
+ ` sys.stdout.write("\\n${RESULT_MARKER}" + json.dumps(value, default=str) + "\\n")`,
15
+ "try:",
16
+ " from cron import jobs as cron_jobs",
17
+ " with cron_jobs.use_cron_store(os.environ['HQ_FLEET_HOME']):",
18
+ " op = request.get('op')",
19
+ " if op == 'list':",
20
+ " result = cron_jobs.list_jobs(include_disabled=True)",
21
+ " elif op == 'create':",
22
+ " result = cron_jobs.create_job(**request['job'])",
23
+ " elif op == 'remove':",
24
+ " result = cron_jobs.remove_job(request['id'])",
25
+ " elif op == 'reschedule':",
26
+ " result = cron_jobs.update_job(request['id'], {'schedule': request['schedule']})",
27
+ " else:",
28
+ " raise ValueError('unknown operation ' + str(op))",
29
+ " emit({'ok': True, 'result': result})",
30
+ "except Exception as exc:",
31
+ " emit({'ok': False, 'error': str(exc), 'type': type(exc).__name__})",
32
+ ].join("\n");
33
+ function expectedError(message) {
34
+ return Object.assign(new Error(message), { expected: true });
35
+ }
36
+ export function runFleetCronRequest(agentUid, request, deps = {}) {
37
+ const runtime = fleetLocalRuntimePython(agentUid, deps);
38
+ const run = deps.run ?? execFileSync;
39
+ let output;
40
+ try {
41
+ output = run(runtime.python, ["-I", "-c", FLEET_CRON_BRIDGE, runtime.source], {
42
+ input: JSON.stringify(request),
43
+ encoding: "utf8",
44
+ stdio: ["pipe", "pipe", "pipe"],
45
+ env: { ...(deps.env ?? process.env), HQ_FLEET_HOME: runtime.profileDir, HQ_FLEET_ENABLE_PROJECT_PLUGINS: "0" },
46
+ timeout: 60_000,
47
+ maxBuffer: 16 * 1024 * 1024,
48
+ windowsHide: true,
49
+ });
50
+ }
51
+ catch (error) {
52
+ const tail = stderrTail(error);
53
+ throw expectedError(`The HQ Fleet Runtime could not open the bot's scheduled jobs.${tail ? `\n${tail}` : ""}`);
54
+ }
55
+ const text = Buffer.isBuffer(output) ? output.toString("utf8") : output;
56
+ const line = text.split(/\r?\n/).reverse().find((entry) => entry.startsWith(RESULT_MARKER));
57
+ if (!line)
58
+ throw expectedError("The HQ Fleet Runtime returned no answer about the bot's scheduled jobs.");
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(line.slice(RESULT_MARKER.length));
62
+ }
63
+ catch {
64
+ throw expectedError("The HQ Fleet Runtime returned an unreadable answer about the bot's scheduled jobs.");
65
+ }
66
+ if (parsed.ok !== true) {
67
+ throw expectedError(typeof parsed.error === "string" && parsed.error ? parsed.error : "The HQ Fleet Runtime refused the change.");
68
+ }
69
+ return parsed.result;
70
+ }
71
+ function asJob(value) {
72
+ if (!value || typeof value !== "object" || Array.isArray(value))
73
+ return null;
74
+ const record = value;
75
+ return typeof record.id === "string" && record.id ? record : null;
76
+ }
77
+ export function fleetCronOps(agentUid, deps = {}) {
78
+ return {
79
+ list() {
80
+ const result = runFleetCronRequest(agentUid, { op: "list" }, deps);
81
+ return Array.isArray(result) ? result.map(asJob).filter((job) => job !== null) : [];
82
+ },
83
+ create(input) {
84
+ const job = asJob(runFleetCronRequest(agentUid, {
85
+ op: "create",
86
+ job: {
87
+ prompt: input.prompt,
88
+ schedule: input.schedule,
89
+ deliver: "origin",
90
+ origin: input.origin,
91
+ ...(input.name ? { name: input.name } : {}),
92
+ ...(input.script ? { script: input.script } : {}),
93
+ ...(input.noAgent ? { no_agent: true } : {}),
94
+ ...(input.workdir ? { workdir: input.workdir } : {}),
95
+ },
96
+ }, deps));
97
+ if (!job)
98
+ throw expectedError("The HQ Fleet Runtime did not return the new job.");
99
+ return job;
100
+ },
101
+ remove(id) {
102
+ return runFleetCronRequest(agentUid, { op: "remove", id }, deps) === true;
103
+ },
104
+ reschedule(id, schedule) {
105
+ return asJob(runFleetCronRequest(agentUid, { op: "reschedule", id, schedule }, deps));
106
+ },
107
+ };
108
+ }
109
+ export function fleetOwnerOrigin(ownerUid, jobs = []) {
110
+ const known = jobs
111
+ .map((job) => job.origin)
112
+ .find((origin) => origin && origin.platform === "hqdm" && origin.chat_id === ownerUid && typeof origin.chat_name === "string" && origin.chat_name);
113
+ return {
114
+ platform: "hqdm",
115
+ chat_id: ownerUid,
116
+ user_id: ownerUid,
117
+ ...(known ? { chat_name: known.chat_name } : {}),
118
+ };
119
+ }
120
+ function throwSchedule(message) {
121
+ throw Object.assign(new Error(message), { expected: true });
122
+ }
123
+ function unshiftDayOfWeekTerm(term) {
124
+ const step = term.match(/^(.+)\/(\d+)$/);
125
+ if (step)
126
+ return `${unshiftDayOfWeekTerm(step[1])}/${step[2]}`;
127
+ if (term === "*")
128
+ return term;
129
+ const range = term.match(/^(\d+)-(\d+)$/);
130
+ if (range)
131
+ return `${Number(range[1]) - 1}-${Number(range[2]) - 1}`;
132
+ if (/^\d+$/.test(term))
133
+ return String(Number(term) - 1);
134
+ return term;
135
+ }
136
+ export function fleetScheduleFromLegacy(expr) {
137
+ const trimmed = expr.trim();
138
+ const rate = trimmed.match(/^rate\(\s*(\d+)\s+(minute|minutes|hour|hours|day|days)\s*\)$/i);
139
+ if (rate)
140
+ return `every ${rate[1]}${rate[2].toLowerCase()[0]}`;
141
+ const cron = trimmed.match(/^cron\(\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*\)$/i);
142
+ if (!cron)
143
+ throwSchedule(`"${trimmed}" is not a schedule the HQ Fleet Runtime can run.`);
144
+ const [minute, hour, dayOfMonth, month, dayOfWeek, year] = cron.slice(1);
145
+ if (year !== "*")
146
+ throwSchedule(`"${trimmed}" names a year; the HQ Fleet Runtime runs recurring schedules only.`);
147
+ const dom = dayOfMonth === "?" ? "*" : dayOfMonth;
148
+ const dow = dayOfWeek === "?" ? "*" : dayOfWeek.split(",").map((term) => unshiftDayOfWeekTerm(term.trim())).join(",");
149
+ return `${minute} ${hour} ${dom} ${month} ${dow}`;
150
+ }
151
+ function migratedJobsPath(dir, now) {
152
+ const first = path.join(dir, "jobs.migrated.json");
153
+ if (!fs.existsSync(first))
154
+ return first;
155
+ return path.join(dir, `jobs.migrated-${now.toISOString().replace(/[:.]/g, "-")}.json`);
156
+ }
157
+ export function migrateLegacyJobsToFleet(dir, config, ops, now = new Date()) {
158
+ const file = path.join(dir, "jobs.json");
159
+ if (!fs.existsSync(file))
160
+ return null;
161
+ return withJobsLock(dir, () => {
162
+ const legacy = readLocalJobs(dir).filter((job) => hotSignalSyncJobCompany(job.context) === null);
163
+ if (legacy.length === 0)
164
+ return null;
165
+ const existing = ops.list();
166
+ const origin = fleetOwnerOrigin(config.ownerUid, existing);
167
+ let imported = 0;
168
+ let alreadyPresent = 0;
169
+ const skipped = [];
170
+ const moved = new Set();
171
+ for (const job of legacy) {
172
+ let schedule;
173
+ try {
174
+ schedule = /^(cron|rate)\(/i.test(job.cron.trim()) ? fleetScheduleFromLegacy(job.cron) : job.cron.trim();
175
+ }
176
+ catch {
177
+ skipped.push(job.jobId);
178
+ continue;
179
+ }
180
+ const present = existing.some((candidate) => candidate.prompt === job.prompt && candidate.schedule?.expr === schedule);
181
+ if (present) {
182
+ alreadyPresent += 1;
183
+ moved.add(job.jobId);
184
+ continue;
185
+ }
186
+ existing.push(ops.create({ prompt: job.prompt, schedule, origin, workdir: config.hqRoot }));
187
+ imported += 1;
188
+ moved.add(job.jobId);
189
+ }
190
+ if (skipped.length > 0) {
191
+ // Some jobs could not move. jobs.json stays, with the jobs that could not
192
+ // move left in it; only the jobs now in the Fleet schedule leave it.
193
+ if (moved.size > 0) {
194
+ const all = readLocalJobs(dir);
195
+ writeLocalJobs(dir, all.filter((job) => !moved.has(job.jobId)));
196
+ }
197
+ return { imported, alreadyPresent, skipped, renamedTo: null };
198
+ }
199
+ const renamedTo = migratedJobsPath(dir, now);
200
+ fs.renameSync(file, renamedTo);
201
+ return { imported, alreadyPresent, skipped, renamedTo };
202
+ });
203
+ }
204
+ export const FLEET_HOT_SIGNAL_SYNC_NAME = "hot-signal-sync";
205
+ export const FLEET_HOT_SIGNAL_SYNC_SCHEDULE = "30 6 * * *";
206
+ export function fleetHotSignalSyncScript(company) {
207
+ return `hot-signal-sync-${company}.py`;
208
+ }
209
+ export function companyOfHotSignalSyncJob(job) {
210
+ if (job.name !== FLEET_HOT_SIGNAL_SYNC_NAME || typeof job.script !== "string")
211
+ return null;
212
+ const match = /^hot-signal-sync-([a-z0-9][a-z0-9_-]*)\.py$/i.exec(path.basename(job.script));
213
+ return match ? match[1] : null;
214
+ }
215
+ export function renderFleetHotSignalSyncScript(input) {
216
+ const args = input.signalsEngine === false
217
+ ? ["sources", "sync", "--company", input.company, "--hq-root", input.hqRoot]
218
+ : ["signals", "run", "--trigger", "job", "--company", input.company, "--hq-root", input.hqRoot, "--report-found"];
219
+ // The engine prints only when it finds new signals (--report-found), so its
220
+ // stdout goes to the owner's DM as is. The plain sync prints a report every
221
+ // run, so with the flag off its stdout goes to the log and the owner hears
222
+ // only about a run that failed (for example, nobody signed in).
223
+ const deliver = input.signalsEngine === false
224
+ ? [
225
+ " with open(LOG, 'a', encoding='utf-8') as log:",
226
+ " log.write(result.stdout)",
227
+ " if result.returncode != 0:",
228
+ ` sys.stdout.write(${JSON.stringify(`The 06:30 hot-channel sync for ${input.company} did not finish. Details are in `)} + LOG + '\\n')`,
229
+ ]
230
+ : [" sys.stdout.write(result.stdout)"];
231
+ return [
232
+ "import subprocess, sys",
233
+ `NODE = ${JSON.stringify(input.nodeBinary)}`,
234
+ `HQ = ${JSON.stringify(input.hqBinary)}`,
235
+ `ARGS = ${JSON.stringify(args)}`,
236
+ `LOG = ${JSON.stringify(input.logFile)}`,
237
+ "try:",
238
+ " with open(LOG, 'a', encoding='utf-8') as log:",
239
+ " result = subprocess.run([NODE, HQ, *ARGS], stdout=subprocess.PIPE, stderr=log, text=True)",
240
+ ...deliver,
241
+ "except OSError as exc:",
242
+ " with open(LOG, 'a', encoding='utf-8') as log:",
243
+ " log.write('hot-signal-sync could not run hq: ' + str(exc) + '\\n')",
244
+ "",
245
+ ].join("\n");
246
+ }
247
+ export function fleetHotSignalSyncCompanies(config, activeCompany, ownerCompanies = []) {
248
+ const single = currentHotSignalSyncCompany(config, activeCompany);
249
+ if (config.companySlug || effectiveBotCompanies(config).length > 0 || effectiveBotKind(config) === "company") {
250
+ return single ? [single] : [];
251
+ }
252
+ const owned = [...new Set(ownerCompanies.map((slug) => slug.trim()).filter((slug) => slug && slug !== "personal"))].sort();
253
+ if (owned.length > 0)
254
+ return owned;
255
+ return single ? [single] : [];
256
+ }
257
+ export function ensureFleetHotSignalSync(input) {
258
+ const companies = fleetHotSignalSyncCompanies(input.config, input.activeCompany, input.ownerCompanies);
259
+ const wanted = new Set(companies);
260
+ const jobs = input.ops.list();
261
+ const removed = [];
262
+ for (const job of jobs) {
263
+ const jobCompany = companyOfHotSignalSyncJob(job);
264
+ if (jobCompany !== null && !wanted.has(jobCompany) && input.ops.remove(job.id))
265
+ removed.push(job.id);
266
+ }
267
+ forgetHotSignalSyncRegistrationsExcept(input.dir, companies);
268
+ const results = [];
269
+ let remaining = jobs.filter((job) => !removed.includes(job.id));
270
+ for (const company of companies) {
271
+ const result = ensureFleetHotSignalSyncFor(input, company, remaining);
272
+ if (result.job && !remaining.some((job) => job.id === result.job.id))
273
+ remaining = [...remaining, result.job];
274
+ results.push(result);
275
+ }
276
+ return { companies: results, removed };
277
+ }
278
+ function ensureFleetHotSignalSyncFor(input, company, jobs) {
279
+ const scriptName = fleetHotSignalSyncScript(company);
280
+ const scriptsDir = path.join(input.profileDir, "scripts");
281
+ fs.mkdirSync(scriptsDir, { recursive: true, mode: 0o700 });
282
+ const logFile = path.join(input.dir, "logs", "hot-signal-sync.log");
283
+ fs.mkdirSync(path.dirname(logFile), { recursive: true, mode: 0o700 });
284
+ const scriptFile = path.join(scriptsDir, scriptName);
285
+ const temporary = `${scriptFile}.${process.pid}.tmp`;
286
+ fs.writeFileSync(temporary, renderFleetHotSignalSyncScript({
287
+ nodeBinary: input.nodeBinary,
288
+ hqBinary: input.hqBinary,
289
+ company,
290
+ hqRoot: input.config.hqRoot,
291
+ logFile,
292
+ signalsEngine: input.signalsJobEnabled === true,
293
+ }), { mode: 0o600 });
294
+ fs.renameSync(temporary, scriptFile);
295
+ const current = jobs.find((job) => companyOfHotSignalSyncJob(job) === company) ?? null;
296
+ if (fleetHotSignalSyncRegistered(input.dir, company))
297
+ return { company, created: false, job: current };
298
+ if (current) {
299
+ markFleetHotSignalSyncRegistered(input.dir, company, input.now);
300
+ return { company, created: false, job: current };
301
+ }
302
+ const heldBy = otherBotWithHotSignalSync(input.dir, company, input.otherBotDirs);
303
+ if (heldBy)
304
+ return { company, created: false, job: null, heldBy };
305
+ const job = input.ops.create({
306
+ name: FLEET_HOT_SIGNAL_SYNC_NAME,
307
+ prompt: input.signalsJobEnabled === true
308
+ ? `Run the signals engine for ${company} and post to the owner only when it finds new signals.`
309
+ : `Run the hot-channel sync for ${company}.`,
310
+ schedule: FLEET_HOT_SIGNAL_SYNC_SCHEDULE,
311
+ script: scriptName,
312
+ noAgent: true,
313
+ origin: fleetOwnerOrigin(input.config.ownerUid, [...jobs]),
314
+ workdir: input.config.hqRoot,
315
+ });
316
+ markFleetHotSignalSyncRegistered(input.dir, company, input.now);
317
+ return { company, created: true, job };
318
+ }
319
+ //# sourceMappingURL=fleet-cron.js.map
@@ -1,6 +1,7 @@
1
1
  import { type BotEngineMetadata } from "./config.js";
2
2
  import type { BotMachineCreds } from "./creds.js";
3
3
  import { type FleetPython } from "./fleet-python.js";
4
+ import { vaultApiFetch } from "../../utils/vault-api.js";
4
5
  import type { LocalBotPromotionRuntime } from "./promote.js";
5
6
  export interface FleetLocalReleasePin {
6
7
  repository: string;
@@ -41,6 +42,7 @@ export interface FleetLocalProfile {
41
42
  transport?: string;
42
43
  };
43
44
  ownerCompanies?: string[];
45
+ autoApprove?: boolean;
44
46
  hqRoot: string;
45
47
  profileDir: string;
46
48
  runtime: {
@@ -60,7 +62,7 @@ export interface FleetLocalInstallDeps {
60
62
  hqReleaseLinks?: (release: FleetLocalReleasePin) => Promise<Map<string, string> | null>;
61
63
  now?: () => Date;
62
64
  release?: FleetLocalReleasePin;
63
- publicKey?: string;
65
+ publicKey?: string | readonly string[];
64
66
  }
65
67
  export interface FleetLocalProfileInput {
66
68
  agentUid: string;
@@ -79,6 +81,8 @@ export interface FleetLocalProfileInput {
79
81
  };
80
82
  /** Companies the owner's own turns may use; other senders get none of them. */
81
83
  ownerCompanies?: string[];
84
+ /** True turns the runtime's command approval prompts off for every sender (`hq bot create --auto-approve`). */
85
+ autoApprove?: boolean;
82
86
  }
83
87
  /** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
84
88
  export declare const FLEET_CLAUDE_DEFAULT_MODEL = "claude-opus-5-5";
@@ -92,10 +96,19 @@ export declare function fleetProviderForRuntime(runtime: string, model?: string)
92
96
  /** True when a bot with this provider still needs `hq bot auth` before it can answer. */
93
97
  export declare function fleetProviderNeedsSignIn(provider: FleetLocalProfile["provider"]): boolean;
94
98
  type FleetLocalExec = (executable: string, args: string[], options: Record<string, unknown>) => Buffer | string;
99
+ /** spawnSync-shaped runner for steps whose output is copied through after they finish. */
100
+ export type FleetLocalSpawn = (executable: string, args: string[], options: Record<string, unknown>) => {
101
+ status: number | null;
102
+ stdout?: Buffer | string | null;
103
+ stderr?: Buffer | string | null;
104
+ error?: Error;
105
+ };
95
106
  export interface FleetLocalBootstrapDeps {
96
107
  fleetHome?: string;
97
108
  platform?: NodeJS.Platform;
98
109
  run?: FleetLocalExec;
110
+ /** Runs the install steps whose output is copied through. Defaults to spawnSync, or to `run` when only `run` is given. */
111
+ spawn?: FleetLocalSpawn;
99
112
  start?: boolean;
100
113
  python?: FleetPython;
101
114
  }
@@ -162,9 +175,27 @@ export interface FleetLocalRemovalResult {
162
175
  credentialsRemoved: number;
163
176
  }
164
177
  export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
178
+ /** hq-fleet-local trust/release-public.pem (rotated 2026-10-30; signed v0.4.17). */
165
179
  export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=\n-----END PUBLIC KEY-----\n";
180
+ /** hq-fleet-local trust/release-public-v2.pem (added 2026-11-01; signs v0.4.18). */
181
+ export declare const HQ_FLEET_LOCAL_PUBLIC_KEY_V2 = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAmgUi7pKTJUmf3yzwcuH65wSvn1NyJrDfZHZUX08sGN8=\n-----END PUBLIC KEY-----\n";
182
+ /** hq-fleet-local trust/release-public-v3.pem (added 2026-11-02; signs v0.4.20 and later). */
183
+ export declare const HQ_FLEET_LOCAL_PUBLIC_KEY_V3 = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA2NUo6F38D4Kjxmy5qz5ytKRVRwocEvPk8lXqfwB2kS0=\n-----END PUBLIC KEY-----\n";
184
+ /** Every key a release manifest may be signed with. Any one must verify it. */
185
+ export declare const HQ_FLEET_LOCAL_PUBLIC_KEYS: readonly string[];
186
+ /** True when any trusted Ed25519 key verifies the manifest signature. */
187
+ export declare function fleetLocalManifestSignatureValid(manifest: Buffer, signature: Buffer, publicKey?: string | readonly string[]): boolean;
166
188
  export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
167
- export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string): void;
189
+ export declare function verifyFleetLocalRelease(files: FleetLocalReleaseFiles, release?: FleetLocalReleasePin, publicKey?: string | readonly string[]): void;
190
+ /**
191
+ * Check an extracted distribution against the signed manifest: every payload
192
+ * file present with its hash, and nothing else but the release metadata.
193
+ */
194
+ export declare function verifyExtractedDistribution(dir: string, manifest: Buffer, version: string): void;
195
+ /** Write a ZIP archive's files under `target` with the same reader that verified it. */
196
+ export declare function extractZipEntries(archive: Buffer, target: string, version: string): void;
197
+ /** Every file in a ZIP archive by name (stored and deflated entries only; directories skipped). */
198
+ export declare function readZipEntries(archive: Buffer): Map<string, Buffer>;
168
199
  /**
169
200
  * Warning text when the signed manifest asks for a newer hq CLI than this
170
201
  * one, or null. Advisory only: the pinned runtime is still installed, since
@@ -175,12 +206,60 @@ export declare function fleetLocalCliCompatibilityWarning(manifest: Buffer, rele
175
206
  export declare const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15000;
176
207
  export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv, run?: FleetLocalExec): string;
177
208
  export declare function fetchHqFleetReleaseLinks(release: FleetLocalReleasePin): Promise<Map<string, string> | null>;
209
+ export declare const FLEET_LOCAL_LATEST_TIMEOUT_MS = 5000;
210
+ export declare const FLEET_LOCAL_LATEST_CACHE_MS: number;
211
+ export interface FleetLocalReleasePointer {
212
+ release: FleetLocalReleasePin;
213
+ minimumHqCli: string | null;
214
+ links: Map<string, string> | null;
215
+ linksExpireAt: number | null;
216
+ }
217
+ export interface FleetLocalTargetRelease {
218
+ release: FleetLocalReleasePin;
219
+ source: "pointer" | "compiled";
220
+ links: Map<string, string> | null;
221
+ linksExpireAt: number | null;
222
+ }
223
+ export declare function parseFleetLocalReleasePin(value: unknown): FleetLocalReleasePin | null;
224
+ export declare function parseFleetLocalReleasePointer(body: unknown, now?: number): FleetLocalReleasePointer | null;
225
+ export declare function chooseFleetLocalTargetRelease(pointer: FleetLocalReleasePointer | null, compiled?: FleetLocalReleasePin, cliVersion?: string): FleetLocalTargetRelease;
226
+ export declare function fetchFleetLocalReleasePointer(deps?: {
227
+ token?: () => Promise<string>;
228
+ fetchApi?: typeof vaultApiFetch;
229
+ now?: () => number;
230
+ }): Promise<FleetLocalReleasePointer | null>;
231
+ export declare function resetFleetLocalReleasePointerCache(): void;
232
+ interface FleetLocalPointerDeps {
233
+ fleetHome?: string;
234
+ now?: () => number;
235
+ fetchPointer?: () => Promise<FleetLocalReleasePointer | null>;
236
+ }
237
+ export declare function resolveFleetLocalTargetRelease(deps?: FleetLocalPointerDeps & {
238
+ compiled?: FleetLocalReleasePin;
239
+ cliVersion?: string;
240
+ }): Promise<FleetLocalTargetRelease>;
241
+ export declare function resolveFleetLocalPointerTarget(deps?: FleetLocalPointerDeps & {
242
+ cliVersion?: string;
243
+ }): Promise<FleetLocalTargetRelease | null>;
244
+ export declare function installTargetFleetLocalDistribution(deps?: Omit<FleetLocalInstallDeps, "release"> & {
245
+ target?: FleetLocalTargetRelease;
246
+ now?: () => number;
247
+ }): Promise<FleetLocalInstallation>;
178
248
  export declare const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30000;
179
249
  export declare const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS: number;
180
250
  export declare const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS: number;
181
251
  export declare function installPinnedFleetLocalDistribution(deps?: FleetLocalInstallDeps): Promise<FleetLocalInstallation>;
182
252
  /** The process id the bot's gateway recorded, when its file reads; the caller checks whether it is alive. */
183
253
  export declare function fleetLocalGatewayPid(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): number | null;
254
+ /**
255
+ * Whether the bot's HQ Fleet gateway is installed as a login service. The
256
+ * runtime registers it with launchd as `ai.hq-fleet.gateway-<profile>`; null
257
+ * on platforms where hq-cli does not know the service layout.
258
+ */
259
+ export declare function fleetLocalServiceInstalled(agentUid: string, deps?: {
260
+ platform?: NodeJS.Platform;
261
+ home?: string;
262
+ }): boolean | null;
184
263
  export declare function fleetLocalProfileNeedsAdoption(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform"> & {
185
264
  release?: FleetLocalReleasePin;
186
265
  }): boolean;
@@ -202,10 +281,42 @@ export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: stri
202
281
  * the bot's own interpreter, says beforehand what it would refuse.
203
282
  */
204
283
  export declare function scanFleetLocalInstructions(agentUid: string, text: string, deps?: FleetLocalControlDeps): string[];
284
+ export declare function fleetLocalRuntimePython(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome" | "platform">): {
285
+ python: string;
286
+ source: string;
287
+ profileDir: string;
288
+ };
289
+ export declare function fleetProfileOwnerCompanies(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): string[];
290
+ export declare function fleetLocalProfileDir(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): string;
291
+ export interface FleetLocalGatewayState {
292
+ pid: number | null;
293
+ gatewayState: string | null;
294
+ activeAgents: number;
295
+ platforms: Record<string, string>;
296
+ }
297
+ export declare function readFleetLocalGatewayState(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): FleetLocalGatewayState | null;
298
+ export declare function fleetLocalChannels(state: FleetLocalGatewayState | null, gatewayAlive: boolean): {
299
+ slack: string;
300
+ telegram: string;
301
+ };
302
+ export declare function fleetLocalBusyReason(agentUid: string, deps?: Pick<FleetLocalControlDeps, "fleetHome"> & {
303
+ isAlive?: (pid: number) => boolean;
304
+ }): string | null;
205
305
  /** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
206
306
  export declare function refreshFleetLocalInstructions(agentUid: string, systemPrompt: string, deps?: FleetLocalControlDeps): string;
207
307
  /** Last few non-empty stderr lines of a failed child process, trimmed for an error message. */
208
308
  export declare function stderrTail(error: unknown): string;
309
+ /** A FleetLocalSpawn backed by an execFileSync-shaped runner (stderr is only known on failure). */
310
+ export declare function spawnFromRun(run: FleetLocalExec): FleetLocalSpawn;
311
+ /**
312
+ * Replaces inherited stdio slots (an fd number or "inherit") with "pipe".
313
+ * `forward` is true when any slot was replaced, so the caller copies the
314
+ * captured output to this process's stderr.
315
+ */
316
+ export declare function pipeInheritedStdio(stdio: unknown): {
317
+ stdio: unknown;
318
+ forward: boolean;
319
+ };
209
320
  /**
210
321
  * Message for a Windows extraction that cannot fit under MAX_PATH, or null
211
322
  * when it fits or long paths are on. `longest` is the longest member name in