@indigoai-us/hq-cli 5.345.170 → 5.345.172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +206 -7
- package/dist/command-catalog.generated.js +252 -8
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +179 -18
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +228 -10
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/daemon.js +16 -1
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +22 -4
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-core.d.ts +22 -0
- package/dist/commands/sync-core.js +124 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2447 -2135
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +142 -0
- package/dist/lib/bot/fleet-local.js +689 -0
- package/dist/lib/bot/fleet-python.d.ts +47 -0
- package/dist/lib/bot/fleet-python.js +218 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/prompt.d.ts +6 -1
- package/dist/lib/bot/prompt.js +8 -3
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +12 -4
- package/dist/lib/daemon/catalog.js +70 -24
- package/dist/lib/daemon/config.d.ts +0 -7
- package/dist/lib/daemon/config.js +0 -32
- package/dist/lib/daemon/run.d.ts +5 -1
- package/dist/lib/daemon/run.js +48 -11
- package/dist/lib/daemon/supervisor.js +3 -1
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/sync-core/engine-home.d.ts +44 -0
- package/dist/lib/sync-core/engine-home.js +193 -0
- package/dist/lib/sync-core/flag.d.ts +17 -0
- package/dist/lib/sync-core/flag.js +47 -0
- package/dist/lib/sync-core/reconcile.d.ts +31 -0
- package/dist/lib/sync-core/reconcile.js +196 -0
- package/dist/lib/sync-core/release.d.ts +32 -0
- package/dist/lib/sync-core/release.js +140 -0
- package/dist/lib/sync-core/rollout-state.d.ts +24 -0
- package/dist/lib/sync-core/rollout-state.js +94 -0
- package/dist/lib/vault-sync-switches.d.ts +5 -0
- package/dist/lib/vault-sync-switches.js +43 -0
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/remote-list-not-converging.d.ts +21 -0
- package/dist/utils/remote-list-not-converging.js +40 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
- package/dist/lib/daemon/sync-core-canary.d.ts +0 -9
- package/dist/lib/daemon/sync-core-canary.js +0 -56
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { MachineType } from "./machine-type.js";
|
|
13
13
|
import type { SyncOwner } from "./config.js";
|
|
14
|
-
import type
|
|
14
|
+
import { type SyncCoreVaultPlan } from "../sync-core/engine-home.js";
|
|
15
15
|
import type { RemoteControlPoolsConfig } from "./remote-control-pools.js";
|
|
16
16
|
import { type SyncRunnerProcess } from "./sync-runner.js";
|
|
17
17
|
import { type VaultSyncSwitches } from "../vault-sync-switches.js";
|
|
18
|
-
export declare const SERVICE_NAMES: readonly ["sync", "mesh", "heartbeat", "inbox", "bots", "remote-control", "codex-remote-control", "session-host", "lanes-reconcile", "search-index", "jobs-reconcile", "updater", "housekeeping"];
|
|
18
|
+
export declare const SERVICE_NAMES: readonly ["sync", "sync-core", "mesh", "heartbeat", "inbox", "bots", "remote-control", "codex-remote-control", "session-host", "lanes-reconcile", "search-index", "jobs-reconcile", "updater", "housekeeping"];
|
|
19
19
|
export type ServiceName = (typeof SERVICE_NAMES)[number];
|
|
20
20
|
export declare function isServiceName(value: unknown): value is ServiceName;
|
|
21
21
|
export declare const SERVICE_DESCRIPTIONS: Record<ServiceName, string>;
|
|
@@ -86,6 +86,13 @@ export interface UnitPlan {
|
|
|
86
86
|
idleIntervalSec: number;
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
export interface SyncCoreRunning {
|
|
90
|
+
personal: boolean;
|
|
91
|
+
companies: ReadonlySet<string>;
|
|
92
|
+
}
|
|
93
|
+
export declare const SYNC_CORE_RECONCILE_UNIT = "sync-core";
|
|
94
|
+
export declare const SYNC_CORE_RECONCILE_INTERVAL_SEC = 600;
|
|
95
|
+
export declare const SYNC_CORE_LEGACY_SETTLE_MS: number;
|
|
89
96
|
export interface PlanContext {
|
|
90
97
|
machineType: MachineType;
|
|
91
98
|
platform: NodeJS.Platform;
|
|
@@ -113,9 +120,10 @@ export interface PlanContext {
|
|
|
113
120
|
vaultSync?: VaultSyncSwitches;
|
|
114
121
|
/** Persisted/effective owner selected for this machine. */
|
|
115
122
|
syncOwner: SyncOwner;
|
|
116
|
-
|
|
123
|
+
syncCoreVaults?: readonly SyncCoreVaultPlan[];
|
|
124
|
+
syncCoreRunning?: SyncCoreRunning;
|
|
117
125
|
legacySyncReadyForCore?: boolean;
|
|
118
|
-
|
|
126
|
+
now?: number;
|
|
119
127
|
/** A legacy desktop app or sync unit is still managing sync. */
|
|
120
128
|
desktopAppManagesSync: boolean;
|
|
121
129
|
/** Full process list, or null when the daemon could not read it. */
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* with no entrypoint yet stay in the catalog as `unavailable` so status shows
|
|
10
10
|
* them instead of silently dropping them.
|
|
11
11
|
*/
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { syncCoreUnitId } from "../sync-core/engine-home.js";
|
|
12
14
|
import { SYNC_RUNNER_OWNER, syncRunnerUnleasedBlockReason } from "./sync-runner.js";
|
|
13
15
|
import { vaultSkipEnv } from "../vault-sync-switches.js";
|
|
14
16
|
export const SERVICE_NAMES = [
|
|
15
17
|
"sync",
|
|
18
|
+
"sync-core",
|
|
16
19
|
"mesh",
|
|
17
20
|
"heartbeat",
|
|
18
21
|
"inbox",
|
|
@@ -31,6 +34,7 @@ export function isServiceName(value) {
|
|
|
31
34
|
}
|
|
32
35
|
export const SERVICE_DESCRIPTIONS = {
|
|
33
36
|
sync: "keeps company vaults in sync with the cloud",
|
|
37
|
+
"sync-core": "moves vaults to the new sync engine when their company's switch is on",
|
|
34
38
|
mesh: "Work Mesh Live presence",
|
|
35
39
|
heartbeat: "reports this machine's status to HQ",
|
|
36
40
|
inbox: "picks up the agent's incoming messages and tasks",
|
|
@@ -46,9 +50,10 @@ export const SERVICE_DESCRIPTIONS = {
|
|
|
46
50
|
};
|
|
47
51
|
/** The services each machine type runs without being asked. */
|
|
48
52
|
export const DEFAULT_SERVICES = {
|
|
49
|
-
desktop: ["sync", "mesh", "heartbeat", "bots", "lanes-reconcile", "search-index", "updater", "housekeeping"],
|
|
53
|
+
desktop: ["sync", "sync-core", "mesh", "heartbeat", "bots", "lanes-reconcile", "search-index", "updater", "housekeeping"],
|
|
50
54
|
"self-hosted": [
|
|
51
55
|
"sync",
|
|
56
|
+
"sync-core",
|
|
52
57
|
"mesh",
|
|
53
58
|
"heartbeat",
|
|
54
59
|
"bots",
|
|
@@ -79,6 +84,9 @@ export function enabledServices(machineType, overrides) {
|
|
|
79
84
|
const defaults = DEFAULT_SERVICES[machineType];
|
|
80
85
|
return SERVICE_NAMES.filter((s) => (defaults.includes(s) || overrides.enable.includes(s)) && !overrides.disable.includes(s));
|
|
81
86
|
}
|
|
87
|
+
export const SYNC_CORE_RECONCILE_UNIT = "sync-core";
|
|
88
|
+
export const SYNC_CORE_RECONCILE_INTERVAL_SEC = 600;
|
|
89
|
+
export const SYNC_CORE_LEGACY_SETTLE_MS = 10 * 60 * 1000;
|
|
82
90
|
function base(service, over = {}) {
|
|
83
91
|
return {
|
|
84
92
|
id: service,
|
|
@@ -123,11 +131,12 @@ function planService(service, ctx) {
|
|
|
123
131
|
const agentMachine = ctx.machineType === "agent-kit" || ctx.machineType === "agent-box";
|
|
124
132
|
switch (service) {
|
|
125
133
|
case "sync": {
|
|
126
|
-
const
|
|
134
|
+
const running = ctx.syncCoreRunning;
|
|
135
|
+
const vaultSync = running && (running.personal || running.companies.size > 0)
|
|
127
136
|
? {
|
|
128
137
|
allOff: ctx.vaultSync?.allOff === true,
|
|
129
|
-
personalOff: true,
|
|
130
|
-
offCompanies: ctx.vaultSync?.offCompanies ??
|
|
138
|
+
personalOff: ctx.vaultSync?.personalOff === true || running.personal,
|
|
139
|
+
offCompanies: new Set([...(ctx.vaultSync?.offCompanies ?? []), ...running.companies]),
|
|
131
140
|
}
|
|
132
141
|
: ctx.vaultSync;
|
|
133
142
|
let ownerBlock;
|
|
@@ -181,30 +190,67 @@ function planService(service, ctx) {
|
|
|
181
190
|
blocked: commonBlock,
|
|
182
191
|
})
|
|
183
192
|
: unavailable(service, "the hq-cloud sync runner is missing from this CLI install");
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
...(ctx.
|
|
193
|
+
return [unit];
|
|
194
|
+
}
|
|
195
|
+
case "sync-core": {
|
|
196
|
+
if (ctx.platform !== "darwin" && ctx.platform !== "linux") {
|
|
197
|
+
return [unavailable(service, "the new sync engine runs on macOS and Linux only")];
|
|
198
|
+
}
|
|
199
|
+
const reconcile = base(service, {
|
|
200
|
+
id: SYNC_CORE_RECONCILE_UNIT,
|
|
201
|
+
kind: "scheduled",
|
|
202
|
+
intervalSec: SYNC_CORE_RECONCILE_INTERVAL_SEC,
|
|
203
|
+
initialDelaySec: 30,
|
|
204
|
+
command: {
|
|
205
|
+
type: "hq",
|
|
206
|
+
args: ["daemon", "service", "sync-core-reconcile", ...(ctx.hqRoot ? ["--hq-root", ctx.hqRoot] : [])],
|
|
198
207
|
},
|
|
199
|
-
|
|
208
|
+
env: { HQ_NO_UPDATE_CHECK: "1" },
|
|
200
209
|
blocked: firstBlock([
|
|
201
|
-
[
|
|
202
|
-
[
|
|
203
|
-
[ctx.vaultSync?.personalOff !== true, "turn the personal vault off in the current sync before starting the sync-core canary"],
|
|
204
|
-
[ctx.legacySyncReadyForCore !== true, "waiting for legacy sync to run its company-only plan"],
|
|
210
|
+
[!ctx.hqRoot, NO_HQ_ROOT],
|
|
211
|
+
[!ctx.signedIn, NOT_SIGNED_IN],
|
|
205
212
|
]),
|
|
206
213
|
});
|
|
207
|
-
|
|
214
|
+
const now = ctx.now ?? Date.now();
|
|
215
|
+
const engines = (ctx.syncCoreVaults ?? []).map((vault) => {
|
|
216
|
+
const full = vault.phase === "full";
|
|
217
|
+
const legacyOff = vault.kind === "personal"
|
|
218
|
+
? ctx.vaultSync?.personalOff === true
|
|
219
|
+
: ctx.vaultSync?.offCompanies.has(vault.slug ?? "") === true;
|
|
220
|
+
const offSince = vault.legacyOffSince ? Date.parse(vault.legacyOffSince) : Number.NaN;
|
|
221
|
+
const settled = Number.isFinite(offSince) && now - offSince >= SYNC_CORE_LEGACY_SETTLE_MS;
|
|
222
|
+
const legacyReady = ctx.legacySyncReadyForCore === true && (!ctx.desktopAppManagesSync || settled);
|
|
223
|
+
const what = vault.kind === "personal" ? "the personal vault" : `the ${vault.slug} vault`;
|
|
224
|
+
return base(service, {
|
|
225
|
+
id: syncCoreUnitId(vault.uid),
|
|
226
|
+
description: full
|
|
227
|
+
? `keeps ${what} in sync with the new sync engine (${vault.release})`
|
|
228
|
+
: `warms the new sync engine's index for ${what} (${vault.release}, read-only)`,
|
|
229
|
+
command: { type: "exec", file: vault.binaryPath, args: ["start", "--loop"] },
|
|
230
|
+
env: {
|
|
231
|
+
HOME: vault.engineHome,
|
|
232
|
+
HQ_STATE_DIR: path.join(ctx.home, ".hq"),
|
|
233
|
+
HQ_SYNC_ENGINE: "rust-test",
|
|
234
|
+
HQ_SYNC_MODE: full ? "full" : "readonly",
|
|
235
|
+
...(full ? { HQ_SYNC_FULL_VAULT: "1" } : {}),
|
|
236
|
+
HQ_SYNC_PROTOCOL: "1",
|
|
237
|
+
HQ_SYNC_CORE_RELEASE: vault.release,
|
|
238
|
+
},
|
|
239
|
+
heartbeatTimeoutMs: 180_000,
|
|
240
|
+
blocked: firstBlock([
|
|
241
|
+
[ctx.syncPaused, "sync is paused (run `hq daemon sync resume`)"],
|
|
242
|
+
[ctx.vaultSync?.allOff === true, "cloud sync is off for every vault on this computer (turn it back on in HQ Settings > Sync)"],
|
|
243
|
+
[!ctx.hqRoot, NO_HQ_ROOT],
|
|
244
|
+
[!ctx.signedIn, NOT_SIGNED_IN],
|
|
245
|
+
[!!vault.blockedReason, vault.blockedReason ?? ""],
|
|
246
|
+
[full && !legacyOff, `waiting for the current sync to be turned off for ${what}`],
|
|
247
|
+
[full && !legacyReady, ctx.legacySyncReadyForCore !== true
|
|
248
|
+
? "waiting for the current sync to run its plan without this vault"
|
|
249
|
+
: "waiting for the desktop app's sync to pick up the off-switch"],
|
|
250
|
+
]),
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
return [reconcile, ...engines];
|
|
208
254
|
}
|
|
209
255
|
case "mesh": {
|
|
210
256
|
const args = ["mesh", "daemon", "run", ...(ctx.hqRoot ? ["--hq-root", ctx.hqRoot] : [])];
|
|
@@ -18,19 +18,12 @@ export interface DaemonConfig {
|
|
|
18
18
|
instantSyncEnabled?: boolean;
|
|
19
19
|
/** Explicit desktop handoff; absent values preserve the legacy per-machine owner. */
|
|
20
20
|
syncOwner?: SyncOwner;
|
|
21
|
-
syncCoreCanary?: SyncCoreCanaryConfig;
|
|
22
|
-
syncCoreCanaryError?: string;
|
|
23
|
-
syncCoreCanaryInvalid?: unknown;
|
|
24
21
|
/** Services turned on here beyond the machine type's defaults. */
|
|
25
22
|
enable: string[];
|
|
26
23
|
/** Default services turned off here. */
|
|
27
24
|
disable: string[];
|
|
28
25
|
}
|
|
29
26
|
export type SyncOwner = "daemon" | "desktop-app";
|
|
30
|
-
export interface SyncCoreCanaryConfig {
|
|
31
|
-
binaryPath: string;
|
|
32
|
-
vaultUid: string;
|
|
33
|
-
}
|
|
34
27
|
export declare function effectiveSyncOwner(config: Pick<DaemonConfig, "syncOwner">, machineType: MachineType): SyncOwner;
|
|
35
28
|
export declare function readDaemonConfig(file: string): DaemonConfig;
|
|
36
29
|
export declare function writeDaemonConfig(file: string, config: DaemonConfig): void;
|
|
@@ -19,27 +19,6 @@ function emptyDaemonConfig() {
|
|
|
19
19
|
function stringList(value) {
|
|
20
20
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
21
21
|
}
|
|
22
|
-
function syncCoreCanary(value, file) {
|
|
23
|
-
if (value === undefined)
|
|
24
|
-
return {};
|
|
25
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26
|
-
return { error: `hq daemon config at ${file} has invalid syncCoreCanary`, invalid: value };
|
|
27
|
-
}
|
|
28
|
-
const candidate = value;
|
|
29
|
-
if (typeof candidate.binaryPath !== "string" || !path.isAbsolute(candidate.binaryPath)) {
|
|
30
|
-
return {
|
|
31
|
-
error: `hq daemon config at ${file} has invalid syncCoreCanary: binaryPath must be absolute`,
|
|
32
|
-
invalid: value,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (typeof candidate.vaultUid !== "string" || !/^prs_[A-Za-z0-9][A-Za-z0-9_-]*$/.test(candidate.vaultUid)) {
|
|
36
|
-
return {
|
|
37
|
-
error: `hq daemon config at ${file} has invalid syncCoreCanary: vaultUid must name one personal vault`,
|
|
38
|
-
invalid: value,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return { config: { binaryPath: candidate.binaryPath, vaultUid: candidate.vaultUid } };
|
|
42
|
-
}
|
|
43
22
|
export function readDaemonConfig(file) {
|
|
44
23
|
let text;
|
|
45
24
|
try {
|
|
@@ -72,13 +51,6 @@ export function readDaemonConfig(file) {
|
|
|
72
51
|
config.machineType = obj.machineType;
|
|
73
52
|
if (obj.syncOwner === "daemon" || obj.syncOwner === "desktop-app")
|
|
74
53
|
config.syncOwner = obj.syncOwner;
|
|
75
|
-
const parsedCanary = syncCoreCanary(obj.syncCoreCanary, file);
|
|
76
|
-
if (parsedCanary.config)
|
|
77
|
-
config.syncCoreCanary = parsedCanary.config;
|
|
78
|
-
if (parsedCanary.error)
|
|
79
|
-
config.syncCoreCanaryError = parsedCanary.error;
|
|
80
|
-
if (parsedCanary.error)
|
|
81
|
-
config.syncCoreCanaryInvalid = parsedCanary.invalid;
|
|
82
54
|
if (typeof obj.hqRoot === "string" && obj.hqRoot.trim())
|
|
83
55
|
config.hqRoot = obj.hqRoot;
|
|
84
56
|
if (typeof obj.hqCloudVersion === "string" && validSemver(obj.hqCloudVersion) === obj.hqCloudVersion) {
|
|
@@ -101,10 +73,6 @@ export function writeDaemonConfig(file, config) {
|
|
|
101
73
|
ordered.instantSyncEnabled = config.instantSyncEnabled;
|
|
102
74
|
if (config.syncOwner)
|
|
103
75
|
ordered.syncOwner = config.syncOwner;
|
|
104
|
-
if (config.syncCoreCanary)
|
|
105
|
-
ordered.syncCoreCanary = config.syncCoreCanary;
|
|
106
|
-
else if (config.syncCoreCanaryError)
|
|
107
|
-
ordered.syncCoreCanary = config.syncCoreCanaryInvalid;
|
|
108
76
|
ordered.enable = config.enable;
|
|
109
77
|
ordered.disable = config.disable;
|
|
110
78
|
fs.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}\n`, { mode: 0o600 });
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { type HostApp, type Supervisor } from "./supervisor.js";
|
|
|
14
14
|
export declare const LOG_ROTATE_BYTES: number;
|
|
15
15
|
/** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
|
|
16
16
|
export declare const RESTART_EXIT_CODE = 75;
|
|
17
|
-
export declare function planUnitsWithSyncCoreInterlock(services: readonly ServiceName[], context: PlanContext, supervisor: Pick<Supervisor, "runningPlanKey"> | null | undefined): UnitPlan[];
|
|
17
|
+
export declare function planUnitsWithSyncCoreInterlock(services: readonly ServiceName[], context: PlanContext, supervisor: Pick<Supervisor, "runningPlanKey" | "state"> | null | undefined): UnitPlan[];
|
|
18
18
|
/** A bounded shutdown deadline that can account for sync push persistence. */
|
|
19
19
|
export declare function createShutdownCap(): {
|
|
20
20
|
promise: Promise<"timeout">;
|
|
@@ -94,6 +94,10 @@ export declare function daemonSyncUnitIsPresent(units: readonly {
|
|
|
94
94
|
export declare function resolveSyncRunnerPath(): string | null;
|
|
95
95
|
/** The hosted desktop app uses this env value when planning daemon services. */
|
|
96
96
|
export declare function daemonInstantSyncEnabled(env: NodeJS.ProcessEnv, configured?: boolean): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The bots the daemon runs itself. A bot on the HQ Fleet Runtime has its own
|
|
99
|
+
* background service, so it is not one of them.
|
|
100
|
+
*/
|
|
97
101
|
export declare function listLocalBots(root?: string): string[];
|
|
98
102
|
export declare function windowsTaskExists(taskName: string): boolean;
|
|
99
103
|
export declare function probeLegacyUnits(platform: NodeJS.Platform, home: string): LegacyUnit[];
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -18,11 +18,12 @@ import { anywhereRuntimeAllowed, readHqAnywherePersonSetting, stickyPersonSettin
|
|
|
18
18
|
import { currentPackageUseLeaseInstall, currentPackageUseUpdateRequested } from "../../utils/package-use-lease.js";
|
|
19
19
|
import { isLocalDependencyInstall, resolveRunningInstall } from "../../utils/version-gate.js";
|
|
20
20
|
import { agentKitPaths } from "../agent-kit/paths.js";
|
|
21
|
-
import { readBotConfig } from "../bot/config.js";
|
|
21
|
+
import { HQ_FLEET_RUNTIME_ENGINE_ID, readBotConfig } from "../bot/config.js";
|
|
22
22
|
import { botsRoot } from "../bot/paths.js";
|
|
23
23
|
import { CLAUDE_RUNNER_SCRIPT, CODEX_RUNNER_SCRIPT, enabledServices, planUnits } from "./catalog.js";
|
|
24
24
|
import { effectiveSyncOwner, readDaemonConfig } from "./config.js";
|
|
25
|
-
import {
|
|
25
|
+
import { inspectEngineHome } from "../sync-core/engine-home.js";
|
|
26
|
+
import { readSyncCoreRolloutState } from "../sync-core/rollout-state.js";
|
|
26
27
|
import { assertPersonalServiceSessionCapability, readRemoteControlPoolsConfig, remoteControlPoolSecretsEnv, } from "./remote-control-pools.js";
|
|
27
28
|
import { loadLanesConfig } from "../lanes/config.js";
|
|
28
29
|
import { daemonLanesReconcileHandler } from "../lanes/daemon-reconcile.js";
|
|
@@ -64,17 +65,48 @@ export const LOG_ROTATE_BYTES = 5 * 1024 * 1024;
|
|
|
64
65
|
export const RESTART_EXIT_CODE = 75;
|
|
65
66
|
const NON_SYNC_SHUTDOWN_CAP_MS = STOP_ESCALATION_MS + 5_000;
|
|
66
67
|
const SYNC_RUNNER_SHUTDOWN_CAP_MS = STOP_ESCALATION_MS + SYNC_RUNNER_PERSIST_GRACE_MS + 5_000;
|
|
68
|
+
function syncCoreVaultPlans(home, hqRoot, platform) {
|
|
69
|
+
const state = readSyncCoreRolloutState(home);
|
|
70
|
+
if (!state)
|
|
71
|
+
return [];
|
|
72
|
+
return state.vaults.map((vault) => inspectEngineHome(home, hqRoot, {
|
|
73
|
+
uid: vault.uid,
|
|
74
|
+
kind: vault.kind,
|
|
75
|
+
...(vault.slug ? { slug: vault.slug } : {}),
|
|
76
|
+
phase: vault.phase,
|
|
77
|
+
engineHome: vault.engineHome,
|
|
78
|
+
binaryPath: state.binaryPath,
|
|
79
|
+
release: state.release,
|
|
80
|
+
...(vault.legacyOffSince ? { legacyOffSince: vault.legacyOffSince } : {}),
|
|
81
|
+
}, platform));
|
|
82
|
+
}
|
|
83
|
+
const rememberedSyncCoreVaults = new Map();
|
|
67
84
|
export function planUnitsWithSyncCoreInterlock(services, context, supervisor) {
|
|
68
|
-
const
|
|
85
|
+
const companies = new Set();
|
|
86
|
+
let personal = false;
|
|
87
|
+
for (const vault of context.syncCoreVaults ?? []) {
|
|
88
|
+
rememberedSyncCoreVaults.set(vault.uid, { kind: vault.kind, slug: vault.slug, full: vault.phase === "full" });
|
|
89
|
+
}
|
|
90
|
+
for (const unit of supervisor?.state().units ?? []) {
|
|
91
|
+
if (!unit.id.startsWith("sync-core:") || supervisor?.runningPlanKey(unit.id) === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
const vault = rememberedSyncCoreVaults.get(unit.id.slice("sync-core:".length));
|
|
94
|
+
if (!vault?.full)
|
|
95
|
+
continue;
|
|
96
|
+
if (vault.kind === "personal")
|
|
97
|
+
personal = true;
|
|
98
|
+
else if (vault.slug)
|
|
99
|
+
companies.add(vault.slug);
|
|
100
|
+
}
|
|
69
101
|
const transitionContext = {
|
|
70
102
|
...context,
|
|
71
|
-
|
|
103
|
+
syncCoreRunning: { personal, companies },
|
|
72
104
|
legacySyncReadyForCore: false,
|
|
73
105
|
};
|
|
74
106
|
const waiting = planUnits(services, transitionContext);
|
|
75
107
|
const legacy = waiting.find((unit) => unit.id === "sync");
|
|
76
108
|
const legacySyncReadyForCore = legacy !== undefined
|
|
77
|
-
&& supervisor?.runningPlanKey("sync") === planKey(legacy, CLI_VERSION);
|
|
109
|
+
&& (legacy.blocked !== undefined || supervisor?.runningPlanKey("sync") === planKey(legacy, CLI_VERSION));
|
|
78
110
|
return planUnits(services, { ...transitionContext, legacySyncReadyForCore });
|
|
79
111
|
}
|
|
80
112
|
/** A bounded shutdown deadline that can account for sync push persistence. */
|
|
@@ -260,12 +292,21 @@ export function resolveSyncRunnerPath() {
|
|
|
260
292
|
export function daemonInstantSyncEnabled(env, configured) {
|
|
261
293
|
return configured ?? env.HQ_DAEMON_INSTANT_SYNC !== "0";
|
|
262
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* The bots the daemon runs itself. A bot on the HQ Fleet Runtime has its own
|
|
297
|
+
* background service, so it is not one of them.
|
|
298
|
+
*/
|
|
263
299
|
export function listLocalBots(root = botsRoot()) {
|
|
264
300
|
if (!fs.existsSync(root))
|
|
265
301
|
return [];
|
|
266
302
|
return fs
|
|
267
303
|
.readdirSync(root, { withFileTypes: true })
|
|
268
|
-
.filter((d) =>
|
|
304
|
+
.filter((d) => {
|
|
305
|
+
if (!d.isDirectory())
|
|
306
|
+
return false;
|
|
307
|
+
const config = readBotConfig(path.join(root, d.name));
|
|
308
|
+
return config !== null && config.engine?.id !== HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
309
|
+
})
|
|
269
310
|
.map((d) => d.name)
|
|
270
311
|
.sort();
|
|
271
312
|
}
|
|
@@ -978,11 +1019,7 @@ export async function runDaemon(opts) {
|
|
|
978
1019
|
return switches;
|
|
979
1020
|
})(),
|
|
980
1021
|
syncOwner,
|
|
981
|
-
|
|
982
|
-
? { blockedReason: config.syncCoreCanaryError }
|
|
983
|
-
: config.syncCoreCanary
|
|
984
|
-
? inspectSyncCoreCanary(home, hqRoot, config.syncCoreCanary, platform)
|
|
985
|
-
: undefined,
|
|
1022
|
+
syncCoreVaults: syncCoreVaultPlans(home, hqRoot, platform),
|
|
986
1023
|
desktopAppManagesSync: appManagesSync,
|
|
987
1024
|
syncRunnerProcesses,
|
|
988
1025
|
syncRunnerOwnerPid,
|
|
@@ -87,7 +87,7 @@ function keyHash(key) {
|
|
|
87
87
|
return createHash("sha256").update(key).digest("hex");
|
|
88
88
|
}
|
|
89
89
|
function recoverableHold(plan, hold) {
|
|
90
|
-
return plan.kind === "long-running" && (plan.service === "sync" || plan.service === "mesh")
|
|
90
|
+
return plan.kind === "long-running" && (plan.service === "sync" || plan.service === "sync-core" || plan.service === "mesh")
|
|
91
91
|
&& hold?.kind === "exhausted" && hold.cause === "transient";
|
|
92
92
|
}
|
|
93
93
|
function heldReason(unitId, hold) {
|
|
@@ -388,6 +388,8 @@ export function createSupervisor(deps) {
|
|
|
388
388
|
const cause = classifyExit(evidence);
|
|
389
389
|
if (cause.kind !== "permanent")
|
|
390
390
|
return false;
|
|
391
|
+
if (slot.plan.service === "sync-core" && cause.cause === "bad-config")
|
|
392
|
+
return false;
|
|
391
393
|
slot.failures += 1;
|
|
392
394
|
holdUnit(slot, "permanent", cause.cause, cause.detail);
|
|
393
395
|
return true;
|
|
@@ -9,7 +9,9 @@ import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
|
9
9
|
export declare const RELEASE_LINKS_FAMILY_ID = "release-links";
|
|
10
10
|
export declare const RELEASE_LINKS_FAMILY_TITLE = "Release links";
|
|
11
11
|
export declare const RELEASE_LINKS_CHECK_ID = "release-links.resolve";
|
|
12
|
-
export declare const RELEASE_LINK_REMEDIATION = "Run `hq rescue -y`
|
|
12
|
+
export declare const RELEASE_LINK_REMEDIATION = "Run `hq doctor --fix` (or `hq rescue -y`) to restore the shipped link. A dangling link that points outside the HQ root and the temp folder is treated as your own and left alone. On Windows, a directory link is written as a junction and a file link as a copy when symlinks are not allowed.";
|
|
13
|
+
/** Message prefix for a link whose target is missing; `--fix` keys off it. */
|
|
14
|
+
export declare const DANGLING_RELEASE_LINK_PROBLEM = "does not resolve.";
|
|
13
15
|
interface ReleaseLink {
|
|
14
16
|
rel: string;
|
|
15
17
|
linkText: string;
|
|
@@ -19,5 +21,64 @@ interface ReleaseLink {
|
|
|
19
21
|
export declare const RELEASE_LINKS: readonly ReleaseLink[];
|
|
20
22
|
export declare const releaseLinksFamily: CheckFamily;
|
|
21
23
|
export declare function checkReleaseLinks(context: CheckContext): CheckResult[];
|
|
24
|
+
/** Where a rescue may stage its extraction folder. */
|
|
25
|
+
export interface RescueStagingDeps {
|
|
26
|
+
/** The system temp folder. Default `os.tmpdir()`. */
|
|
27
|
+
tmpDir?: string;
|
|
28
|
+
/** The rescue backup root. Default `$HQ_BACKUP_DIR` or `~/.hq/backups`. */
|
|
29
|
+
backupRoot?: string;
|
|
30
|
+
}
|
|
31
|
+
/** What {@link repairDanglingReleaseLinks} did to one release link. */
|
|
32
|
+
export interface ReleaseLinkRepair {
|
|
33
|
+
rel: string;
|
|
34
|
+
/**
|
|
35
|
+
* `repointed`: the link was rewritten (see `linkText`). `removed`: the
|
|
36
|
+
* shipped target is missing too, so the dangling link was deleted.
|
|
37
|
+
* `skipped`: the link points outside the HQ root and the temp folder, so it
|
|
38
|
+
* is treated as the user's own link and left alone (see `reason`).
|
|
39
|
+
* `failed`: the repair threw for this link only (see `reason`).
|
|
40
|
+
*/
|
|
41
|
+
action: "repointed" | "removed" | "skipped" | "failed";
|
|
42
|
+
/** The absolute target the link now resolves to (repointed only). */
|
|
43
|
+
target?: string;
|
|
44
|
+
/** The text written into the link (repointed only). */
|
|
45
|
+
linkText?: string;
|
|
46
|
+
reason?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface RepairReleaseLinksDeps {
|
|
49
|
+
platform?: NodeJS.Platform;
|
|
50
|
+
/** Repair only this release link (a `RELEASE_LINKS` rel). */
|
|
51
|
+
only?: string;
|
|
52
|
+
/** The temp folder a rescue extracts into. Default `os.tmpdir()`. */
|
|
53
|
+
tmpDir?: string;
|
|
54
|
+
/** The rescue backup root. Default `$HQ_BACKUP_DIR` or `~/.hq/backups`. */
|
|
55
|
+
backupRoot?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Repair HQ-managed release links whose target no longer exists, such as a
|
|
59
|
+
* `.agents/skills` or `.codex/claude` link an earlier rescue pointed at its
|
|
60
|
+
* deleted temp extraction folder (feedback_2ab63e50).
|
|
61
|
+
*
|
|
62
|
+
* Only the fixed {@link RELEASE_LINKS} paths are considered, and only when the
|
|
63
|
+
* entry itself is a symlink or junction that does not resolve. A real file or
|
|
64
|
+
* directory is never touched, a link that resolves anywhere is left alone, and
|
|
65
|
+
* the link's parent folder must sit inside the HQ root, so a `.agents` folder
|
|
66
|
+
* that is itself a link out of the tree is skipped.
|
|
67
|
+
*
|
|
68
|
+
* Ownership rule: a dangling link is repaired only when its current target is
|
|
69
|
+
* under the HQ root or under the system temp folder (where a rescue extracts
|
|
70
|
+
* its staging copy). A dangling link anywhere else, such as `/Volumes/X`, is
|
|
71
|
+
* the user's own link to a drive that is not mounted; it is left alone and
|
|
72
|
+
* reported as `skipped`.
|
|
73
|
+
*
|
|
74
|
+
* The repaired link carries the text HQ ships (`../.claude/skills`), so the
|
|
75
|
+
* git-tracked link is unchanged. On Windows a directory link is an absolute
|
|
76
|
+
* junction and a file link an absolute symlink, falling back to a copy. When
|
|
77
|
+
* the shipped target is missing too, the dangling link is removed and nothing
|
|
78
|
+
* is created. The old entry is removed with {@link removeLinkOnly}, which never
|
|
79
|
+
* follows it. A failure on one link is reported as `failed` and the remaining
|
|
80
|
+
* links are still repaired.
|
|
81
|
+
*/
|
|
82
|
+
export declare function repairDanglingReleaseLinks(hqRoot: string, deps?: RepairReleaseLinksDeps): ReleaseLinkRepair[];
|
|
22
83
|
export {};
|
|
23
84
|
//# sourceMappingURL=release-links.d.ts.map
|