@indigoai-us/hq-cli 5.345.61 → 5.345.62
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 +5 -0
- package/dist/commands/core-hq-session-native.js +5 -1
- package/dist/commands/daemon.js +6 -1
- package/dist/lib/daemon/hq-cloud.d.ts +13 -1
- package/dist/lib/daemon/hq-cloud.js +79 -16
- package/dist/utils/client-health-contract.d.ts +12 -0
- package/dist/utils/client-health-contract.js +51 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.62] — 2026-10-09
|
|
6
|
+
- Repair the scope capability from the validated metadata set when `hq core hq-session add company` is repeated for a company already listed in the session.
|
|
7
|
+
|
|
8
|
+
- `hq daemon sync status --json` reports daemon-owned hq-cloud updater checks, target, outcome, closed failure class, and pin status. Registry lookups are bounded to 15 seconds and installs to 2 minutes; success is reported only after the installed package converges to the selected version.
|
|
9
|
+
|
|
5
10
|
## [5.345.61] — 2026-10-09
|
|
6
11
|
|
|
7
12
|
## [5.345.60] — 2026-10-09
|
|
@@ -657,8 +657,12 @@ export function executeHqSession(argv, context, overrides = {}) {
|
|
|
657
657
|
const oldText = existed ? deps.readFile(target, "utf8") : `session_id: ${sessionId}\nstarted_at: "${timestamp(deps.now())}"\nsenior: user\n`;
|
|
658
658
|
const rawCompanies = metaCompanySlugs(oldText);
|
|
659
659
|
const companies = rawCompanies.filter((company) => company === "personal" || exactCompanyDir(root, company, deps));
|
|
660
|
-
if (subcommand === "add" && companies.includes(slug))
|
|
660
|
+
if (subcommand === "add" && companies.includes(slug)) {
|
|
661
|
+
if (!mintScopeCapability(root, sessionId, companies, deps, true)) {
|
|
662
|
+
return { code: 1, stdout: "", stderr: `hq-session: failed to repair scope-capability for session ${sessionId}; metadata was unchanged\n` };
|
|
663
|
+
}
|
|
661
664
|
return { code: 0, stdout: "", stderr: "" };
|
|
665
|
+
}
|
|
662
666
|
const next = subcommand === "add"
|
|
663
667
|
? [...(companies[0] === "personal" ? [] : companies), slug]
|
|
664
668
|
: companies.filter((value) => value !== slug);
|
package/dist/commands/daemon.js
CHANGED
|
@@ -24,7 +24,7 @@ import { adoptLegacyUnits, readAdoptedUnits, restoreLegacyUnits } from "../lib/d
|
|
|
24
24
|
import { ROOT_REFUSAL, SYSTEM_UNIT_DIR, daemonRootRefusal, installDaemonUnit, installScopeFor, uninstallDaemonUnit, } from "../lib/daemon/install.js";
|
|
25
25
|
import { describeLegacyUnit, findLegacyUnits } from "../lib/daemon/legacy.js";
|
|
26
26
|
import { isDaemonProcess, runningDaemonPid } from "../lib/daemon/lock.js";
|
|
27
|
-
import { bundledHqCloud, managedHqCloud, managedHqCloudRoot, readHqCloudCopy } from "../lib/daemon/hq-cloud.js";
|
|
27
|
+
import { bundledHqCloud, managedHqCloud, managedHqCloudRoot, readHqCloudCopy, readHqCloudUpdateState } from "../lib/daemon/hq-cloud.js";
|
|
28
28
|
import { MACHINE_TYPE_MARKER_PATH, detectMachineType, homeFromPasswd, } from "../lib/daemon/machine-type.js";
|
|
29
29
|
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
30
30
|
import { processCommandLine } from "../lib/daemon/lock.js";
|
|
@@ -510,6 +510,7 @@ function daemonSyncStatus(d) {
|
|
|
510
510
|
const runnerSource = syncRunnerSource(runnerPath, paths.root, d.platform);
|
|
511
511
|
const managedCopy = managedHqCloud(paths.root);
|
|
512
512
|
const managedVersion = managedCopy?.version ?? null;
|
|
513
|
+
const hqCloudUpdate = readHqCloudUpdateState(paths.root);
|
|
513
514
|
let hqCloudVersion = ownerStatus?.version ?? null;
|
|
514
515
|
let hqCloudVersionSource = hqCloudVersion ? "owner-status" : "unknown";
|
|
515
516
|
if (!hqCloudVersion && runnerPath) {
|
|
@@ -547,6 +548,10 @@ function daemonSyncStatus(d) {
|
|
|
547
548
|
runnerSource,
|
|
548
549
|
managedHqCloudVersion: managedVersion,
|
|
549
550
|
pinnedHqCloudVersion: config.hqCloudVersion ?? null,
|
|
551
|
+
hqCloudUpdateLastCheckAt: hqCloudUpdate?.lastCheckAt ?? null,
|
|
552
|
+
hqCloudUpdateOutcome: hqCloudUpdate?.outcome ?? null,
|
|
553
|
+
hqCloudUpdateTarget: hqCloudUpdate?.target ?? null,
|
|
554
|
+
hqCloudUpdateErrorClass: hqCloudUpdate?.errorClass ?? null,
|
|
550
555
|
lastHeartbeat: syncUnit?.lastHeartbeatAt ?? ownerStatus?.heartbeatAt ?? null,
|
|
551
556
|
lastPassResult: syncUnit?.lastPassResult ?? null,
|
|
552
557
|
unitStatus: syncUnit?.status ?? "not-running",
|
|
@@ -71,8 +71,19 @@ export interface HqCloudUpdateOutcome {
|
|
|
71
71
|
ok: boolean;
|
|
72
72
|
from: string | null;
|
|
73
73
|
to: string | null;
|
|
74
|
+
outcome?: HqCloudUpdateState["outcome"];
|
|
75
|
+
target?: string | null;
|
|
76
|
+
errorClass?: HqCloudUpdateState["errorClass"];
|
|
77
|
+
lastCheckAt?: string;
|
|
74
78
|
detail?: string;
|
|
75
79
|
}
|
|
80
|
+
export interface HqCloudUpdateState {
|
|
81
|
+
lastCheckAt: string;
|
|
82
|
+
outcome: "updated" | "up_to_date" | "pinned" | "failed";
|
|
83
|
+
target: string | null;
|
|
84
|
+
errorClass: "registry" | "install" | "convergence" | "filesystem" | "unknown" | null;
|
|
85
|
+
}
|
|
86
|
+
export declare function readHqCloudUpdateState(daemonRoot: string): HqCloudUpdateState | null;
|
|
76
87
|
interface NpmRunResult {
|
|
77
88
|
status: number | null;
|
|
78
89
|
stdout?: string | null;
|
|
@@ -80,11 +91,12 @@ interface NpmRunResult {
|
|
|
80
91
|
error?: Error;
|
|
81
92
|
}
|
|
82
93
|
export interface UpdateHqCloudDeps {
|
|
83
|
-
run?: (file: string, args: string[], shell: boolean, env: NodeJS.ProcessEnv) => NpmRunResult;
|
|
94
|
+
run?: (file: string, args: string[], shell: boolean, env: NodeJS.ProcessEnv, timeoutMs: number) => NpmRunResult;
|
|
84
95
|
bundled?: HqCloudCopy | null;
|
|
85
96
|
platform?: NodeJS.Platform;
|
|
86
97
|
/** Test seam for bounded install retry delays. */
|
|
87
98
|
sleep?: (ms: number) => void;
|
|
99
|
+
now?: () => Date;
|
|
88
100
|
}
|
|
89
101
|
/** Install or update the updater's copy, as this (the login) user. */
|
|
90
102
|
export declare function updateManagedHqCloud(daemonRoot: string, env: NodeJS.ProcessEnv, deps?: UpdateHqCloudDeps): HqCloudUpdateOutcome;
|
|
@@ -161,6 +161,47 @@ export function currentHqCloudAfterLeaseRelease(daemonRoot, expectedVersion) {
|
|
|
161
161
|
return null;
|
|
162
162
|
return { ...managed, source: "managed" };
|
|
163
163
|
}
|
|
164
|
+
const HQ_CLOUD_UPDATE_STATE_FILE = "hq-cloud-update-state.json";
|
|
165
|
+
const HQ_CLOUD_UPDATE_STATE_MAX_BYTES = 4_096;
|
|
166
|
+
const HQ_CLOUD_UPDATE_OUTCOMES = new Set(["updated", "up_to_date", "pinned", "failed"]);
|
|
167
|
+
const HQ_CLOUD_UPDATE_ERROR_CLASSES = new Set([
|
|
168
|
+
"registry", "install", "convergence", "filesystem", "unknown",
|
|
169
|
+
]);
|
|
170
|
+
export function readHqCloudUpdateState(daemonRoot) {
|
|
171
|
+
try {
|
|
172
|
+
const file = path.join(daemonRoot, HQ_CLOUD_UPDATE_STATE_FILE);
|
|
173
|
+
const stat = fs.lstatSync(file);
|
|
174
|
+
if (!stat.isFile() || stat.size > HQ_CLOUD_UPDATE_STATE_MAX_BYTES) {
|
|
175
|
+
throw new Error("updater state is not a small regular file");
|
|
176
|
+
}
|
|
177
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
178
|
+
if (typeof raw.lastCheckAt !== "string"
|
|
179
|
+
|| !HQ_CLOUD_UPDATE_OUTCOMES.has(raw.outcome)
|
|
180
|
+
|| !(raw.target === null || typeof raw.target === "string")
|
|
181
|
+
|| !(raw.errorClass === null || HQ_CLOUD_UPDATE_ERROR_CLASSES.has(raw.errorClass)))
|
|
182
|
+
return null;
|
|
183
|
+
return raw;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error.code !== "ENOENT") {
|
|
187
|
+
console.warn("Could not read daemon hq-cloud updater state; status will omit it:", error instanceof Error ? error.message : String(error));
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function writeHqCloudUpdateState(daemonRoot, state) {
|
|
193
|
+
const file = path.join(daemonRoot, HQ_CLOUD_UPDATE_STATE_FILE);
|
|
194
|
+
const temporary = `${file}.tmp-${process.pid}`;
|
|
195
|
+
fs.writeFileSync(temporary, JSON.stringify(state) + "\n", { mode: 0o600 });
|
|
196
|
+
fs.renameSync(temporary, file);
|
|
197
|
+
}
|
|
198
|
+
function updateErrorClass(detail, fallback) {
|
|
199
|
+
if (/could not look up|npm listed no/i.test(detail))
|
|
200
|
+
return "registry";
|
|
201
|
+
return fallback;
|
|
202
|
+
}
|
|
203
|
+
const HQ_CLOUD_REGISTRY_TIMEOUT_MS = 15_000;
|
|
204
|
+
const HQ_CLOUD_INSTALL_TIMEOUT_MS = 120_000;
|
|
164
205
|
function isTransientNpmFailure(result) {
|
|
165
206
|
// spawnSync's ETIMEDOUT means this attempt hit our full 10-minute process
|
|
166
207
|
// timeout. Retrying that result would multiply one stall across every attempt.
|
|
@@ -172,11 +213,11 @@ function isTransientNpmFailure(result) {
|
|
|
172
213
|
function sleepSync(ms) {
|
|
173
214
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
174
215
|
}
|
|
175
|
-
function runNpm(file, args, shell, env) {
|
|
216
|
+
function runNpm(file, args, shell, env, timeoutMs) {
|
|
176
217
|
return spawnSync(file, args, {
|
|
177
218
|
encoding: "utf8",
|
|
178
219
|
env,
|
|
179
|
-
timeout:
|
|
220
|
+
timeout: timeoutMs,
|
|
180
221
|
stdio: ["ignore", "pipe", "pipe"],
|
|
181
222
|
windowsHide: true,
|
|
182
223
|
shell,
|
|
@@ -193,24 +234,40 @@ export function updateManagedHqCloud(daemonRoot, env, deps = {}) {
|
|
|
193
234
|
const pinnedVersion = readDaemonConfig(path.join(daemonRoot, "config.json")).hqCloudVersion;
|
|
194
235
|
const bundled = deps.bundled !== undefined ? deps.bundled : bundledHqCloud();
|
|
195
236
|
const from = managedHqCloud(daemonRoot)?.version ?? bundled?.version ?? null;
|
|
237
|
+
const checkedAt = (deps.now ?? (() => new Date()))().toISOString();
|
|
238
|
+
const finish = (result, outcome, target, errorClass) => {
|
|
239
|
+
const state = { lastCheckAt: checkedAt, outcome, target, errorClass };
|
|
240
|
+
try {
|
|
241
|
+
fs.mkdirSync(daemonRoot, { recursive: true, mode: 0o700 });
|
|
242
|
+
writeHqCloudUpdateState(daemonRoot, state);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
246
|
+
console.warn("Could not persist daemon hq-cloud updater state; preserving update result:", detail);
|
|
247
|
+
}
|
|
248
|
+
return { ...result, outcome, target, errorClass, lastCheckAt: checkedAt };
|
|
249
|
+
};
|
|
196
250
|
if (!bundled)
|
|
197
|
-
return { ok: false, from, to: null, detail: "this CLI install has no bundled hq-cloud to match" };
|
|
251
|
+
return finish({ ok: false, from, to: null, detail: "this CLI install has no bundled hq-cloud to match" }, "failed", pinnedVersion ?? null, "unknown");
|
|
198
252
|
const prefix = managedHqCloudDir(daemonRoot);
|
|
199
253
|
try {
|
|
200
254
|
fs.mkdirSync(prefix, { recursive: true, mode: 0o700 });
|
|
201
255
|
}
|
|
202
256
|
catch (err) {
|
|
203
|
-
return { ok: false, from, to: null, detail: `could not create ${prefix}: ${err instanceof Error ? err.message : String(err)}` };
|
|
257
|
+
return finish({ ok: false, from, to: null, detail: `could not create ${prefix}: ${err instanceof Error ? err.message : String(err)}` }, "failed", pinnedVersion ?? null, "filesystem");
|
|
204
258
|
}
|
|
205
259
|
let version = pinnedVersion;
|
|
206
260
|
if (!version) {
|
|
207
261
|
const view = hqCloudLatestCommand(bundled.version, platform);
|
|
208
|
-
const r = run(view.file, view.args, view.shell, env);
|
|
209
|
-
if (r.status !== 0)
|
|
210
|
-
|
|
262
|
+
const r = run(view.file, view.args, view.shell, env, HQ_CLOUD_REGISTRY_TIMEOUT_MS);
|
|
263
|
+
if (r.status !== 0) {
|
|
264
|
+
const detail = `could not look up the newest hq-cloud: ${npmFailure(r)}`;
|
|
265
|
+
return finish({ ok: false, from, to: null, detail }, "failed", null, updateErrorClass(detail, "registry"));
|
|
266
|
+
}
|
|
211
267
|
const latest = latestSameMajor(r.stdout ?? "", bundled.version);
|
|
212
268
|
if (!latest) {
|
|
213
|
-
|
|
269
|
+
const detail = `npm listed no hq-cloud release at or above ${bundled.version} in the same major version`;
|
|
270
|
+
return finish({ ok: false, from, to: null, detail }, "failed", null, "registry");
|
|
214
271
|
}
|
|
215
272
|
version = latest;
|
|
216
273
|
}
|
|
@@ -219,22 +276,28 @@ export function updateManagedHqCloud(daemonRoot, env, deps = {}) {
|
|
|
219
276
|
const sleep = deps.sleep ?? sleepSync;
|
|
220
277
|
let r;
|
|
221
278
|
for (let attempt = 0;; attempt++) {
|
|
222
|
-
r = run(cmd.file, cmd.args, cmd.shell, env);
|
|
279
|
+
r = run(cmd.file, cmd.args, cmd.shell, env, HQ_CLOUD_INSTALL_TIMEOUT_MS);
|
|
223
280
|
if (r.status === 0)
|
|
224
281
|
break;
|
|
225
282
|
const retryable = pinnedVersion !== undefined && pinnedVersion !== null && isTransientNpmFailure(r);
|
|
226
283
|
const delay = retryDelays[attempt];
|
|
227
284
|
if (!retryable || delay === undefined) {
|
|
228
285
|
const retries = retryable ? attempt : 0;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
from,
|
|
232
|
-
to: null,
|
|
233
|
-
detail: retries > 0 ? `pinned hq-cloud install failed after ${retries + 1} attempts: ${npmFailure(r)}` : npmFailure(r),
|
|
234
|
-
};
|
|
286
|
+
const detail = retries > 0 ? `pinned hq-cloud install failed after ${retries + 1} attempts: ${npmFailure(r)}` : npmFailure(r);
|
|
287
|
+
return finish({ ok: false, from, to: null, detail }, "failed", version, retryable ? "registry" : "install");
|
|
235
288
|
}
|
|
236
289
|
sleep(delay);
|
|
237
290
|
}
|
|
238
|
-
|
|
291
|
+
const installedPackage = managedHqCloud(daemonRoot);
|
|
292
|
+
const installedVersion = installedPackage?.version ?? null;
|
|
293
|
+
const syncRunner = installedPackage ? hqCloudRunners(installedPackage.root).syncRunner : null;
|
|
294
|
+
if (installedVersion !== version || !syncRunner) {
|
|
295
|
+
const detail = installedVersion !== version
|
|
296
|
+
? `hq-cloud install exited successfully but installed ${installedVersion ?? "no readable package"}, expected ${version}`
|
|
297
|
+
: `hq-cloud install exited successfully at ${version} but has no dist/bin/sync-runner.js`;
|
|
298
|
+
return finish({ ok: false, from, to: installedVersion, detail }, "failed", version, "convergence");
|
|
299
|
+
}
|
|
300
|
+
const outcome = pinnedVersion ? "pinned" : installedVersion === from ? "up_to_date" : "updated";
|
|
301
|
+
return finish({ ok: true, from, to: installedVersion }, outcome, version, null);
|
|
239
302
|
}
|
|
240
303
|
//# sourceMappingURL=hq-cloud.js.map
|
|
@@ -53,6 +53,13 @@ export type ClientHealthSyncState = (typeof CLIENT_HEALTH_SYNC_STATES)[number];
|
|
|
53
53
|
*/
|
|
54
54
|
export declare const CLIENT_HEALTH_UPDATER_STATES: readonly ["unchecked", "up_to_date", "update_available", "update_downloading", "update_ready", "update_failed", "unsupported"];
|
|
55
55
|
export type ClientHealthUpdaterState = (typeof CLIENT_HEALTH_UPDATER_STATES)[number];
|
|
56
|
+
/** Origin of the runner currently used by desktop sync. */
|
|
57
|
+
export declare const CLIENT_HEALTH_SYNC_RUNNER_SOURCES: readonly ["npx", "daemon_managed", "daemon_staged", "daemon_bundled", "unknown"];
|
|
58
|
+
export type ClientHealthSyncRunnerSource = (typeof CLIENT_HEALTH_SYNC_RUNNER_SOURCES)[number];
|
|
59
|
+
export declare const CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_OUTCOMES: readonly ["updated", "up_to_date", "pinned", "failed"];
|
|
60
|
+
export type ClientHealthDaemonHqCloudUpdateOutcome = (typeof CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_OUTCOMES)[number];
|
|
61
|
+
export declare const CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_ERROR_CLASSES: readonly ["registry", "install", "convergence", "filesystem", "unknown"];
|
|
62
|
+
export type ClientHealthDaemonHqCloudUpdateErrorClass = (typeof CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_ERROR_CLASSES)[number];
|
|
56
63
|
/** Closed failure/blocker reason codes — the ONLY reasons that cross the wire. */
|
|
57
64
|
export declare const CLIENT_HEALTH_FAILURE_REASONS: readonly ["SYNC_PAUSED", "CONFLICT_BLOCKED", "DESKTOP_OUTDATED", "CLI_OUTDATED", "CORE_OUTDATED", "AUTH_EXPIRED", "UPDATE_FAILED", "RUNNER_FAILED", "PERMISSION_DENIED", "DISK_FULL", "HEARTBEAT_STALE"];
|
|
58
65
|
export type ClientHealthFailureReason = (typeof CLIENT_HEALTH_FAILURE_REASONS)[number];
|
|
@@ -139,6 +146,11 @@ export interface ClientHealthHeartbeat {
|
|
|
139
146
|
conflictCount?: number;
|
|
140
147
|
updaterState?: ClientHealthUpdaterState;
|
|
141
148
|
failureReason?: ClientHealthFailureReason;
|
|
149
|
+
syncRunnerSource?: ClientHealthSyncRunnerSource;
|
|
150
|
+
daemonHqCloudUpdateLastCheckAt?: string;
|
|
151
|
+
daemonHqCloudUpdateOutcome?: ClientHealthDaemonHqCloudUpdateOutcome;
|
|
152
|
+
daemonHqCloudUpdateTarget?: string;
|
|
153
|
+
daemonHqCloudUpdateErrorClass?: ClientHealthDaemonHqCloudUpdateErrorClass;
|
|
142
154
|
/** Absent on older clients / non-sync-outcome events. See {@link ClientHealthLocalFilesOverview}. */
|
|
143
155
|
localFilesOverview?: ClientHealthLocalFilesOverview;
|
|
144
156
|
}
|
|
@@ -65,6 +65,27 @@ export const CLIENT_HEALTH_UPDATER_STATES = [
|
|
|
65
65
|
"update_failed",
|
|
66
66
|
"unsupported",
|
|
67
67
|
];
|
|
68
|
+
/** Origin of the runner currently used by desktop sync. */
|
|
69
|
+
export const CLIENT_HEALTH_SYNC_RUNNER_SOURCES = [
|
|
70
|
+
"npx",
|
|
71
|
+
"daemon_managed",
|
|
72
|
+
"daemon_staged",
|
|
73
|
+
"daemon_bundled",
|
|
74
|
+
"unknown",
|
|
75
|
+
];
|
|
76
|
+
export const CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_OUTCOMES = [
|
|
77
|
+
"updated",
|
|
78
|
+
"up_to_date",
|
|
79
|
+
"pinned",
|
|
80
|
+
"failed",
|
|
81
|
+
];
|
|
82
|
+
export const CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_ERROR_CLASSES = [
|
|
83
|
+
"registry",
|
|
84
|
+
"install",
|
|
85
|
+
"convergence",
|
|
86
|
+
"filesystem",
|
|
87
|
+
"unknown",
|
|
88
|
+
];
|
|
68
89
|
/** Closed failure/blocker reason codes — the ONLY reasons that cross the wire. */
|
|
69
90
|
export const CLIENT_HEALTH_FAILURE_REASONS = [
|
|
70
91
|
"SYNC_PAUSED",
|
|
@@ -193,6 +214,23 @@ function assertEnum(field, value, allowed) {
|
|
|
193
214
|
}
|
|
194
215
|
return safe;
|
|
195
216
|
}
|
|
217
|
+
function copyValidOptionalDiagnostic(field, value, validate) {
|
|
218
|
+
try {
|
|
219
|
+
return validate(field, value);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (error instanceof ClientHealthContractError)
|
|
223
|
+
return undefined;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function copyKnownOptionalEnum(target, field, value, allowed) {
|
|
228
|
+
if (value === undefined)
|
|
229
|
+
return;
|
|
230
|
+
const parsed = copyValidOptionalDiagnostic(field, value, (name, input) => assertEnum(name, input, allowed));
|
|
231
|
+
if (parsed !== undefined)
|
|
232
|
+
Object.assign(target, { [field]: parsed });
|
|
233
|
+
}
|
|
196
234
|
function assertBoundedInt(field, value, max) {
|
|
197
235
|
if (value === undefined || value === null)
|
|
198
236
|
throw new ClientHealthContractError("MISSING_FIELD", field);
|
|
@@ -264,6 +302,19 @@ export function parseClientHealthHeartbeat(input) {
|
|
|
264
302
|
if (raw.failureReason !== undefined) {
|
|
265
303
|
heartbeat.failureReason = assertEnum("failureReason", raw.failureReason, CLIENT_HEALTH_FAILURE_REASONS);
|
|
266
304
|
}
|
|
305
|
+
copyKnownOptionalEnum(heartbeat, "syncRunnerSource", raw.syncRunnerSource, CLIENT_HEALTH_SYNC_RUNNER_SOURCES);
|
|
306
|
+
if (raw.daemonHqCloudUpdateLastCheckAt !== undefined) {
|
|
307
|
+
const lastCheckAt = copyValidOptionalDiagnostic("daemonHqCloudUpdateLastCheckAt", raw.daemonHqCloudUpdateLastCheckAt, assertIsoUtc);
|
|
308
|
+
if (lastCheckAt !== undefined)
|
|
309
|
+
heartbeat.daemonHqCloudUpdateLastCheckAt = lastCheckAt;
|
|
310
|
+
}
|
|
311
|
+
copyKnownOptionalEnum(heartbeat, "daemonHqCloudUpdateOutcome", raw.daemonHqCloudUpdateOutcome, CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_OUTCOMES);
|
|
312
|
+
if (raw.daemonHqCloudUpdateTarget !== undefined) {
|
|
313
|
+
const target = copyValidOptionalDiagnostic("daemonHqCloudUpdateTarget", raw.daemonHqCloudUpdateTarget, assertVersion);
|
|
314
|
+
if (target !== undefined)
|
|
315
|
+
heartbeat.daemonHqCloudUpdateTarget = target;
|
|
316
|
+
}
|
|
317
|
+
copyKnownOptionalEnum(heartbeat, "daemonHqCloudUpdateErrorClass", raw.daemonHqCloudUpdateErrorClass, CLIENT_HEALTH_DAEMON_HQ_CLOUD_UPDATE_ERROR_CLASSES);
|
|
267
318
|
if (raw.localFilesOverview !== undefined) {
|
|
268
319
|
heartbeat.localFilesOverview = parseClientHealthLocalFilesOverview("localFilesOverview", raw.localFilesOverview);
|
|
269
320
|
}
|