@indigoai-us/hq-cli 5.264.0 → 5.266.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.
@@ -49,6 +49,7 @@ import { createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, emitLane
49
49
  import { envelopeQuestionId } from "./questions.js";
50
50
  import { readLanesRegistryFlag } from "./registry-flag.js";
51
51
  import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "./blocker-freshness.js";
52
+ import { krFreshnessFingerprint, resolveKrFreshnessForLane, } from "./kr-freshness.js";
52
53
  import { MANAGER_STATUS_GATE_FLAG, managerStatusWasWritten, readStatusSnapshot, resolveManagerStatusTarget, managerStatusGateEnabledForLane, } from "./manager-status-gate.js";
53
54
  import { queueLaneTerminalResume, queueManagerStatusGateResume } from "./keep-alive.js";
54
55
  import { monitorSessionDir } from "../monitor.js";
@@ -863,6 +864,72 @@ async function settleBlockerFreshness(hqRoot, lane, manifestTurnId, at, deps, me
863
864
  }
864
865
  return updated;
865
866
  }
867
+ async function settleKrFreshness(hqRoot, lane, manifestTurnId, at, deps, logDecision) {
868
+ if (manifestTurnId !== undefined && lane.status_gate !== undefined && lane.status_gate.turn_id !== manifestTurnId) {
869
+ logDecision({ event: "kr_freshness_baseline_mismatch", at, turn_id: lane.status_gate.turn_id });
870
+ return lane;
871
+ }
872
+ let errorLogged = false;
873
+ const resolution = await resolveKrFreshnessForLane(hqRoot, lane, {
874
+ readFlag: deps.readKrFreshnessFlag ?? readLanesRegistryFlag,
875
+ now: Date.parse(at),
876
+ onError: (error) => {
877
+ if (errorLogged)
878
+ return;
879
+ errorLogged = true;
880
+ const code = error instanceof LanesError ? error.error : "kr_freshness_unavailable";
881
+ process.stderr.write(`hq lanes watcher: KR freshness disabled for ${lane.lane_id}: ${code}\n`);
882
+ logDecision({ event: "kr_freshness_disabled", at, error: code });
883
+ },
884
+ });
885
+ if (!resolution.enabled || resolution.statusPath === undefined || resolution.error !== undefined)
886
+ return lane;
887
+ const stalled = resolution.kpis.filter((kpi) => kpi.verdict === "stalled");
888
+ if (stalled.length === 0) {
889
+ if (lane.kr_freshness_state === undefined)
890
+ return lane;
891
+ return retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => current.kr_freshness_state === undefined
892
+ ? current
893
+ : { ...current, kr_freshness_state: undefined });
894
+ }
895
+ const fingerprint = krFreshnessFingerprint(resolution.stalledKeys);
896
+ const names = stalled.map((kpi) => kpi.name);
897
+ const turnId = manifestTurnId ?? lane.status_gate?.turn_id ?? `unknown-${at}`;
898
+ const resumeText = `These KPIs in ${resolution.statusPath} were not re-measured in the last ${resolution.windowHours}h: ${names.join(", ")}. Measure each one now, update value and measured_at, then end the turn.`;
899
+ return retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => {
900
+ const history = current.status_gate_history ?? [];
901
+ if (history.some((entry) => entry.event === "kr_stalled" && entry.turn_id === turnId))
902
+ return current;
903
+ const sameStalledSet = current.kr_freshness_state?.stalled_fingerprint === fingerprint;
904
+ let nextLane = current;
905
+ let resumeQueued = false;
906
+ let resumeSkippedReason;
907
+ if (!sameStalledSet) {
908
+ const mergeExisting = current.pending_resume !== undefined &&
909
+ current.state !== "blocked" && current.state !== "awaiting_input";
910
+ const resume = queueLaneTerminalResume(current, at, resumeText, { mergeExisting });
911
+ nextLane = resume.lane;
912
+ resumeQueued = resume.resumeQueued;
913
+ resumeSkippedReason = resume.resumeSkippedReason;
914
+ }
915
+ const entry = {
916
+ event: "kr_stalled",
917
+ turn_id: turnId,
918
+ path: resolution.statusPath,
919
+ at,
920
+ names,
921
+ count: stalled.length,
922
+ fingerprint,
923
+ ...(resumeQueued ? { resume_queued: true } : {}),
924
+ ...(resumeSkippedReason !== undefined ? { resume_skipped_reason: resumeSkippedReason } : {}),
925
+ };
926
+ return {
927
+ ...nextLane,
928
+ kr_freshness_state: { stalled_fingerprint: fingerprint },
929
+ status_gate_history: appendCappedHistory(history, entry),
930
+ };
931
+ });
932
+ }
866
933
  function resolveBlockerFreshnessSessionInbox(hqRoot, sessionId) {
867
934
  let candidates;
868
935
  try {
@@ -1452,11 +1519,13 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
1452
1519
  if (terminalWrite.changed) {
1453
1520
  updated = await settleManagerStatusGate(hqRoot, updated, manifest.turn_id, tick.emit.at, deps, logDecision);
1454
1521
  updated = await settleBlockerFreshness(hqRoot, updated, manifest.turn_id, tick.emit.at, deps, mesh, logDecision);
1522
+ updated = await settleKrFreshness(hqRoot, updated, manifest.turn_id, tick.emit.at, deps, logDecision);
1455
1523
  }
1456
1524
  const managerStatusFollowupQueued = manifest.turn_id !== undefined &&
1457
1525
  updated.status_gate_history?.some((entry) => entry.turn_id === manifest.turn_id &&
1458
1526
  ((entry.event === "status_missed" && entry.resume_queued === true) ||
1459
- (entry.event === "blocker_stale" && entry.resume_queued === true))) === true;
1527
+ (entry.event === "blocker_stale" && entry.resume_queued === true) ||
1528
+ (entry.event === "kr_stalled" && entry.resume_queued === true))) === true;
1460
1529
  if (terminalWrite.changed && deps.recoverQueuedMessages && !managerStatusFollowupQueued) {
1461
1530
  try {
1462
1531
  if (dropboxInboxHasMessages(hqRoot, laneId)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.264.0",
3
+ "version": "5.266.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {