@indigoai-us/hq-cli 5.245.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.
@@ -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>): "retired" | "superseded" | undefined;
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
- keepAliveFlagByCompany.set(key, await readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.245.0",
3
+ "version": "5.247.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@aws-sdk/client-iot-data-plane": "^3.1096.0",
46
46
  "@aws-sdk/client-s3": "^3.1049.0",
47
- "@indigoai-us/hq-cloud": "~6.18.5",
47
+ "@indigoai-us/hq-cloud": "~6.18.10",
48
48
  "@indigoai-us/hq-flags-client": "^0.1.2",
49
49
  "@sentry/node": "^10.49.0",
50
50
  "@tobilu/qmd": "2.5.3",