@indigoai-us/hq-cli 5.318.1-beta.0 → 5.320.0
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 +10 -2
- package/assets/scaffold/core/scripts/repo-run-registry.sh +61 -17
- package/assets/scaffold/core/scripts/session-journal.sh +40 -2
- package/dist/bin/hq-auth-refresh.d.ts +3 -1
- package/dist/bin/hq-auth-refresh.js +41 -6
- package/dist/command-catalog.generated.d.ts +22 -0
- package/dist/command-catalog.generated.js +29 -0
- package/dist/commands/cloud.js +0 -1
- package/dist/commands/core-hook-state-helpers-native.d.ts +1 -1
- package/dist/commands/core-hook-state-helpers-native.js +36 -7
- package/dist/commands/core-repo-run-registry-native.d.ts +1 -0
- package/dist/commands/core-repo-run-registry-native.js +10 -2
- package/dist/commands/core-rrr-worktree-scope-fix-flag.d.ts +4 -0
- package/dist/commands/core-rrr-worktree-scope-fix-flag.js +7 -0
- package/dist/commands/core-session-journal-native.js +31 -2
- package/dist/commands/lanes-checks.js +12 -0
- package/dist/commands/lanes-dropbox-repair.d.ts +14 -0
- package/dist/commands/lanes-dropbox-repair.js +33 -0
- package/dist/commands/lanes.js +44 -5
- package/dist/fast-core.js +6 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +135 -63
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +10 -1
- package/dist/lib/lanes/autopilot.js +9 -2
- package/dist/lib/lanes/flag-keys.d.ts +9 -0
- package/dist/lib/lanes/flag-keys.js +9 -0
- package/dist/lib/lanes/keep-alive-audit.d.ts +1 -1
- package/dist/lib/lanes/keep-alive-audit.js +69 -4
- package/dist/lib/lanes/orphaned-dropbox.d.ts +24 -0
- package/dist/lib/lanes/orphaned-dropbox.js +213 -0
- package/dist/lib/lanes/tracker-checks.d.ts +1 -1
- package/dist/utils/package-use-lease.d.ts +25 -0
- package/dist/utils/package-use-lease.js +213 -0
- package/package.json +1 -1
package/dist/commands/lanes.js
CHANGED
|
@@ -26,6 +26,7 @@ import { registerLaneKpisCommand } from "./lanes-kpis.js";
|
|
|
26
26
|
import { registerLaneKeepAliveAuditCommand } from "./lanes-keep-alive-audit.js";
|
|
27
27
|
import { registerLaneAutopilotReportCommand } from "./lanes-autopilot.js";
|
|
28
28
|
import { registerLaneChecksCommand } from "./lanes-checks.js";
|
|
29
|
+
import { registerLaneDropboxRepairCommand } from "./lanes-dropbox-repair.js";
|
|
29
30
|
import { registerLaneReportCommands } from "./lanes-report.js";
|
|
30
31
|
import { registerLaneRepoMergeHoldCommand } from "./lanes-hold.js";
|
|
31
32
|
import { registerLaneReviewCommand } from "./lanes-review.js";
|
|
@@ -34,7 +35,7 @@ import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
|
|
|
34
35
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
35
36
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
36
37
|
import { readLanesFlagLocalOverride, readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "../lib/lanes/registry-flag.js";
|
|
37
|
-
import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG } from "../lib/lanes/flag-keys.js";
|
|
38
|
+
import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, } from "../lib/lanes/flag-keys.js";
|
|
38
39
|
import { CODEX_ROLLOUT_RETENTION_FLAG, configuredCodexRolloutHomes, lowDiskGuardEnabledForLane, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
|
|
39
40
|
import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
|
|
40
41
|
import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
|
|
@@ -1336,6 +1337,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1336
1337
|
updated: new Map(),
|
|
1337
1338
|
unverifiedRemote: new Map(),
|
|
1338
1339
|
missingOnOwner: new Map(),
|
|
1340
|
+
skippedRemoteRefresh: new Set(),
|
|
1339
1341
|
};
|
|
1340
1342
|
const now = ctx.now();
|
|
1341
1343
|
const nowMs = now.getTime();
|
|
@@ -1377,6 +1379,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1377
1379
|
if (candidates.length === 0)
|
|
1378
1380
|
return result;
|
|
1379
1381
|
const enabledByCompany = new Map();
|
|
1382
|
+
const skipRefreshByCompany = new Map();
|
|
1380
1383
|
const enabledCandidates = [];
|
|
1381
1384
|
for (const lane of candidates) {
|
|
1382
1385
|
const company = resolveLaneCompany(hqRoot, lane).company;
|
|
@@ -1391,8 +1394,20 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1391
1394
|
});
|
|
1392
1395
|
enabledByCompany.set(companyKey, enabled);
|
|
1393
1396
|
}
|
|
1394
|
-
if (enabled)
|
|
1395
|
-
|
|
1397
|
+
if (!enabled)
|
|
1398
|
+
continue;
|
|
1399
|
+
if (options.skipRemoteRefreshForCompany) {
|
|
1400
|
+
let skipRefresh = skipRefreshByCompany.get(companyKey);
|
|
1401
|
+
if (skipRefresh === undefined) {
|
|
1402
|
+
skipRefresh = await options.skipRemoteRefreshForCompany(company);
|
|
1403
|
+
skipRefreshByCompany.set(companyKey, skipRefresh);
|
|
1404
|
+
}
|
|
1405
|
+
if (skipRefresh) {
|
|
1406
|
+
result.skippedRemoteRefresh.add(lane.lane_id);
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
enabledCandidates.push(lane);
|
|
1396
1411
|
}
|
|
1397
1412
|
if (enabledCandidates.length === 0)
|
|
1398
1413
|
return result;
|
|
@@ -4949,7 +4964,12 @@ async function runList(ctx, opts) {
|
|
|
4949
4964
|
}
|
|
4950
4965
|
const remoteRead = ctx.env[REMOTE_LANE_READ_ENV] === "1";
|
|
4951
4966
|
const remoteRefresh = remoteRead
|
|
4952
|
-
? {
|
|
4967
|
+
? {
|
|
4968
|
+
updated: new Map(),
|
|
4969
|
+
unverifiedRemote: new Map(),
|
|
4970
|
+
missingOnOwner: new Map(),
|
|
4971
|
+
skippedRemoteRefresh: new Set(),
|
|
4972
|
+
}
|
|
4953
4973
|
: await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(laneSource
|
|
4954
4974
|
.filter((lane) => matchesLaneFilters(lane, false))
|
|
4955
4975
|
.map((lane) => lane.lane_id)), {
|
|
@@ -4957,6 +4977,7 @@ async function runList(ctx, opts) {
|
|
|
4957
4977
|
laneIds: new Set(laneSource
|
|
4958
4978
|
.filter((lane) => matchesLaneFilters(lane, false))
|
|
4959
4979
|
.map((lane) => lane.lane_id)),
|
|
4980
|
+
skipRemoteRefreshForCompany: (company) => readLanesRegistryFlagFailOff(LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, { company, env: ctx.env }),
|
|
4960
4981
|
});
|
|
4961
4982
|
for (const [laneId, updated] of remoteRefresh.updated) {
|
|
4962
4983
|
const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
|
|
@@ -4994,6 +5015,7 @@ async function runList(ctx, opts) {
|
|
|
4994
5015
|
const ownerEligibleLaneIds = new Set();
|
|
4995
5016
|
for (const lane of selectedLanes) {
|
|
4996
5017
|
if (remoteRead ||
|
|
5018
|
+
remoteRefresh.skippedRemoteRefresh.has(lane.lane_id) ||
|
|
4997
5019
|
lane.host.kind !== "outpost" ||
|
|
4998
5020
|
isTerminalLaneState(lane.state) ||
|
|
4999
5021
|
laneRunsHere(lane, host)) {
|
|
@@ -5033,6 +5055,7 @@ async function runList(ctx, opts) {
|
|
|
5033
5055
|
const livenessBySession = new Map();
|
|
5034
5056
|
let livenessDeps;
|
|
5035
5057
|
const lanes = selectedLanes.map((lane) => {
|
|
5058
|
+
const overdue = Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now);
|
|
5036
5059
|
const remoteQueued = lane.state === "queued" &&
|
|
5037
5060
|
lane.host.kind === "outpost" &&
|
|
5038
5061
|
!laneRunsHere(lane, host);
|
|
@@ -5051,7 +5074,7 @@ async function runList(ctx, opts) {
|
|
|
5051
5074
|
queued_details: lane.queued_reason === "low_disk" ? lane.queued_details : undefined,
|
|
5052
5075
|
}
|
|
5053
5076
|
: {}),
|
|
5054
|
-
overdue
|
|
5077
|
+
overdue,
|
|
5055
5078
|
stalled: Boolean(lane.stalled),
|
|
5056
5079
|
// Listing is observational. Reconcile owns durable liveness transitions.
|
|
5057
5080
|
senior_lost: lane.state === "running" && lane.senior.kind === "session" && lane.senior.id
|
|
@@ -7872,6 +7895,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7872
7895
|
updated: new Map(),
|
|
7873
7896
|
unverifiedRemote: new Map(),
|
|
7874
7897
|
missingOnOwner: new Map(),
|
|
7898
|
+
skippedRemoteRefresh: new Set(),
|
|
7875
7899
|
}
|
|
7876
7900
|
: await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot));
|
|
7877
7901
|
const remoteLost = [];
|
|
@@ -8322,6 +8346,21 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8322
8346
|
deps: ctx.livenessDeps,
|
|
8323
8347
|
}),
|
|
8324
8348
|
}, wrap);
|
|
8349
|
+
registerLaneDropboxRepairCommand(lanes, {
|
|
8350
|
+
hqRoot: ctx.hqRoot,
|
|
8351
|
+
now: ctx.now,
|
|
8352
|
+
resolveCompany: ctx.resolveCompany,
|
|
8353
|
+
repairEnabled: async (lane) => {
|
|
8354
|
+
const resolution = resolveLaneCompany(ctx.hqRoot(), lane);
|
|
8355
|
+
const company = resolution.company ?? undefined;
|
|
8356
|
+
return readLanesRegistryFlagFailOff(LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, {
|
|
8357
|
+
company,
|
|
8358
|
+
...(company ? { companyUidSource: laneCompanyUidSource(resolution.source) } : {}),
|
|
8359
|
+
env: ctx.env,
|
|
8360
|
+
}, readLanesRegistryFlag);
|
|
8361
|
+
},
|
|
8362
|
+
wrap,
|
|
8363
|
+
});
|
|
8325
8364
|
registerLaneReportCommands(lanes, {
|
|
8326
8365
|
hqRoot: ctx.hqRoot,
|
|
8327
8366
|
resolveCompany: ctx.resolveCompany,
|
package/dist/fast-core.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { resolveLiveRoot } from "./utils/hq-roots.js";
|
|
10
10
|
import { runBundledScript } from "./utils/run-bundled-script.js";
|
|
11
11
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
12
|
+
import { CORE_RRR_WORKTREE_SCOPE_FIX_FLAG } from "./commands/core-rrr-worktree-scope-fix-flag.js";
|
|
12
13
|
export async function readStdinIfPiped(stream) {
|
|
13
14
|
if (stream.isTTY)
|
|
14
15
|
return "";
|
|
@@ -35,6 +36,9 @@ export async function runFastCore(argv) {
|
|
|
35
36
|
familyEnabled = envOverride ?? await (await import("./native-fast-route.js")).fastFlagEnabled(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
|
|
36
37
|
}
|
|
37
38
|
const useShell = entry.kind === "native" && Boolean(entry.familyFlagKey) && !familyEnabled;
|
|
39
|
+
const worktreeScopeFixEnabled = entry.name === "repo-run-registry"
|
|
40
|
+
? await (await import("./native-fast-route.js")).fastFlagEnabled(CORE_RRR_WORKTREE_SCOPE_FIX_FLAG, process.env, { numericEnvironmentOverride: false })
|
|
41
|
+
: undefined;
|
|
38
42
|
const shouldInjectRoot = useShell
|
|
39
43
|
? entry.shellHqRoot === "live" || (entry.shellHqRoot === "reconcile-default" &&
|
|
40
44
|
!parsed.args.includes("--hq-root") && !parsed.args.includes("--help") && !parsed.args.includes("-h"))
|
|
@@ -69,7 +73,7 @@ export async function runFastCore(argv) {
|
|
|
69
73
|
}
|
|
70
74
|
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
71
75
|
const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
|
|
72
|
-
const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
76
|
+
const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg, worktreeScopeFixEnabled);
|
|
73
77
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
74
78
|
// stderr message must survive the native route's process boundary.
|
|
75
79
|
process.exitCode = code;
|
|
@@ -90,6 +94,7 @@ export async function runFastCore(argv) {
|
|
|
90
94
|
...(entry.name === "migrate-policy-triggers"
|
|
91
95
|
? { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR || process.cwd() }
|
|
92
96
|
: {}),
|
|
97
|
+
...(entry.name === "repo-run-registry" ? { HQ_FLAG_CORE_RRR_WORKTREE_SCOPE_FIX: String(worktreeScopeFixEnabled) } : {}),
|
|
93
98
|
};
|
|
94
99
|
const { code } = runBundledScript({
|
|
95
100
|
asset,
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,17 @@ import "./node-preflight.js";
|
|
|
3
3
|
import "./node-network-compat-fast.js";
|
|
4
4
|
import { isVersionRequest } from "./version-request.js";
|
|
5
5
|
import { isFastCoreRequest } from "./fast-scaffold.js";
|
|
6
|
+
type PackageUseLeaseLoader = () => Promise<void>;
|
|
7
|
+
declare function isNpmPackageEntry(entryUrl: string): boolean;
|
|
8
|
+
declare function dispatchWithPackageUseLease(entryUrl: string, dispatch: () => void, loadLease?: PackageUseLeaseLoader): void | Promise<void>;
|
|
9
|
+
declare function runCliEntry(entryUrl: string, dispatch: () => void, loadLease?: PackageUseLeaseLoader): void | Promise<void>;
|
|
6
10
|
export declare const __test__: {
|
|
7
11
|
isVersionRequest: typeof isVersionRequest;
|
|
8
12
|
isFastCoreRequest: typeof isFastCoreRequest;
|
|
13
|
+
dispatchWithPackageUseLease: typeof dispatchWithPackageUseLease;
|
|
14
|
+
runCliEntry: typeof runCliEntry;
|
|
15
|
+
isNpmPackageEntry: typeof isNpmPackageEntry;
|
|
16
|
+
acquirePackageUseLease: (prefix: string, version: string, stateDirectory?: string) => Promise<import("./utils/package-use-lease.js").PackageUseLease>;
|
|
9
17
|
};
|
|
18
|
+
export {};
|
|
10
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -17,77 +17,149 @@ import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.
|
|
|
17
17
|
// command never run (HQ-CLI-W). Sets process.exitCode only, never process.exit,
|
|
18
18
|
// so runCli's finally still finalizes release health and flushes Sentry.
|
|
19
19
|
installProcessRejectionBoundary();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
function dispatchCliEntry() {
|
|
21
|
+
if (isVersionRequest(process.argv)) {
|
|
22
|
+
// Bare `--version` is the CLI semver only (the contract field updaters
|
|
23
|
+
// compare). `hq version` and `--json` print the labelled HQ/CLI report.
|
|
24
|
+
// Lazy so this path still skips the command graph.
|
|
25
|
+
if (wantsLabelledVersionReport(process.argv)) {
|
|
26
|
+
void import("./lib/flag-registry.js").then(({ kickFlagRegistryReadiness }) => {
|
|
27
|
+
kickFlagRegistryReadiness();
|
|
28
|
+
return import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Keep the benchmarked updater fast route limited to the installed semver.
|
|
33
|
+
// Registry checks and flag resolution belong to the regular command path.
|
|
34
|
+
void import("./cli-version.js").then(({ CLI_VERSION }) => {
|
|
35
|
+
process.stdout.write(`${CLI_VERSION}\n`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (parseFastCore(process.argv)?.entry.kind === "native" &&
|
|
40
|
+
(process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "true" ||
|
|
41
|
+
process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "1")) {
|
|
42
|
+
// An explicit enabled override has already decided the global gate. Skip the
|
|
43
|
+
// snapshot reader import on hot native plumbing paths; runFastCore still
|
|
44
|
+
// checks the command-family flag before choosing native or shell rollback.
|
|
45
|
+
void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
|
|
46
|
+
}
|
|
47
|
+
else if (parseFastCore(process.argv)?.entry.kind === "native") {
|
|
48
|
+
// Native routes are default-off. Resolve the company-scoped persisted flag
|
|
49
|
+
// without loading the command graph; a miss or stale snapshot takes the
|
|
50
|
+
// existing full-graph route.
|
|
51
|
+
void import("./native-fast-route.js").then(async ({ nativeFastRouteEnabled }) => {
|
|
52
|
+
if (await nativeFastRouteEnabled(process.argv)) {
|
|
53
|
+
const { runFastCore } = await import("./fast-core.js");
|
|
54
|
+
await runFastCore(process.argv);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const { runCli } = await import("./main.js");
|
|
58
|
+
await runCli();
|
|
59
|
+
}
|
|
28
60
|
});
|
|
29
61
|
}
|
|
30
|
-
else {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
else if (parseSecretsEnvFastRequest(process.argv)) {
|
|
63
|
+
const request = parseSecretsEnvFastRequest(process.argv);
|
|
64
|
+
void import("./secrets-env-fast-route.js").then(async (route) => {
|
|
65
|
+
await route.dispatchSecretsEnvFastRequest(request, {
|
|
66
|
+
isEnabled: () => route.secretsEnvFastRouteEnabled(request),
|
|
67
|
+
runFast: async () => {
|
|
68
|
+
const { runSecretsEnvFastPath } = await import("./secrets-env-fast-path.js");
|
|
69
|
+
return runSecretsEnvFastPath(request);
|
|
70
|
+
},
|
|
71
|
+
runCurrent: async () => {
|
|
72
|
+
const { runCli } = await import("./main.js");
|
|
73
|
+
await runCli();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
35
76
|
});
|
|
36
77
|
}
|
|
78
|
+
else if (isFastCoreRequest(process.argv)) {
|
|
79
|
+
// Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
|
|
80
|
+
// dispatches straight to the bundled-script runner, so it never pays for the
|
|
81
|
+
// ~60-module command graph main.ts imports. See src/fast-scaffold.ts.
|
|
82
|
+
void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Keep the command lifecycle detached from module evaluation, as it was
|
|
86
|
+
// before the fast --version split. Some best-effort teardown work uses
|
|
87
|
+
// unref'd handles; top-level-awaiting runCli() makes Node turn an otherwise
|
|
88
|
+
// successful command into exit 13 when that teardown promise remains pending.
|
|
89
|
+
// A floated rejection here no longer exits 0 silently: the boundary installed
|
|
90
|
+
// above turns it into a non-zero exit (HQ-CLI-W).
|
|
91
|
+
void import("./main.js").then(({ runCli }) => runCli());
|
|
92
|
+
}
|
|
37
93
|
}
|
|
38
|
-
|
|
39
|
-
(
|
|
40
|
-
process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "1")) {
|
|
41
|
-
// An explicit enabled override has already decided the global gate. Skip the
|
|
42
|
-
// snapshot reader import on hot native plumbing paths; runFastCore still
|
|
43
|
-
// checks the command-family flag before choosing native or shell rollback.
|
|
44
|
-
void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
|
|
94
|
+
function isNpmPackageEntry(entryUrl) {
|
|
95
|
+
return /\/node_modules\/@indigoai-us\/hq-cli\/dist\/index\.js$/i.test(entryUrl);
|
|
45
96
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
void import("./native-fast-route.js").then(async ({ nativeFastRouteEnabled }) => {
|
|
51
|
-
if (await nativeFastRouteEnabled(process.argv)) {
|
|
52
|
-
const { runFastCore } = await import("./fast-core.js");
|
|
53
|
-
await runFastCore(process.argv);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
const { runCli } = await import("./main.js");
|
|
57
|
-
await runCli();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
97
|
+
function isPackageUseUpdatePendingError(error) {
|
|
98
|
+
return Boolean(error &&
|
|
99
|
+
typeof error === "object" &&
|
|
100
|
+
error.name === "PackageUseUpdatePendingError");
|
|
60
101
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
102
|
+
function packageUseErrorClass(error) {
|
|
103
|
+
if (!error || typeof error !== "object")
|
|
104
|
+
return "unknown";
|
|
105
|
+
const value = error;
|
|
106
|
+
if (typeof value.code === "string")
|
|
107
|
+
return value.code;
|
|
108
|
+
if (value.cause && typeof value.cause === "object") {
|
|
109
|
+
const cause = value.cause;
|
|
110
|
+
if (typeof cause.code === "string")
|
|
111
|
+
return cause.code;
|
|
112
|
+
if (typeof cause.name === "string")
|
|
113
|
+
return cause.name;
|
|
114
|
+
}
|
|
115
|
+
return typeof value.name === "string" ? value.name : "unknown";
|
|
76
116
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
117
|
+
function dispatchWithPackageUseLease(entryUrl, dispatch, loadLease = async () => {
|
|
118
|
+
const { acquireCurrentPackageUseLease } = await import("./utils/package-use-lease.js");
|
|
119
|
+
acquireCurrentPackageUseLease();
|
|
120
|
+
}) {
|
|
121
|
+
if (!isNpmPackageEntry(entryUrl)) {
|
|
122
|
+
dispatch();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
return loadLease().then(dispatch, (error) => {
|
|
126
|
+
if (isPackageUseUpdatePendingError(error))
|
|
127
|
+
throw error;
|
|
128
|
+
process.stderr.write(`hq-cli package-use lease unavailable (${packageUseErrorClass(error)}); continuing without lease\n`);
|
|
129
|
+
dispatch();
|
|
130
|
+
});
|
|
82
131
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
132
|
+
function runCliEntry(entryUrl, dispatch, loadLease) {
|
|
133
|
+
const handlePendingUpdate = (error) => {
|
|
134
|
+
if (!isPackageUseUpdatePendingError(error))
|
|
135
|
+
throw error;
|
|
136
|
+
const message = error instanceof Error
|
|
137
|
+
? error.message
|
|
138
|
+
: "The HQ CLI package is being updated";
|
|
139
|
+
process.stderr.write(`hq-cli: ${message}\n`);
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
const result = dispatchWithPackageUseLease(entryUrl, dispatch, loadLease);
|
|
144
|
+
return result?.catch(handlePendingUpdate);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
handlePendingUpdate(error);
|
|
148
|
+
}
|
|
91
149
|
}
|
|
92
|
-
|
|
150
|
+
// Local builds and benchmark fixtures are not installed in an npm prefix.
|
|
151
|
+
// Keep their fast route free of package-use filesystem work; npm installs still
|
|
152
|
+
// publish a lease before dispatch.
|
|
153
|
+
void runCliEntry(import.meta.url, dispatchCliEntry);
|
|
154
|
+
export const __test__ = {
|
|
155
|
+
isVersionRequest,
|
|
156
|
+
isFastCoreRequest,
|
|
157
|
+
dispatchWithPackageUseLease,
|
|
158
|
+
runCliEntry,
|
|
159
|
+
isNpmPackageEntry,
|
|
160
|
+
acquirePackageUseLease: async (prefix, version, stateDirectory) => {
|
|
161
|
+
const lease = await import("./utils/package-use-lease.js");
|
|
162
|
+
return lease.acquirePackageUseLease(prefix, version, stateDirectory);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
93
165
|
//# sourceMappingURL=index.js.map
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { type HostApp } from "./supervisor.js";
|
|
|
12
12
|
export declare const LOG_ROTATE_BYTES: number;
|
|
13
13
|
/** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
|
|
14
14
|
export declare const RESTART_EXIT_CODE = 75;
|
|
15
|
+
/** Route an updater request through the daemon's existing graceful handoff. */
|
|
16
|
+
export declare function handoffForPackageUpdateRequest(requested: boolean, restartDaemon: () => void): void;
|
|
15
17
|
export interface RunDaemonOptions {
|
|
16
18
|
foreground?: boolean;
|
|
17
19
|
/** Started by the OS unit, which relaunches the daemon whenever it exits. */
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as os from "node:os";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { loadCachedTokens } from "@indigoai-us/hq-cloud";
|
|
14
14
|
import { CLI_VERSION } from "../../cli-version.js";
|
|
15
|
+
import { currentPackageUseUpdateRequested } from "../../utils/package-use-lease.js";
|
|
15
16
|
import { isLocalDependencyInstall, resolveRunningInstall } from "../../utils/version-gate.js";
|
|
16
17
|
import { agentKitPaths } from "../agent-kit/paths.js";
|
|
17
18
|
import { readBotConfig } from "../bot/config.js";
|
|
@@ -39,6 +40,11 @@ import { userToolDirs } from "./tools.js";
|
|
|
39
40
|
export const LOG_ROTATE_BYTES = 5 * 1024 * 1024;
|
|
40
41
|
/** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
|
|
41
42
|
export const RESTART_EXIT_CODE = 75;
|
|
43
|
+
/** Route an updater request through the daemon's existing graceful handoff. */
|
|
44
|
+
export function handoffForPackageUpdateRequest(requested, restartDaemon) {
|
|
45
|
+
if (requested)
|
|
46
|
+
restartDaemon();
|
|
47
|
+
}
|
|
42
48
|
const LEGACY_PROBE_TTL_MS = 60_000;
|
|
43
49
|
/** A flag read that has not answered by then counts as off. */
|
|
44
50
|
const HANDOFF_FLAG_TIMEOUT_MS = 10_000;
|
|
@@ -603,7 +609,10 @@ export async function runDaemon(opts) {
|
|
|
603
609
|
log("info", `rotated ${n} log file(s)`);
|
|
604
610
|
return true;
|
|
605
611
|
},
|
|
606
|
-
takeRequests: () =>
|
|
612
|
+
takeRequests: () => {
|
|
613
|
+
handoffForPackageUpdateRequest(currentPackageUseUpdateRequested(), restartDaemon);
|
|
614
|
+
return takeControlRequests(paths.requestsDir);
|
|
615
|
+
},
|
|
607
616
|
restartDaemon: () => restartDaemon(),
|
|
608
617
|
writeState: (state) => writeJsonAtomic(paths.statePath, state),
|
|
609
618
|
log,
|
|
@@ -6,7 +6,7 @@ import { isControllerAliasOutpostId } from "./admission-host.js";
|
|
|
6
6
|
import { LEAD_REGISTRY_REL, validateOutpostLeadRegistry } from "./autopilot-registry.js";
|
|
7
7
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
8
8
|
import { LanesError } from "./errors.js";
|
|
9
|
-
import { LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG, LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_FLAG, LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG, LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG as PER_LEAD_ERRORS_FLAG, } from "./flag-keys.js";
|
|
9
|
+
import { LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG, LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_FLAG, LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG, LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_FLAG, LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG as PER_LEAD_ERRORS_FLAG, } from "./flag-keys.js";
|
|
10
10
|
import { resolveLaneCompany } from "./lane-company.js";
|
|
11
11
|
import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
12
12
|
import { readAllLanes } from "./store.js";
|
|
@@ -786,9 +786,16 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
786
786
|
result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_company_unresolved" });
|
|
787
787
|
continue;
|
|
788
788
|
}
|
|
789
|
+
// A lead created on an Outpost records a local host, while its registry row names the Outpost.
|
|
790
|
+
const outpostNativeLead = !registeredLocalHost &&
|
|
791
|
+
input.outpostId === registered.host &&
|
|
792
|
+
lead.host.kind === "local" &&
|
|
793
|
+
lead.host.id === input.hostname &&
|
|
794
|
+
await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_FLAG, { company }).catch(() => false);
|
|
789
795
|
const hostMatchesRegistry = registeredLocalHost
|
|
790
796
|
? lead.host.kind === "local"
|
|
791
|
-
:
|
|
797
|
+
: outpostNativeLead ||
|
|
798
|
+
(lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host);
|
|
792
799
|
if (!hostMatchesRegistry ||
|
|
793
800
|
!laneRunsOnHost(lead, { hostname: input.hostname, outpostId: effectiveOutpostId })) {
|
|
794
801
|
result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_host_mismatch" });
|
|
@@ -4,6 +4,9 @@ export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-bala
|
|
|
4
4
|
export declare const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
|
|
5
5
|
/** Count only children with live work against their senior's hire grant. */
|
|
6
6
|
export declare const LANE_HIRE_GRANT_FINISHED_LANES_FLAG = "lanes.hire-grant-count-live-only";
|
|
7
|
+
/** Skips owner refreshes during ordinary lane list polling when explicitly enabled. */
|
|
8
|
+
export declare const LANE_LIST_SKIP_REMOTE_REFRESH_FLAG = "lanes.list-skip-remote-refresh";
|
|
9
|
+
export declare const LANE_LIST_SKIP_REMOTE_REFRESH_DEFAULT_ENABLED = false;
|
|
7
10
|
/** Enables missed-deadline lead nudges and second-miss operator escalation. */
|
|
8
11
|
export declare const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-missed-deadline-escalation";
|
|
9
12
|
export declare const LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG = "lanes.autopilot-nudge-dedupe";
|
|
@@ -22,4 +25,10 @@ export declare const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_DEFAULT_ENABLED = false;
|
|
|
22
25
|
/** Enables staleness notices for a registered lead's dashboard headline. */
|
|
23
26
|
export declare const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
|
|
24
27
|
export declare const LEAD_AUTOPILOT_HEADLINE_STALENESS_DEFAULT_ENABLED = false;
|
|
28
|
+
/** Lets an Outpost's autopilot wake leads created on that Outpost, which record a local host. */
|
|
29
|
+
export declare const LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_FLAG = "lanes.autopilot-outpost-native-leads";
|
|
30
|
+
export declare const LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_DEFAULT_ENABLED = false;
|
|
31
|
+
/** Allows applying an operator-reviewed repair of messages stranded in bare-ULID dropboxes. */
|
|
32
|
+
export declare const LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG = "lanes.orphaned-bare-id-dropbox-repair";
|
|
33
|
+
export declare const LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_DEFAULT_ENABLED = false;
|
|
25
34
|
//# sourceMappingURL=flag-keys.d.ts.map
|
|
@@ -4,6 +4,9 @@ export const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
|
|
|
4
4
|
export const LANE_ENVELOPE_REPAIR_CONTINUE_FLAG = "lanes.envelope-repair-continue";
|
|
5
5
|
/** Count only children with live work against their senior's hire grant. */
|
|
6
6
|
export const LANE_HIRE_GRANT_FINISHED_LANES_FLAG = "lanes.hire-grant-count-live-only";
|
|
7
|
+
/** Skips owner refreshes during ordinary lane list polling when explicitly enabled. */
|
|
8
|
+
export const LANE_LIST_SKIP_REMOTE_REFRESH_FLAG = "lanes.list-skip-remote-refresh";
|
|
9
|
+
export const LANE_LIST_SKIP_REMOTE_REFRESH_DEFAULT_ENABLED = false;
|
|
7
10
|
/** Enables missed-deadline lead nudges and second-miss operator escalation. */
|
|
8
11
|
export const LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG = "lanes.autopilot-missed-deadline-escalation";
|
|
9
12
|
export const LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG = "lanes.autopilot-nudge-dedupe";
|
|
@@ -22,4 +25,10 @@ export const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_DEFAULT_ENABLED = false;
|
|
|
22
25
|
/** Enables staleness notices for a registered lead's dashboard headline. */
|
|
23
26
|
export const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
|
|
24
27
|
export const LEAD_AUTOPILOT_HEADLINE_STALENESS_DEFAULT_ENABLED = false;
|
|
28
|
+
/** Lets an Outpost's autopilot wake leads created on that Outpost, which record a local host. */
|
|
29
|
+
export const LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_FLAG = "lanes.autopilot-outpost-native-leads";
|
|
30
|
+
export const LEAD_AUTOPILOT_OUTPOST_NATIVE_LEADS_DEFAULT_ENABLED = false;
|
|
31
|
+
/** Allows applying an operator-reviewed repair of messages stranded in bare-ULID dropboxes. */
|
|
32
|
+
export const LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG = "lanes.orphaned-bare-id-dropbox-repair";
|
|
33
|
+
export const LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_DEFAULT_ENABLED = false;
|
|
25
34
|
//# sourceMappingURL=flag-keys.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "self_wake" | "early_by_keep_alive" | "not_restarted" | "waiting";
|
|
1
|
+
type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "self_wake" | "early_by_keep_alive" | "not_restarted" | "keep_alive_off" | "waiting";
|
|
2
2
|
interface KeepAliveAuditRestart {
|
|
3
3
|
at: string;
|
|
4
4
|
trigger: string;
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS } from "./keep-alive.js";
|
|
4
4
|
import { LanesError } from "./errors.js";
|
|
5
5
|
import { laneRunDir } from "./spawn.js";
|
|
6
|
-
import { readAllLanes } from "./store.js";
|
|
6
|
+
import { listQuestionIds, readAllLanes, tryReadQuestion } from "./store.js";
|
|
7
7
|
function readDoneClassifications(hqRoot, laneId) {
|
|
8
8
|
const root = fs.realpathSync(hqRoot);
|
|
9
9
|
const watcherLog = path.join(laneRunDir(root, laneId), "logs", "watcher.out");
|
|
@@ -56,6 +56,7 @@ const VERDICTS = [
|
|
|
56
56
|
"message_wake",
|
|
57
57
|
"self_wake",
|
|
58
58
|
"early_by_keep_alive",
|
|
59
|
+
"keep_alive_off",
|
|
59
60
|
"not_restarted",
|
|
60
61
|
"waiting",
|
|
61
62
|
];
|
|
@@ -219,9 +220,66 @@ function hasDeliveredDropboxEvidence(hqRoot, laneId, stoppedAt, restartAt) {
|
|
|
219
220
|
const realFile = fs.realpathSync(file);
|
|
220
221
|
if (!withinRoot(root, realFile))
|
|
221
222
|
return false;
|
|
222
|
-
|
|
223
|
+
if (stat.mtimeMs >= stoppedAt && stat.mtimeMs <= restartAt + 2 * 60_000)
|
|
224
|
+
return true;
|
|
225
|
+
if (!name.endsWith(".inbox.jsonl") && !name.endsWith(".resume.jsonl"))
|
|
226
|
+
return false;
|
|
227
|
+
const text = fs.readFileSync(realFile, "utf8");
|
|
228
|
+
for (const line of text.split(/\r?\n/)) {
|
|
229
|
+
if (!line.trim())
|
|
230
|
+
continue;
|
|
231
|
+
let value;
|
|
232
|
+
try {
|
|
233
|
+
value = JSON.parse(line);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
throw new LanesError("keep_alive_audit_event_invalid", `Cannot parse delivered lane message ${realFile}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: laneId, path: realFile });
|
|
237
|
+
}
|
|
238
|
+
if (isRecord(value)) {
|
|
239
|
+
const at = parseTime(value.at);
|
|
240
|
+
if (at !== undefined && at >= stoppedAt && at <= restartAt + 2 * 60_000)
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
223
245
|
});
|
|
224
246
|
}
|
|
247
|
+
function hasMessageResumeEvidence(lane, stoppedAt, restartAt) {
|
|
248
|
+
return (lane.resume_history ?? []).some((entry) => (entry.trigger === "message" || entry.trigger === "answered_questions") &&
|
|
249
|
+
(parseTime(entry.at) ?? Number.NaN) >= stoppedAt &&
|
|
250
|
+
(parseTime(entry.at) ?? Number.NaN) <= restartAt + 2 * 60_000);
|
|
251
|
+
}
|
|
252
|
+
function hasAnsweredQuestionEvidence(hqRoot, laneId, stoppedAt, restartAt) {
|
|
253
|
+
for (const questionId of listQuestionIds(hqRoot)) {
|
|
254
|
+
let question;
|
|
255
|
+
try {
|
|
256
|
+
question = tryReadQuestion(hqRoot, questionId);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// One unreadable question file must not fail the whole audit.
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!question || question.lane_id !== laneId || question.origin?.id !== laneId)
|
|
263
|
+
continue;
|
|
264
|
+
const timestamps = [
|
|
265
|
+
question.delivered_at,
|
|
266
|
+
question.answer?.answered_at,
|
|
267
|
+
...(question.answer_history ?? []).map((answer) => answer.answered_at),
|
|
268
|
+
];
|
|
269
|
+
if (timestamps.some((timestamp) => {
|
|
270
|
+
const at = parseTime(timestamp);
|
|
271
|
+
return at !== undefined && at >= stoppedAt && at <= restartAt + 2 * 60_000;
|
|
272
|
+
}))
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
function laneWasKeepAliveOffAtStop(lane, stoppedAt) {
|
|
278
|
+
if (lane.keep_alive === true)
|
|
279
|
+
return false;
|
|
280
|
+
const optedInAt = parseTime(lane.keep_alive_opted_in_at);
|
|
281
|
+
return optedInAt === undefined || optedInAt > stoppedAt;
|
|
282
|
+
}
|
|
225
283
|
function firstRestartAfter(lane, stop, nextEnvelopeMs) {
|
|
226
284
|
const restart = (lane.resume_history ?? [])
|
|
227
285
|
.map((entry) => ({ entry, at: parseTime(entry.at) }))
|
|
@@ -241,13 +299,18 @@ function classifyVerdict(input) {
|
|
|
241
299
|
const restartAt = Date.parse(restart.at);
|
|
242
300
|
if (restart.caller === lane.lane_id)
|
|
243
301
|
return "self_wake";
|
|
302
|
+
if (restart.trigger === "message" || restart.trigger === "answered_questions")
|
|
303
|
+
return "message_wake";
|
|
244
304
|
if (restart.trigger === "keep_alive") {
|
|
245
305
|
const event = input.resumeEvents
|
|
246
|
-
.filter((candidate) => candidate.at > input.stoppedAt &&
|
|
306
|
+
.filter((candidate) => candidate.at > input.stoppedAt && candidate.at <= restartAt + 2 * 60_000)
|
|
247
307
|
.sort((left, right) => Math.abs(left.at - restartAt) - Math.abs(right.at - restartAt))[0];
|
|
248
308
|
if (event?.reason === "pending_work")
|
|
249
309
|
return "message_wake";
|
|
250
|
-
if (event?.reason === undefined &&
|
|
310
|
+
if (event?.reason === undefined &&
|
|
311
|
+
(hasDeliveredDropboxEvidence(input.hqRoot, lane.lane_id, input.stoppedAt, restartAt) ||
|
|
312
|
+
hasMessageResumeEvidence(lane, input.stoppedAt, restartAt) ||
|
|
313
|
+
hasAnsweredQuestionEvidence(input.hqRoot, lane.lane_id, input.stoppedAt, restartAt))) {
|
|
251
314
|
return "message_wake";
|
|
252
315
|
}
|
|
253
316
|
const nextCheckAt = parseTime(input.nextCheckAt);
|
|
@@ -264,6 +327,8 @@ function classifyVerdict(input) {
|
|
|
264
327
|
return "early_wake";
|
|
265
328
|
return "late";
|
|
266
329
|
}
|
|
330
|
+
if (laneWasKeepAliveOffAtStop(lane, input.stoppedAt))
|
|
331
|
+
return "keep_alive_off";
|
|
267
332
|
if (lane.state === "done" && nowMs > dueAt + KEEP_ALIVE_NOT_RESTARTED_GRACE_MS)
|
|
268
333
|
return "not_restarted";
|
|
269
334
|
return "waiting";
|