@indigoai-us/hq-cli 5.246.0 → 5.247.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
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.247.0] — 2026-09-27
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- The default-off `lanes.supersession-lineage-aware` flag lets a worker's lane
|
|
9
|
+
keep-alive and message delivery continue when its newer lane senior or a
|
|
10
|
+
same-profile sibling under that senior would otherwise look like a replacement.
|
|
11
|
+
- Keep-alive setup reads lineage settings before provider locks, and message
|
|
12
|
+
eligibility rechecks lane and retirement state after that read.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `hq agent kit install` no longer implies the `@reboot` crontab entry is
|
|
17
|
+
enough on its own. The entry is installed and does restart the kit on an
|
|
18
|
+
ordinary host, but on a container whose root filesystem is rebuilt each boot
|
|
19
|
+
both cron and the CLI are erased along with it. The line now says so and
|
|
20
|
+
says what to do instead.
|
|
21
|
+
|
|
5
22
|
## [5.246.0] — 2026-09-27
|
|
6
23
|
|
|
7
24
|
### Fixed
|
|
@@ -279,6 +279,10 @@ export function registerAgentKitCommand(agent) {
|
|
|
279
279
|
console.log(` kit log: ${result.fallback.logPath}`);
|
|
280
280
|
if (result.fallback.reboot.installed) {
|
|
281
281
|
console.log(chalk.green(" reboot: a @reboot crontab entry was installed, so the kit restarts with the host"));
|
|
282
|
+
console.log(chalk.dim(" That holds only if cron and this CLI survive the restart. On a container " +
|
|
283
|
+
"whose root filesystem is rebuilt each boot, both are erased and the entry goes with " +
|
|
284
|
+
"them — reinstall the kit from your persistent storage after a restart, or have your " +
|
|
285
|
+
"platform start `hq agent kit run all` for you."));
|
|
282
286
|
}
|
|
283
287
|
else {
|
|
284
288
|
console.log(chalk.red(` reboot: NOT protected — ${result.fallback.reboot.reason}. Until this is fixed the kit ` +
|
package/dist/commands/lanes.js
CHANGED
|
@@ -46,7 +46,7 @@ import { effectiveAccountHome } from "../lib/lanes/account-home.js";
|
|
|
46
46
|
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, appendDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
|
|
47
47
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
48
48
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
49
|
-
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
49
|
+
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
50
50
|
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
51
51
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
52
52
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
@@ -4891,21 +4891,22 @@ async function runKeepAlive(ctx, laneId, opts) {
|
|
|
4891
4891
|
throw new LanesError("keep_alive_mode_invalid", "Pass exactly one of --on or --off", { lane_id: laneId });
|
|
4892
4892
|
}
|
|
4893
4893
|
const hqRoot = ctx.hqRoot();
|
|
4894
|
+
let lineageAware = false;
|
|
4895
|
+
if (opts.on) {
|
|
4896
|
+
const initialLane = readLane(hqRoot, laneId);
|
|
4897
|
+
assertKeepAliveChangeAllowed(ctx, initialLane, laneId);
|
|
4898
|
+
lineageAware = await readLanesRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, {
|
|
4899
|
+
company: initialLane.company,
|
|
4900
|
+
env: ctx.env,
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4894
4903
|
const updated = await withAllLaneAdmissionLocks(hqRoot, async () => {
|
|
4895
4904
|
const lane = readLane(hqRoot, laneId);
|
|
4896
|
-
|
|
4897
|
-
throw new LanesError("keep_alive_unauthorized", `Only senior ${seniorKey(lane.senior)} may change lane ${laneId}'s keep-alive setting`, { lane_id: laneId, senior: lane.senior });
|
|
4898
|
-
}
|
|
4899
|
-
if (!laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
|
|
4900
|
-
throw new LanesError("keep_alive_owner_host_required", `Run hq lanes keep-alive ${laneId} on its owner host ${lane.host.kind}:${lane.host.id}`, { lane_id: laneId, host: lane.host });
|
|
4901
|
-
}
|
|
4902
|
-
if (lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") {
|
|
4903
|
-
throw new LanesError("keep_alive_terminal_lane", `Cannot change keep-alive for lane ${laneId} in terminal state ${lane.state}`, { lane_id: laneId, state: lane.state });
|
|
4904
|
-
}
|
|
4905
|
+
assertKeepAliveChangeAllowed(ctx, lane, laneId);
|
|
4905
4906
|
let optedInAt;
|
|
4906
4907
|
if (opts.on) {
|
|
4907
4908
|
const retiredLaneIds = loadRetiredLaneIds(hqRoot);
|
|
4908
|
-
const exclusion = keepAliveExclusion(lane, loadAllLanes(hqRoot), retiredLaneIds);
|
|
4909
|
+
const exclusion = keepAliveExclusion(lane, loadAllLanes(hqRoot), retiredLaneIds, lineageAware);
|
|
4909
4910
|
if (exclusion !== undefined) {
|
|
4910
4911
|
throw new LanesError("keep_alive_lane_excluded", `Cannot opt in lane ${laneId}: it is ${exclusion}`, { lane_id: laneId, blocked_reason: exclusion });
|
|
4911
4912
|
}
|
|
@@ -4926,6 +4927,17 @@ async function runKeepAlive(ctx, laneId, opts) {
|
|
|
4926
4927
|
lane: updated,
|
|
4927
4928
|
});
|
|
4928
4929
|
}
|
|
4930
|
+
function assertKeepAliveChangeAllowed(ctx, lane, laneId) {
|
|
4931
|
+
if (!callerMatchesSenior(ctx, lane.senior)) {
|
|
4932
|
+
throw new LanesError("keep_alive_unauthorized", "Only senior " + seniorKey(lane.senior) + " may change lane " + laneId + "'s keep-alive setting", { lane_id: laneId, senior: lane.senior });
|
|
4933
|
+
}
|
|
4934
|
+
if (!laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
|
|
4935
|
+
throw new LanesError("keep_alive_owner_host_required", "Run hq lanes keep-alive " + laneId + " on its owner host " + lane.host.kind + ":" + lane.host.id, { lane_id: laneId, host: lane.host });
|
|
4936
|
+
}
|
|
4937
|
+
if (lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") {
|
|
4938
|
+
throw new LanesError("keep_alive_terminal_lane", "Cannot change keep-alive for lane " + laneId + " in terminal state " + lane.state, { lane_id: laneId, state: lane.state });
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4929
4941
|
async function runGrant(ctx, laneId, opts) {
|
|
4930
4942
|
if (opts.hire === undefined) {
|
|
4931
4943
|
throw new LanesError("missing_hire_grant", "--hire is required", {});
|
|
@@ -5808,7 +5820,9 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
5808
5820
|
}
|
|
5809
5821
|
}
|
|
5810
5822
|
try {
|
|
5811
|
-
const
|
|
5823
|
+
const eligibility = await messageLaneExclusion(ctx, lane);
|
|
5824
|
+
lane = eligibility.lane;
|
|
5825
|
+
const exclusion = eligibility.exclusion;
|
|
5812
5826
|
if (exclusion !== undefined) {
|
|
5813
5827
|
let messageCount;
|
|
5814
5828
|
if (dropboxInboxHasMessages(hqRoot, laneId)) {
|
|
@@ -6183,10 +6197,24 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
6183
6197
|
throw err;
|
|
6184
6198
|
}
|
|
6185
6199
|
}
|
|
6186
|
-
function messageLaneExclusion(ctx, lane) {
|
|
6187
|
-
if (lane.state === "killed" || lane.state === "cancelled")
|
|
6188
|
-
return lane.state;
|
|
6189
|
-
|
|
6200
|
+
async function messageLaneExclusion(ctx, lane) {
|
|
6201
|
+
if (lane.state === "killed" || lane.state === "cancelled") {
|
|
6202
|
+
return { lane, exclusion: lane.state };
|
|
6203
|
+
}
|
|
6204
|
+
const lineageAware = await readLanesRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, {
|
|
6205
|
+
company: lane.company,
|
|
6206
|
+
env: ctx.env,
|
|
6207
|
+
});
|
|
6208
|
+
const currentLane = readLane(ctx.hqRoot(), lane.lane_id);
|
|
6209
|
+
if (currentLane.state === "killed" || currentLane.state === "cancelled") {
|
|
6210
|
+
return { lane: currentLane, exclusion: currentLane.state };
|
|
6211
|
+
}
|
|
6212
|
+
const lanes = loadAllLanes(ctx.hqRoot());
|
|
6213
|
+
const retiredLaneIds = loadRetiredLaneIds(ctx.hqRoot());
|
|
6214
|
+
return {
|
|
6215
|
+
lane: currentLane,
|
|
6216
|
+
exclusion: keepAliveExclusion(currentLane, lanes, retiredLaneIds, lineageAware),
|
|
6217
|
+
};
|
|
6190
6218
|
}
|
|
6191
6219
|
function recordMessageUndeliverable(ctx, lane, reason, messageCount) {
|
|
6192
6220
|
writeWatcherDecision(watcherOutLogPath(ctx.hqRoot(), lane.lane_id), {
|
|
@@ -6206,10 +6234,12 @@ function recordMessageUndeliverable(ctx, lane, reason, messageCount) {
|
|
|
6206
6234
|
*/
|
|
6207
6235
|
async function recoverQueuedMessages(ctx, laneId) {
|
|
6208
6236
|
const hqRoot = ctx.hqRoot();
|
|
6209
|
-
|
|
6237
|
+
let lane = readLane(hqRoot, laneId);
|
|
6210
6238
|
if (!dropboxInboxHasMessages(hqRoot, laneId))
|
|
6211
6239
|
return { action: "none", message_count: 0 };
|
|
6212
|
-
const
|
|
6240
|
+
const eligibility = await messageLaneExclusion(ctx, lane);
|
|
6241
|
+
lane = eligibility.lane;
|
|
6242
|
+
const exclusion = eligibility.exclusion;
|
|
6213
6243
|
const refusal = exclusion ?? (lane.state === "done"
|
|
6214
6244
|
? "done_before_envelope"
|
|
6215
6245
|
: lane.state === "awaiting_input"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { LaneRecord, QuestionRecord } from "./types.js";
|
|
2
2
|
export declare const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
3
3
|
export declare const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
4
|
+
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
5
|
+
export declare const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
6
|
+
export declare const LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED = false;
|
|
4
7
|
export declare const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
5
8
|
export declare const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS: number;
|
|
6
9
|
export declare const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS: number;
|
|
@@ -27,7 +30,7 @@ export declare function retiredLaneRegistryPath(hqRoot: string): string;
|
|
|
27
30
|
export declare function loadRetiredLaneIds(hqRoot: string): ReadonlySet<string>;
|
|
28
31
|
/** IDs may be stored bare or with an owner-host suffix after an underscore. */
|
|
29
32
|
export declare function isRetiredLaneId(laneId: string, retiredLaneIds: ReadonlySet<string>): boolean;
|
|
30
|
-
export declare function keepAliveSupersededBy(lane: LaneRecord, lanes: readonly LaneRecord[]): LaneRecord | undefined;
|
|
31
|
-
export declare function keepAliveExclusion(lane: LaneRecord, lanes: readonly LaneRecord[], retiredLaneIds: ReadonlySet<string
|
|
33
|
+
export declare function keepAliveSupersededBy(lane: LaneRecord, lanes: readonly LaneRecord[], lineageAware?: boolean): LaneRecord | undefined;
|
|
34
|
+
export declare function keepAliveExclusion(lane: LaneRecord, lanes: readonly LaneRecord[], retiredLaneIds: ReadonlySet<string>, lineageAware?: boolean): "retired" | "superseded" | undefined;
|
|
32
35
|
export declare function leadLaneKeepAliveBackoffMs(attempt: number): number;
|
|
33
36
|
//# sourceMappingURL=keep-alive.d.ts.map
|
|
@@ -5,6 +5,9 @@ import { dropboxInboxHasMessages } from "./dropbox.js";
|
|
|
5
5
|
import { listQuestionIds, readQuestion } from "./store.js";
|
|
6
6
|
export const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
7
7
|
export const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
8
|
+
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
9
|
+
export const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
10
|
+
export const LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED = false;
|
|
8
11
|
export const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
9
12
|
export const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS = 5 * 60 * 1000;
|
|
10
13
|
export const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS = 60 * 60 * 1000;
|
|
@@ -119,11 +122,22 @@ export function isRetiredLaneId(laneId, retiredLaneIds) {
|
|
|
119
122
|
}
|
|
120
123
|
return false;
|
|
121
124
|
}
|
|
122
|
-
export function keepAliveSupersededBy(lane, lanes) {
|
|
125
|
+
export function keepAliveSupersededBy(lane, lanes, lineageAware = LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED) {
|
|
123
126
|
const createdAt = Date.parse(lane.created_at);
|
|
124
127
|
if (!Number.isFinite(createdAt))
|
|
125
128
|
return undefined;
|
|
126
129
|
const lanesById = new Map(lanes.map((candidate) => [candidate.lane_id, candidate]));
|
|
130
|
+
const targetAncestorIds = new Set();
|
|
131
|
+
if (lineageAware) {
|
|
132
|
+
let parentLaneId = lane.senior.kind === "lane" ? lane.senior.id : undefined;
|
|
133
|
+
while (parentLaneId) {
|
|
134
|
+
if (targetAncestorIds.has(parentLaneId))
|
|
135
|
+
break;
|
|
136
|
+
targetAncestorIds.add(parentLaneId);
|
|
137
|
+
const parent = lanesById.get(parentLaneId);
|
|
138
|
+
parentLaneId = parent?.senior.kind === "lane" ? parent.senior.id : undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
127
141
|
return lanes.find((candidate) => {
|
|
128
142
|
if (candidate.lane_id === lane.lane_id || Date.parse(candidate.created_at) <= createdAt)
|
|
129
143
|
return false;
|
|
@@ -132,6 +146,8 @@ export function keepAliveSupersededBy(lane, lanes) {
|
|
|
132
146
|
: candidate.company?.uid === lane.company.uid;
|
|
133
147
|
if (!sameCompany)
|
|
134
148
|
return false;
|
|
149
|
+
if (lineageAware && targetAncestorIds.has(candidate.lane_id))
|
|
150
|
+
return false;
|
|
135
151
|
// Any descendant is part of this lead's work tree, not a replacement lead.
|
|
136
152
|
let parentLaneId = candidate.senior.kind === "lane" ? candidate.senior.id : undefined;
|
|
137
153
|
const visitedLaneIds = new Set();
|
|
@@ -144,18 +160,22 @@ export function keepAliveSupersededBy(lane, lanes) {
|
|
|
144
160
|
const parent = lanesById.get(parentLaneId);
|
|
145
161
|
parentLaneId = parent?.senior.kind === "lane" ? parent.senior.id : undefined;
|
|
146
162
|
}
|
|
163
|
+
const sameLaneSenior = lane.senior.kind === "lane" &&
|
|
164
|
+
candidate.senior.kind === "lane" &&
|
|
165
|
+
candidate.senior.id === lane.senior.id;
|
|
147
166
|
const sameProfile = lane.worker_profile !== undefined &&
|
|
148
|
-
candidate.worker_profile === lane.worker_profile
|
|
167
|
+
candidate.worker_profile === lane.worker_profile &&
|
|
168
|
+
!(lineageAware && sameLaneSenior);
|
|
149
169
|
const projectLead = lane.project_id !== undefined &&
|
|
150
170
|
candidate.project_id === lane.project_id &&
|
|
151
171
|
(candidate.hire_grant ?? 0) > 0;
|
|
152
172
|
return sameProfile || projectLead;
|
|
153
173
|
});
|
|
154
174
|
}
|
|
155
|
-
export function keepAliveExclusion(lane, lanes, retiredLaneIds) {
|
|
175
|
+
export function keepAliveExclusion(lane, lanes, retiredLaneIds, lineageAware = LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED) {
|
|
156
176
|
if (isRetiredLaneId(lane.lane_id, retiredLaneIds))
|
|
157
177
|
return "retired";
|
|
158
|
-
return keepAliveSupersededBy(lane, lanes) === undefined ? undefined : "superseded";
|
|
178
|
+
return keepAliveSupersededBy(lane, lanes, lineageAware) === undefined ? undefined : "superseded";
|
|
159
179
|
}
|
|
160
180
|
export function leadLaneKeepAliveBackoffMs(attempt) {
|
|
161
181
|
if (!Number.isSafeInteger(attempt) || attempt < 1) {
|
|
@@ -22,7 +22,7 @@ import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecor
|
|
|
22
22
|
import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
|
|
23
23
|
import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, throttleStatePath, writeThrottleState, } from "./throttle.js";
|
|
24
24
|
import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
25
|
-
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, canResumeDoneKeepAliveLane, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
25
|
+
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, canResumeDoneKeepAliveLane, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
26
26
|
import { providerSupportsSessionResume } from "./delivery.js";
|
|
27
27
|
import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./quota.js";
|
|
28
28
|
import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
@@ -821,17 +821,24 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
821
821
|
keepAliveFlagCompanies.set(key, lane.company);
|
|
822
822
|
}
|
|
823
823
|
const keepAliveFlagByCompany = new Map();
|
|
824
|
+
const supersessionLineageAwareByCompany = new Map();
|
|
824
825
|
const env = deps.env ?? process.env;
|
|
825
826
|
await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
|
|
826
|
-
|
|
827
|
+
const options = {
|
|
827
828
|
company: company ?? undefined,
|
|
828
829
|
env,
|
|
829
|
-
}
|
|
830
|
+
};
|
|
831
|
+
const [keepAliveEnabled, lineageAware] = await Promise.all([
|
|
832
|
+
readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
|
|
833
|
+
readRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
|
|
834
|
+
]);
|
|
835
|
+
keepAliveFlagByCompany.set(key, keepAliveEnabled);
|
|
836
|
+
supersessionLineageAwareByCompany.set(key, lineageAware);
|
|
830
837
|
}));
|
|
831
838
|
await withAllLaneAdmissionLocks(hqRoot, async () => {
|
|
832
839
|
const freshLanes = loadLaneRecords(hqRoot, summary);
|
|
833
840
|
const freshQuestions = loadQuestionRecords(hqRoot, summary);
|
|
834
|
-
resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, deps, now, keepAliveFlagByCompany);
|
|
841
|
+
resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, deps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany);
|
|
835
842
|
const drainLanes = loadLaneRecords(hqRoot, summary);
|
|
836
843
|
await admitQueued(hqRoot, drainLanes, summary, deps, now);
|
|
837
844
|
await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, deps);
|
|
@@ -1546,7 +1553,7 @@ function latestEnvelopeMtimeMs(hqRoot, lane, ref) {
|
|
|
1546
1553
|
throw new LanesError("keep_alive_envelope_timestamp_unavailable", `Cannot stat latest envelope for lane ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: lane.lane_id, path: filePath });
|
|
1547
1554
|
}
|
|
1548
1555
|
}
|
|
1549
|
-
function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany) {
|
|
1556
|
+
function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany) {
|
|
1550
1557
|
const nowMs = now.getTime();
|
|
1551
1558
|
const nowIso = now.toISOString();
|
|
1552
1559
|
const config = loadLanesConfig(hqRoot);
|
|
@@ -1702,7 +1709,7 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
|
|
|
1702
1709
|
recordKeepAliveSkip(hqRoot, lane, "retired_list_invalid", summary, deps);
|
|
1703
1710
|
continue;
|
|
1704
1711
|
}
|
|
1705
|
-
const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds);
|
|
1712
|
+
const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, supersessionLineageAwareByCompany.get(keepAliveCompanyKey(lane.company)) === true);
|
|
1706
1713
|
if (exclusion !== undefined) {
|
|
1707
1714
|
recordKeepAliveSkip(hqRoot, lane, exclusion, summary, deps);
|
|
1708
1715
|
continue;
|
|
@@ -1790,7 +1797,7 @@ function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCom
|
|
|
1790
1797
|
continue;
|
|
1791
1798
|
}
|
|
1792
1799
|
const currentLanes = loadLaneRecords(hqRoot, summary);
|
|
1793
|
-
const currentExclusion = keepAliveExclusion(current, currentLanes, retiredLaneIds);
|
|
1800
|
+
const currentExclusion = keepAliveExclusion(current, currentLanes, retiredLaneIds, supersessionLineageAwareByCompany.get(keepAliveCompanyKey(current.company)) === true);
|
|
1794
1801
|
if (currentExclusion !== undefined) {
|
|
1795
1802
|
recordKeepAliveSkip(hqRoot, current, currentExclusion, summary, deps);
|
|
1796
1803
|
continue;
|