@indigoai-us/hq-cli 5.345.191 → 5.345.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +431 -0
  2. package/assets/bot-workers/setup/worker.yaml +91 -34
  3. package/dist/command-catalog.generated.d.ts +145 -19
  4. package/dist/command-catalog.generated.js +184 -21
  5. package/dist/commands/bot-jobs.d.ts +21 -18
  6. package/dist/commands/bot-jobs.js +127 -39
  7. package/dist/commands/bot.d.ts +4 -3
  8. package/dist/commands/bot.js +265 -140
  9. package/dist/commands/core-jobs-schedule.js +10 -0
  10. package/dist/commands/daemon.js +37 -0
  11. package/dist/commands/files.d.ts +41 -6
  12. package/dist/commands/files.js +108 -27
  13. package/dist/commands/pack-start-bot.d.ts +4 -6
  14. package/dist/commands/pack-start-bot.js +4 -8
  15. package/dist/commands/signals-run.d.ts +46 -0
  16. package/dist/commands/signals-run.js +180 -0
  17. package/dist/commands/signals.d.ts +1 -0
  18. package/dist/commands/signals.js +3 -0
  19. package/dist/commands/sources.d.ts +58 -0
  20. package/dist/commands/sources.js +294 -26
  21. package/dist/commands/staff/reset-test-account.d.ts +10 -0
  22. package/dist/commands/staff/reset-test-account.js +18 -2
  23. package/dist/commands/whoami.d.ts +7 -0
  24. package/dist/commands/whoami.js +35 -2
  25. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
  26. package/dist/hq-cloud-sync-runner/package.json +1 -1
  27. package/dist/lib/bot/api.d.ts +3 -2
  28. package/dist/lib/bot/config.d.ts +2 -0
  29. package/dist/lib/bot/fleet-cron.d.ts +121 -0
  30. package/dist/lib/bot/fleet-cron.js +319 -0
  31. package/dist/lib/bot/fleet-local.d.ts +93 -1
  32. package/dist/lib/bot/fleet-local.js +546 -34
  33. package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
  34. package/dist/lib/bot/fleet-owner-companies.js +30 -0
  35. package/dist/lib/bot/fleet-watch.d.ts +25 -0
  36. package/dist/lib/bot/fleet-watch.js +51 -0
  37. package/dist/lib/bot/index.d.ts +1 -1
  38. package/dist/lib/bot/index.js +1 -1
  39. package/dist/lib/bot/job-run.d.ts +5 -4
  40. package/dist/lib/bot/job-run.js +5 -4
  41. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
  42. package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
  43. package/dist/lib/bot/local-jobs.d.ts +13 -2
  44. package/dist/lib/bot/local-jobs.js +17 -4
  45. package/dist/lib/bot/progress.d.ts +14 -0
  46. package/dist/lib/bot/progress.js +47 -1
  47. package/dist/lib/bot/run.d.ts +55 -2
  48. package/dist/lib/bot/run.js +192 -10
  49. package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
  50. package/dist/lib/bot/setup-tool-offer.js +16 -0
  51. package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
  52. package/dist/lib/bot/signals-engine-flag.js +30 -0
  53. package/dist/lib/daemon/catalog.d.ts +4 -0
  54. package/dist/lib/daemon/catalog.js +29 -9
  55. package/dist/lib/daemon/legacy.d.ts +6 -0
  56. package/dist/lib/daemon/legacy.js +16 -10
  57. package/dist/lib/daemon/run.d.ts +1 -0
  58. package/dist/lib/daemon/run.js +10 -0
  59. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  60. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  61. package/dist/lib/flag-registry.d.ts +13 -1
  62. package/dist/lib/flag-registry.js +46 -0
  63. package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
  64. package/dist/lib/hot-channels/adapters/attio.js +367 -29
  65. package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
  66. package/dist/lib/hot-channels/adapters/gmail.js +59 -2
  67. package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
  68. package/dist/lib/hot-channels/adapters/linear.js +71 -1
  69. package/dist/lib/hot-channels/adapters/meetings.js +4 -1
  70. package/dist/lib/hot-channels/adapters/slack.js +7 -3
  71. package/dist/lib/hot-channels/alert.js +1 -1
  72. package/dist/lib/hot-channels/contract.d.ts +11 -0
  73. package/dist/lib/hot-channels/contract.js +1 -0
  74. package/dist/lib/hot-channels/cursors.js +1 -1
  75. package/dist/lib/hot-channels/landing.d.ts +6 -2
  76. package/dist/lib/hot-channels/landing.js +13 -6
  77. package/dist/lib/hot-channels/ledger.js +2 -0
  78. package/dist/lib/hot-channels/loop.d.ts +6 -0
  79. package/dist/lib/hot-channels/loop.js +19 -4
  80. package/dist/lib/hot-channels/registry.js +9 -1
  81. package/dist/lib/hot-channels/run.d.ts +18 -0
  82. package/dist/lib/hot-channels/run.js +15 -4
  83. package/dist/lib/hot-channels/signals-pass.d.ts +34 -2
  84. package/dist/lib/hot-channels/signals-pass.js +149 -38
  85. package/dist/lib/hot-channels/signals-redo.d.ts +60 -0
  86. package/dist/lib/hot-channels/signals-redo.js +207 -0
  87. package/dist/lib/hot-channels/sync-lock.js +22 -6
  88. package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
  89. package/dist/lib/mesh/live/daemon/install.js +117 -30
  90. package/dist/lib/signals/adapters-catalog.d.ts +77 -0
  91. package/dist/lib/signals/adapters-catalog.js +194 -0
  92. package/dist/lib/signals/engine.d.ts +202 -0
  93. package/dist/lib/signals/engine.js +504 -0
  94. package/dist/lib/signals/failure-alert.d.ts +89 -0
  95. package/dist/lib/signals/failure-alert.js +175 -0
  96. package/dist/lib/signals/local-date.d.ts +3 -0
  97. package/dist/lib/signals/local-date.js +6 -0
  98. package/dist/lib/signals/projection.d.ts +42 -0
  99. package/dist/lib/signals/projection.js +67 -0
  100. package/dist/lib/signals/reply.d.ts +112 -0
  101. package/dist/lib/signals/reply.js +368 -0
  102. package/dist/lib/signals/run-lock.d.ts +36 -0
  103. package/dist/lib/signals/run-lock.js +148 -0
  104. package/dist/lib/signals/run-record.d.ts +24 -0
  105. package/dist/lib/signals/run-record.js +82 -0
  106. package/dist/lib/signals/sanitize.d.ts +105 -0
  107. package/dist/lib/signals/sanitize.js +439 -0
  108. package/dist/lib/signals/settings.d.ts +10 -0
  109. package/dist/lib/signals/settings.js +48 -0
  110. package/dist/lib/signals/steps/adapters.d.ts +14 -0
  111. package/dist/lib/signals/steps/adapters.js +156 -0
  112. package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
  113. package/dist/lib/signals/steps/entity-similarity.js +47 -0
  114. package/dist/lib/signals/steps/extract.d.ts +118 -0
  115. package/dist/lib/signals/steps/extract.js +793 -0
  116. package/dist/lib/signals/steps/garden.d.ts +24 -0
  117. package/dist/lib/signals/steps/garden.js +250 -0
  118. package/dist/lib/signals/steps/types.d.ts +117 -0
  119. package/dist/lib/signals/steps/types.js +4 -0
  120. package/dist/lib/signals/steps/upload.d.ts +43 -0
  121. package/dist/lib/signals/steps/upload.js +324 -0
  122. package/dist/lib/signals/write-guard.d.ts +63 -0
  123. package/dist/lib/signals/write-guard.js +287 -0
  124. package/dist/utils/resident-package-preload.js +16 -0
  125. package/dist/utils/vault-api.d.ts +1 -0
  126. package/dist/utils/vault-api.js +1 -1
  127. package/package.json +9 -9
  128. package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
  129. package/dist/lib/bot/fleet-default-flag.js +0 -127
@@ -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;
@@ -80,7 +81,7 @@ export interface FleetLocalProfileInput {
80
81
  };
81
82
  /** Companies the owner's own turns may use; other senders get none of them. */
82
83
  ownerCompanies?: string[];
83
- /** False keeps the runtime's command approval prompts (`hq bot create --no-auto-approve`). */
84
+ /** True turns the runtime's command approval prompts off for every sender (`hq bot create --auto-approve`). */
84
85
  autoApprove?: boolean;
85
86
  }
86
87
  /** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
@@ -95,10 +96,19 @@ export declare function fleetProviderForRuntime(runtime: string, model?: string)
95
96
  /** True when a bot with this provider still needs `hq bot auth` before it can answer. */
96
97
  export declare function fleetProviderNeedsSignIn(provider: FleetLocalProfile["provider"]): boolean;
97
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
+ };
98
106
  export interface FleetLocalBootstrapDeps {
99
107
  fleetHome?: string;
100
108
  platform?: NodeJS.Platform;
101
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;
102
112
  start?: boolean;
103
113
  python?: FleetPython;
104
114
  }
@@ -169,12 +179,23 @@ export declare const HQ_FLEET_LOCAL_RELEASE: FleetLocalReleasePin;
169
179
  export declare const HQ_FLEET_LOCAL_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEASJoapvo0WMTg+Wu3Jvt0nzlXLOBEr+G1aJRi0JkEb4A=\n-----END PUBLIC KEY-----\n";
170
180
  /** hq-fleet-local trust/release-public-v2.pem (added 2026-11-01; signs v0.4.18). */
171
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";
172
184
  /** Every key a release manifest may be signed with. Any one must verify it. */
173
185
  export declare const HQ_FLEET_LOCAL_PUBLIC_KEYS: readonly string[];
174
186
  /** True when any trusted Ed25519 key verifies the manifest signature. */
175
187
  export declare function fleetLocalManifestSignatureValid(manifest: Buffer, signature: Buffer, publicKey?: string | readonly string[]): boolean;
176
188
  export declare function fleetHome(home?: string, env?: NodeJS.ProcessEnv): string;
177
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>;
178
199
  /**
179
200
  * Warning text when the signed manifest asks for a newer hq CLI than this
180
201
  * one, or null. Advisory only: the pinned runtime is still installed, since
@@ -185,6 +206,45 @@ export declare function fleetLocalCliCompatibilityWarning(manifest: Buffer, rele
185
206
  export declare const FLEET_RELEASE_TOKEN_TIMEOUT_MS = 15000;
186
207
  export declare function resolveFleetReleaseToken(env?: NodeJS.ProcessEnv, run?: FleetLocalExec): string;
187
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>;
188
248
  export declare const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30000;
189
249
  export declare const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS: number;
190
250
  export declare const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS: number;
@@ -221,10 +281,42 @@ export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: stri
221
281
  * the bot's own interpreter, says beforehand what it would refuse.
222
282
  */
223
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;
224
305
  /** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
225
306
  export declare function refreshFleetLocalInstructions(agentUid: string, systemPrompt: string, deps?: FleetLocalControlDeps): string;
226
307
  /** Last few non-empty stderr lines of a failed child process, trimmed for an error message. */
227
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
+ };
228
320
  /**
229
321
  * Message for a Windows extraction that cannot fit under MAX_PATH, or null
230
322
  * when it fits or long paths are on. `longest` is the longest member name in