@indigoai-us/hq-cli 5.294.0 → 5.296.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 +33 -0
- package/dist/command-catalog.generated.d.ts +70 -9
- package/dist/command-catalog.generated.js +88 -12
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-job-native.d.ts +7 -0
- package/dist/commands/core-job-native.js +20 -0
- package/dist/commands/core-job-probe-native.d.ts +45 -0
- package/dist/commands/core-job-probe-native.js +564 -0
- package/dist/commands/core-job-remote-launcher.d.ts +7 -0
- package/dist/commands/core-job-remote-launcher.js +51 -0
- package/dist/commands/core-job-run-native.d.ts +47 -0
- package/dist/commands/core-job-run-native.js +1215 -0
- package/dist/commands/core.js +1 -1
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-message.js +24 -5
- package/dist/commands/lanes-resume.js +1 -3
- package/dist/commands/lanes-shared.d.ts +1 -2
- package/dist/commands/lanes-stories.d.ts +1 -0
- package/dist/commands/lanes-stories.js +40 -1
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +197 -174
- package/dist/commands/run.js +5 -0
- package/dist/fast-scaffold.d.ts +0 -5
- package/dist/fast-scaffold.js +0 -10
- package/dist/index.js +1 -10
- package/dist/lib/core-utils/sentry-report.d.ts +0 -19
- package/dist/lib/core-utils/sentry-report.js +8 -61
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
- package/dist/lib/lanes/manager-status-gate.js +2 -43
- package/dist/lib/lanes/mesh-events.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
- package/dist/lib/lanes/outpost-dispatch.js +7 -1
- package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
- package/dist/lib/lanes/outpost-hire-cache.js +0 -1
- package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
- package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
- package/dist/lib/lanes/reconcile.d.ts +2 -3
- package/dist/lib/lanes/reconcile.js +67 -57
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +57 -5
- package/dist/lib/lanes/store.js +54 -0
- package/dist/lib/lanes/stories.d.ts +7 -1
- package/dist/lib/lanes/stories.js +16 -0
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +122 -56
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/run/env-graph-guard.d.ts +2 -2
- package/dist/run/env-graph-guard.js +5 -2
- package/dist/run/hq-plugin.js +6 -5
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +2 -2
- package/dist/fast-lanes-list-route.d.ts +0 -8
- package/dist/fast-lanes-list-route.js +0 -32
- package/dist/fast-lanes-list.d.ts +0 -3
- package/dist/fast-lanes-list.js +0 -224
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
- package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
- package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
- package/dist/lib/doctor/client-health-sentry.js +0 -286
- package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
- package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
- package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
- package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
|
@@ -36,8 +36,8 @@ import * as path from "node:path";
|
|
|
36
36
|
import { LanesError } from "./errors.js";
|
|
37
37
|
import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
38
38
|
import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, LANE_PROVIDERS, } from "./types.js";
|
|
39
|
-
import { laneStories,
|
|
40
|
-
import { appendCappedHistory, readLane, retryUpdateLane, withLaneHireLocks } from "./store.js";
|
|
39
|
+
import { laneStories, terminalEnvelopeStoryStatus } from "./stories.js";
|
|
40
|
+
import { appendCappedHistory, listQuestionIds, readLane, readQuestion, retryUpdateLane, withLaneHireLocks } from "./store.js";
|
|
41
41
|
import { enforceRegistryApprovalGate } from "./approval-gate.js";
|
|
42
42
|
import { readWorkerRunResult } from "./delivery.js";
|
|
43
43
|
import { applyDefaultQuotaHold, quotaSignalFromFields, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
@@ -51,13 +51,15 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
|
51
51
|
import { laneCompanyUidSource, resolveLaneCompany } from "./lane-company.js";
|
|
52
52
|
import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "./blocker-freshness.js";
|
|
53
53
|
import { krFreshnessFingerprint, resolveKrFreshnessForLane, } from "./kr-freshness.js";
|
|
54
|
-
import {
|
|
54
|
+
import { managerStatusWasWritten, readStatusSnapshot, resolveManagerStatusTarget, } from "./manager-status-gate.js";
|
|
55
55
|
import { queueLaneTerminalResume, queueManagerStatusGateResume } from "./keep-alive.js";
|
|
56
|
+
import { LANE_ENVELOPE_REPAIR_CONTINUE_FLAG } from "./flag-keys.js";
|
|
56
57
|
import { monitorSessionDir } from "../monitor.js";
|
|
57
58
|
import { laneRunDir, missingPidProbe, probePidRecord, readPidRecord, readProcStarttime, readSpawnManifest, envelopeRepairMode, spawnEnvelopeRepair, writePidRecord, retryLaneOnAlternateAccount, } from "./spawn.js";
|
|
58
59
|
/** Spec §5.7: no signal for 20 minutes while the process is still alive. */
|
|
59
60
|
export const SILENCE_MS = 20 * 60 * 1000;
|
|
60
61
|
export const WATCHER_POLL_MS = 5_000;
|
|
62
|
+
export const ENVELOPE_REPAIR_CONTINUE_TEXT = "continue the task; the previous turn ended without an envelope.";
|
|
61
63
|
/** Synced last_host_report_at cadence. Reconcile dead-host TTL is 20 minutes. */
|
|
62
64
|
export const WATCHER_HEARTBEAT_MS = 60 * 1000;
|
|
63
65
|
/**
|
|
@@ -72,6 +74,7 @@ export const WATCHER_DECISION_EVENTS = [
|
|
|
72
74
|
"envelope_observed",
|
|
73
75
|
"envelope_invalid",
|
|
74
76
|
"envelope_repair",
|
|
77
|
+
"envelope_repair_continue",
|
|
75
78
|
"classification",
|
|
76
79
|
"terminal_write",
|
|
77
80
|
"message_recovery",
|
|
@@ -420,27 +423,25 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
420
423
|
artifacts: envelope.artifacts,
|
|
421
424
|
...(envelope.blocked_reason !== undefined ? { blocked_reason: envelope.blocked_reason } : {}),
|
|
422
425
|
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
?
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
: story);
|
|
443
|
-
next.stories = updatedStories;
|
|
426
|
+
const storyStatus = terminalEnvelopeStoryStatus(current, envelope.decision, envelope.story_status);
|
|
427
|
+
if (storyStatus !== undefined) {
|
|
428
|
+
next.story_status = envelope.story_status;
|
|
429
|
+
next.story_complete = envelope.story_complete;
|
|
430
|
+
next.story_complete_reason = envelope.story_complete_reason;
|
|
431
|
+
const currentProject = current.project_id;
|
|
432
|
+
const currentStory = current.story_id;
|
|
433
|
+
const updatedStories = laneStories(current).map((story) => story.project_id === currentProject && story.story_id === currentStory
|
|
434
|
+
? {
|
|
435
|
+
...story,
|
|
436
|
+
status: storyStatus,
|
|
437
|
+
updated_at: at,
|
|
438
|
+
...(envelope.decision === "blocked"
|
|
439
|
+
? { blocked_reason: envelope.blocked_reason ?? "" }
|
|
440
|
+
: { blocked_reason: undefined }),
|
|
441
|
+
}
|
|
442
|
+
: story);
|
|
443
|
+
next.stories = updatedStories;
|
|
444
|
+
}
|
|
444
445
|
}
|
|
445
446
|
return next;
|
|
446
447
|
});
|
|
@@ -659,26 +660,9 @@ export function missingExpectedEnvelopeFile(hqRoot, laneId, envelopePath) {
|
|
|
659
660
|
};
|
|
660
661
|
}
|
|
661
662
|
async function settleManagerStatusGate(hqRoot, lane, manifestTurnId, at, deps, logDecision) {
|
|
662
|
-
const readFlag = deps.readManagerStatusGateFlag ?? readLanesRegistryFlag;
|
|
663
|
-
let enabled = false;
|
|
664
|
-
try {
|
|
665
|
-
const resolution = resolveLaneCompany(hqRoot, lane);
|
|
666
|
-
const company = resolution.company ?? undefined;
|
|
667
|
-
enabled = await readFlag(MANAGER_STATUS_GATE_FLAG, {
|
|
668
|
-
company,
|
|
669
|
-
...(company ? {
|
|
670
|
-
companyUidSource: laneCompanyUidSource(resolution.source),
|
|
671
|
-
} : {}),
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
catch (err) {
|
|
675
|
-
process.stderr.write(`hq lanes watcher: manager status gate flag read failed for ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
676
|
-
logDecision({ event: "status_gate_flag_failed", at, error: err instanceof Error ? err.name : "UnknownError" });
|
|
677
|
-
}
|
|
678
|
-
if (!enabled)
|
|
679
|
-
return lane;
|
|
680
663
|
let target;
|
|
681
664
|
try {
|
|
665
|
+
resolveLaneCompany(hqRoot, lane);
|
|
682
666
|
target = resolveManagerStatusTarget(hqRoot, lane);
|
|
683
667
|
}
|
|
684
668
|
catch (err) {
|
|
@@ -725,8 +709,8 @@ async function settleManagerStatusGate(hqRoot, lane, manifestTurnId, at, deps, l
|
|
|
725
709
|
turn_id: gate.turn_id,
|
|
726
710
|
path: gate.status_path,
|
|
727
711
|
consecutive_misses: updated.status_gate?.consecutive_misses ?? 0,
|
|
728
|
-
resume_queued: miss.resume_queued === true,
|
|
729
|
-
resume_skipped_reason: miss.resume_skipped_reason,
|
|
712
|
+
resume_queued: "resume_queued" in miss && miss.resume_queued === true,
|
|
713
|
+
resume_skipped_reason: "resume_skipped_reason" in miss ? miss.resume_skipped_reason : undefined,
|
|
730
714
|
state: updated.state,
|
|
731
715
|
});
|
|
732
716
|
}
|
|
@@ -1058,6 +1042,92 @@ function claimWatcherLoop(hqRoot, laneId, deps) {
|
|
|
1058
1042
|
});
|
|
1059
1043
|
};
|
|
1060
1044
|
}
|
|
1045
|
+
async function maybeContinueAfterMissingEnvelopeRepair(hqRoot, lane, envelope, envelopeRef, at, deps, logDecision) {
|
|
1046
|
+
const repair = lane.envelope_repair;
|
|
1047
|
+
const missingRepair = repair?.attempted === true &&
|
|
1048
|
+
repair.succeeded === true &&
|
|
1049
|
+
repair.envelope_ref === envelopeRef &&
|
|
1050
|
+
repair.first_error.startsWith("Missing expected envelope file:");
|
|
1051
|
+
if (!missingRepair || envelope === null || envelopeRef === undefined) {
|
|
1052
|
+
if (envelope?.decision === "blocked")
|
|
1053
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "not_a_successful_missing_repair", missing_repair: missingRepair, envelope_ref: envelopeRef, repair_ref: repair?.envelope_ref, first_error: repair?.first_error, succeeded: repair?.succeeded });
|
|
1054
|
+
return lane;
|
|
1055
|
+
}
|
|
1056
|
+
const incompleteDone = envelope.decision === "done" && envelope.story_complete === false;
|
|
1057
|
+
const repairInstructionBlock = envelope.decision === "blocked" &&
|
|
1058
|
+
(envelope.questions?.length ?? 0) === 0 &&
|
|
1059
|
+
/(?:not to resume|do not resume|don't resume)(?: the)? task/i.test(envelope.blocked_reason ?? "");
|
|
1060
|
+
if (!incompleteDone && !repairInstructionBlock) {
|
|
1061
|
+
if (envelope.decision === "blocked")
|
|
1062
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "blocked_reason_not_repair_instruction", blocked_reason: envelope.blocked_reason });
|
|
1063
|
+
return lane;
|
|
1064
|
+
}
|
|
1065
|
+
if (lane.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
|
|
1066
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "continue_already_used" });
|
|
1067
|
+
return lane;
|
|
1068
|
+
}
|
|
1069
|
+
let pendingQuestion = false;
|
|
1070
|
+
try {
|
|
1071
|
+
for (const id of listQuestionIds(hqRoot)) {
|
|
1072
|
+
const question = readQuestion(hqRoot, id);
|
|
1073
|
+
if (question.lane_id === lane.lane_id && question.status === "pending") {
|
|
1074
|
+
pendingQuestion = true;
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
catch (error) {
|
|
1080
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "question_check_failed", error: error instanceof Error ? error.message : String(error) });
|
|
1081
|
+
return lane;
|
|
1082
|
+
}
|
|
1083
|
+
if (pendingQuestion) {
|
|
1084
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "pending_question" });
|
|
1085
|
+
return lane;
|
|
1086
|
+
}
|
|
1087
|
+
const readFlag = deps.readEnvelopeRepairContinueFlag ?? readLanesRegistryFlag;
|
|
1088
|
+
let enabled = false;
|
|
1089
|
+
try {
|
|
1090
|
+
const resolution = resolveLaneCompany(hqRoot, lane);
|
|
1091
|
+
const company = resolution.company ?? undefined;
|
|
1092
|
+
enabled = await readFlag(LANE_ENVELOPE_REPAIR_CONTINUE_FLAG, {
|
|
1093
|
+
company,
|
|
1094
|
+
...(company ? { companyUidSource: laneCompanyUidSource(resolution.source) } : {}),
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
catch (error) {
|
|
1098
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "flag_read_failed", error: error instanceof Error ? error.message : String(error) });
|
|
1099
|
+
return lane;
|
|
1100
|
+
}
|
|
1101
|
+
if (!enabled)
|
|
1102
|
+
return lane;
|
|
1103
|
+
const queued = retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => {
|
|
1104
|
+
if (current.version !== lane.version ||
|
|
1105
|
+
(current.state !== "done" && current.state !== "blocked") ||
|
|
1106
|
+
current.last_envelope?.ref !== envelopeRef ||
|
|
1107
|
+
current.envelope_repair?.attempted !== true ||
|
|
1108
|
+
current.envelope_repair.succeeded !== true ||
|
|
1109
|
+
current.envelope_repair.envelope_ref !== envelopeRef ||
|
|
1110
|
+
current.envelope_repair.first_error !== repair.first_error ||
|
|
1111
|
+
current.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT)
|
|
1112
|
+
return current;
|
|
1113
|
+
const result = queueLaneTerminalResume(current, at, ENVELOPE_REPAIR_CONTINUE_TEXT, { allowBlockedResume: true });
|
|
1114
|
+
if (!result.resumeQueued || result.lane.pending_resume?.session_id === undefined)
|
|
1115
|
+
return current;
|
|
1116
|
+
return {
|
|
1117
|
+
...result.lane,
|
|
1118
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1119
|
+
event: "envelope_repair_continued",
|
|
1120
|
+
at,
|
|
1121
|
+
envelope_ref: envelopeRef,
|
|
1122
|
+
session_id: result.lane.pending_resume.session_id,
|
|
1123
|
+
}),
|
|
1124
|
+
};
|
|
1125
|
+
});
|
|
1126
|
+
if (queued.state === "queued" && queued.pending_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
|
|
1127
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "queued", envelope_ref: envelopeRef, session_id: queued.pending_resume.session_id });
|
|
1128
|
+
}
|
|
1129
|
+
return queued;
|
|
1130
|
+
}
|
|
1061
1131
|
/** Detached and synchronous classifiers share one process-identity claim. */
|
|
1062
1132
|
export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
|
|
1063
1133
|
const release = claimWatcherLoop(hqRoot, laneId, deps);
|
|
@@ -1144,17 +1214,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1144
1214
|
}
|
|
1145
1215
|
const progressIntervalMs = deps.progressEventMinIntervalMs ?? readProgressIntervalMs(hqRoot, readLaneSoft(hqRoot, laneId));
|
|
1146
1216
|
const spawnRepair = deps.spawnRepair ?? spawnEnvelopeRepair;
|
|
1147
|
-
const retryAccount = deps.retryAccount ?? (
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
});
|
|
1152
|
-
return retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
|
|
1153
|
-
...deps.retrySpawnDeps,
|
|
1154
|
-
managerStatusGateEnabled,
|
|
1155
|
-
admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
|
|
1156
|
-
});
|
|
1157
|
-
});
|
|
1217
|
+
const retryAccount = deps.retryAccount ?? ((root, lane, reason) => retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
|
|
1218
|
+
...deps.retrySpawnDeps,
|
|
1219
|
+
admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
|
|
1220
|
+
}));
|
|
1158
1221
|
let lastSignalMs = now();
|
|
1159
1222
|
let lastSeenMtime = 0;
|
|
1160
1223
|
let lastAlive = null;
|
|
@@ -1542,7 +1605,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1542
1605
|
((entry.event === "status_missed" && entry.resume_queued === true) ||
|
|
1543
1606
|
(entry.event === "blocker_stale" && entry.resume_queued === true) ||
|
|
1544
1607
|
(entry.event === "kr_stalled" && entry.resume_queued === true))) === true;
|
|
1545
|
-
if (terminalWrite.changed &&
|
|
1608
|
+
if (terminalWrite.changed && !managerStatusFollowupQueued) {
|
|
1609
|
+
updated = await maybeContinueAfterMissingEnvelopeRepair(hqRoot, updated, effectiveEnvelope ?? null, latest?.ref, tick.emit.at, deps, logDecision);
|
|
1610
|
+
}
|
|
1611
|
+
if (terminalWrite.changed && deps.recoverQueuedMessages && !managerStatusFollowupQueued && updated.state !== "queued") {
|
|
1546
1612
|
try {
|
|
1547
1613
|
if (dropboxInboxHasMessages(hqRoot, laneId)) {
|
|
1548
1614
|
await deps.recoverQueuedMessages(updated);
|
|
@@ -701,13 +701,30 @@ export async function runMeshDaemon(deps = {}) {
|
|
|
701
701
|
if (!fs.existsSync(spoolPath)) {
|
|
702
702
|
fs.writeFileSync(spoolPath, "", { mode: 0o600 });
|
|
703
703
|
}
|
|
704
|
-
|
|
704
|
+
const spoolWatcher = fs.watch(path.dirname(spoolPath), { persistent: true }, (_event, filename) => {
|
|
705
705
|
if (stopped)
|
|
706
706
|
return;
|
|
707
707
|
if (shouldFlushOnSpoolDirEvent(filename)) {
|
|
708
708
|
scheduleDebouncedFlush();
|
|
709
709
|
}
|
|
710
710
|
});
|
|
711
|
+
watcher = spoolWatcher;
|
|
712
|
+
spoolWatcher.on("error", (err) => {
|
|
713
|
+
// fs.watch can fail asynchronously after returning a watcher. Without
|
|
714
|
+
// an error listener Node treats EMFILE as an uncaught exception; the
|
|
715
|
+
// periodic flush below already provides a polling fallback.
|
|
716
|
+
if (watcher === spoolWatcher)
|
|
717
|
+
watcher = null;
|
|
718
|
+
try {
|
|
719
|
+
spoolWatcher.close();
|
|
720
|
+
}
|
|
721
|
+
catch (closeError) {
|
|
722
|
+
const closeMessage = closeError instanceof Error ? closeError.message : String(closeError);
|
|
723
|
+
log(dir, `fs.watch close failed after watcher error: ${closeMessage.slice(0, 120)}`);
|
|
724
|
+
}
|
|
725
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
726
|
+
log(dir, `fs.watch error; continuing interval flush: ${msg.slice(0, 120)}`);
|
|
727
|
+
});
|
|
711
728
|
}
|
|
712
729
|
catch (err) {
|
|
713
730
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The varlock version hq-cli is pinned to (package.json `dependencies`). */
|
|
2
|
-
export declare const VARLOCK_PINNED_VERSION = "1.
|
|
2
|
+
export declare const VARLOCK_PINNED_VERSION = "1.21.1";
|
|
3
3
|
/** A schema/plugin source varlock could not finish loading. */
|
|
4
4
|
interface GuardDataSource {
|
|
5
5
|
readonly isValid: boolean;
|
|
@@ -68,7 +68,7 @@ export declare function assertEnvGraphLoaded(graph: LoadedEnvGraph): void;
|
|
|
68
68
|
* rejection into a thrown EnvResolutionError instead of hanging on the promise
|
|
69
69
|
* that never settles.
|
|
70
70
|
*
|
|
71
|
-
* varlock
|
|
71
|
+
* varlock's resolveEnvValues dispatches its per-item resolver un-awaited
|
|
72
72
|
* and with a dead `_reject`, so a throw inside it becomes a process-level
|
|
73
73
|
* unhandled rejection AND leaves the returned promise permanently pending. We
|
|
74
74
|
* race the resolve against a scoped `unhandledRejection` listener: whichever
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// actionable `hq: …` line WITHOUT a Sentry capture (see isVarlockEnvError).
|
|
35
35
|
import { claimRejection, releaseRejection, } from "../unhandled-rejection-boundary.js";
|
|
36
36
|
/** The varlock version hq-cli is pinned to (package.json `dependencies`). */
|
|
37
|
-
export const VARLOCK_PINNED_VERSION = "1.
|
|
37
|
+
export const VARLOCK_PINNED_VERSION = "1.21.1";
|
|
38
38
|
/**
|
|
39
39
|
* A schema/entry file could not be loaded, so the env graph never finished
|
|
40
40
|
* processing. Carries an actionable, file-naming message; classified by
|
|
@@ -114,7 +114,7 @@ export function assertEnvGraphLoaded(graph) {
|
|
|
114
114
|
* rejection into a thrown EnvResolutionError instead of hanging on the promise
|
|
115
115
|
* that never settles.
|
|
116
116
|
*
|
|
117
|
-
* varlock
|
|
117
|
+
* varlock's resolveEnvValues dispatches its per-item resolver un-awaited
|
|
118
118
|
* and with a dead `_reject`, so a throw inside it becomes a process-level
|
|
119
119
|
* unhandled rejection AND leaves the returned promise permanently pending. We
|
|
120
120
|
* race the resolve against a scoped `unhandledRejection` listener: whichever
|
|
@@ -143,6 +143,9 @@ export async function resolveEnvValuesOrThrow(graph) {
|
|
|
143
143
|
});
|
|
144
144
|
process.prependOnceListener("unhandledRejection", onRejection);
|
|
145
145
|
try {
|
|
146
|
+
// Preserve current varlock's direct rejection unchanged. The race only
|
|
147
|
+
// turns a floated rejection (the 1.0.0 behavior) into a typed error rather
|
|
148
|
+
// than leaving the caller waiting on a promise that never settles.
|
|
146
149
|
await Promise.race([graph.resolveEnvValues(), rejection]);
|
|
147
150
|
}
|
|
148
151
|
finally {
|
package/dist/run/hq-plugin.js
CHANGED
|
@@ -23,8 +23,9 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
|
23
23
|
errorsByName: new Map(),
|
|
24
24
|
loadedSecretsByName: new Map(),
|
|
25
25
|
};
|
|
26
|
-
// varlock@1.
|
|
27
|
-
//
|
|
26
|
+
// In varlock@1.21.1, Resolver remains declared in plugin-lib's .d.mts but
|
|
27
|
+
// is not exported from its runtime module. Derive the runtime base class from
|
|
28
|
+
// a built-in resolver registered on the graph; the shape test pins this API.
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
30
|
let RuntimeResolver;
|
|
30
31
|
try {
|
|
@@ -39,8 +40,8 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
|
39
40
|
RuntimeResolver = proto;
|
|
40
41
|
}
|
|
41
42
|
catch (e) {
|
|
42
|
-
throw new Error('varlock Resolver base class extraction failed —
|
|
43
|
-
'
|
|
43
|
+
throw new Error('varlock Resolver base class extraction failed — varlock@1.21.1 still declares ' +
|
|
44
|
+
'Resolver in plugin-lib types without exporting it at runtime. ' +
|
|
44
45
|
`Underlying: ${e instanceof Error ? e.message : String(e)}`);
|
|
45
46
|
}
|
|
46
47
|
// HqResolver is declared INSIDE installHqPlugin so its static def.resolve
|
|
@@ -96,7 +97,7 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
//
|
|
100
|
+
// Varlock's env-spec parser rejects dots in decorator names ([a-zA-Z0-9_] only),
|
|
100
101
|
// so `@hq.company` is not valid syntax. We register as `@hqCompany` (camelCase) instead.
|
|
101
102
|
// Schema files must use `# @hqCompany("slug")` followed by a blank line so the parser
|
|
102
103
|
// treats it as a file-level root decorator rather than an item decorator for the next var.
|
|
@@ -237,6 +237,12 @@ export declare function withMachineTokenStateDir<T>(fn: () => Promise<T>, opts?:
|
|
|
237
237
|
home?: string;
|
|
238
238
|
env?: NodeJS.ProcessEnv;
|
|
239
239
|
}): Promise<T>;
|
|
240
|
+
/**
|
|
241
|
+
* Ensure the current machine identity has a valid session and return only the
|
|
242
|
+
* access/ID token pair needed by supported machine-session handoff scripts.
|
|
243
|
+
* The machine state directory is isolated from the person's session cache.
|
|
244
|
+
*/
|
|
245
|
+
export declare function ensureMachineSessionTokens(): Promise<Pick<CognitoTokens, "accessToken" | "idToken" | "expiresAt">>;
|
|
240
246
|
/**
|
|
241
247
|
* True when a person-token refresh failed with a definitive Cognito auth
|
|
242
248
|
* rejection (refresh token invalid/revoked). Transient network/5xx/timeouts
|
|
@@ -840,6 +840,15 @@ function withTokenStateDirSection(fn) {
|
|
|
840
840
|
async function ensureMachineTokens() {
|
|
841
841
|
return withMachineTokenStateDir(() => getValidMachineTokens(DEFAULT_COGNITO));
|
|
842
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Ensure the current machine identity has a valid session and return only the
|
|
845
|
+
* access/ID token pair needed by supported machine-session handoff scripts.
|
|
846
|
+
* The machine state directory is isolated from the person's session cache.
|
|
847
|
+
*/
|
|
848
|
+
export async function ensureMachineSessionTokens() {
|
|
849
|
+
const tokens = await ensureMachineTokensMaybeFallback({ tokenSource: "machine" });
|
|
850
|
+
return { accessToken: tokens.accessToken, idToken: tokens.idToken, expiresAt: tokens.expiresAt };
|
|
851
|
+
}
|
|
843
852
|
/**
|
|
844
853
|
* Mint machine tokens (daemon state dir cache). Logs once when the fresh-box
|
|
845
854
|
* default-creds fallback is what selected the machine path.
|
|
@@ -62,6 +62,8 @@ import * as fs from "node:fs";
|
|
|
62
62
|
import * as path from "node:path";
|
|
63
63
|
import semver from "semver";
|
|
64
64
|
import chalk from "chalk";
|
|
65
|
+
import { Sentry } from "../sentry.js";
|
|
66
|
+
import { isCliTelemetryDisabled } from "./cli-telemetry.js";
|
|
65
67
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
66
68
|
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, isPrefixWritable, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, } from "./version-gate.js";
|
|
67
69
|
import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
|
|
@@ -499,6 +501,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
499
501
|
console.error(chalk.dim(` Or repair the install: hq rescue`));
|
|
500
502
|
}
|
|
501
503
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
504
|
+
await captureSelfUpdateOutcome("update-failed", current, latestValid, install.manager, result.code, env, flavor.noun === "rescue");
|
|
502
505
|
return { action: "update-failed", latest };
|
|
503
506
|
}
|
|
504
507
|
if (installSnapshot)
|
|
@@ -521,6 +524,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
521
524
|
if (converged === false) {
|
|
522
525
|
(deps.markIneffective ?? markLatestIneffective)(latest);
|
|
523
526
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
527
|
+
await captureSelfUpdateOutcome("update-ineffective", current, latestValid, install.manager, undefined, env, flavor.noun === "rescue");
|
|
524
528
|
return { action: "update-ineffective", latest };
|
|
525
529
|
}
|
|
526
530
|
// A resident mesh daemon keeps the old JS loaded after a global install.
|
|
@@ -550,6 +554,33 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
550
554
|
console.error(chalk.dim(`✓ hq-cli updated to ${latest}.`));
|
|
551
555
|
return { action: "reexec", reexecStatus: status, latest };
|
|
552
556
|
}
|
|
557
|
+
const SELF_UPDATE_ERROR_CLASSES = new Set(["MODULE_NOT_FOUND", "EACCES", "EEXIST"]);
|
|
558
|
+
async function captureSelfUpdateOutcome(action, fromVersion, targetVersion, installFlavor, code, env, flushBeforeExit) {
|
|
559
|
+
if (isCliTelemetryDisabled(env))
|
|
560
|
+
return;
|
|
561
|
+
const error = new Error(action === "update-failed" ? "Self-update failed" : "Self-update ineffective");
|
|
562
|
+
// This event needs no stack: frames would expose the machine's install path.
|
|
563
|
+
error.stack = undefined;
|
|
564
|
+
Sentry.captureException(error, {
|
|
565
|
+
fingerprint: ["self-update", action],
|
|
566
|
+
tags: {
|
|
567
|
+
from_version: fromVersion,
|
|
568
|
+
target_version: targetVersion,
|
|
569
|
+
install_flavor: installFlavor,
|
|
570
|
+
error_class: code && SELF_UPDATE_ERROR_CLASSES.has(code) ? code : "other",
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
if (flushBeforeExit) {
|
|
574
|
+
try {
|
|
575
|
+
if (!(await Sentry.flush(500))) {
|
|
576
|
+
console.error(chalk.dim("hq: Sentry flush timed out after self-update."));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
console.error(chalk.dim("hq: Sentry flush failed after self-update."));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
553
584
|
async function readStagedSelfUpdateFlag(deps, env) {
|
|
554
585
|
try {
|
|
555
586
|
if (deps.stagedSelfUpdate)
|
|
@@ -706,13 +706,32 @@ export function detachUpdateSupervisor() {
|
|
|
706
706
|
try {
|
|
707
707
|
rmSync(tempDir, { recursive: true, force: true });
|
|
708
708
|
}
|
|
709
|
-
catch {
|
|
710
|
-
|
|
709
|
+
catch (error) {
|
|
710
|
+
const failureClass = error &&
|
|
711
|
+
typeof error === "object" &&
|
|
712
|
+
"code" in error &&
|
|
713
|
+
typeof error.code === "string" &&
|
|
714
|
+
/^[A-Z0-9_]{1,32}$/.test(error.code)
|
|
715
|
+
? error.code
|
|
716
|
+
: error instanceof Error
|
|
717
|
+
? "Error"
|
|
718
|
+
: "UnknownError";
|
|
719
|
+
console.error(chalk.yellow(`Could not remove update supervisor temp dir (${failureClass}).`));
|
|
711
720
|
}
|
|
712
721
|
},
|
|
713
722
|
};
|
|
714
723
|
}
|
|
715
|
-
catch {
|
|
724
|
+
catch (error) {
|
|
725
|
+
const failureClass = error &&
|
|
726
|
+
typeof error === "object" &&
|
|
727
|
+
"code" in error &&
|
|
728
|
+
typeof error.code === "string" &&
|
|
729
|
+
/^[A-Z0-9_]{1,32}$/.test(error.code)
|
|
730
|
+
? error.code
|
|
731
|
+
: error instanceof Error
|
|
732
|
+
? "Error"
|
|
733
|
+
: "UnknownError";
|
|
734
|
+
console.error(chalk.yellow(`Could not copy update supervisor (${failureClass}).`));
|
|
716
735
|
if (dir)
|
|
717
736
|
rmSync(dir, { recursive: true, force: true });
|
|
718
737
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.296.0",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"semver": "^7.6.3",
|
|
59
59
|
"simple-git": "^3.27.0",
|
|
60
60
|
"smol-toml": "1.6.1",
|
|
61
|
-
"varlock": "1.
|
|
61
|
+
"varlock": "1.21.1"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "^10.0.1",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface FastLanesListRouteDeps {
|
|
2
|
-
flagEnabled: (flagKey: string, env: NodeJS.ProcessEnv) => Promise<boolean>;
|
|
3
|
-
runFastList: () => Promise<void>;
|
|
4
|
-
runPlacementList: () => Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
/** Returns false unless this exact list form is safely enabled in the local flag snapshot. */
|
|
7
|
-
export declare function runFastLanesListRequest(argv: readonly string[], env?: NodeJS.ProcessEnv, deps?: FastLanesListRouteDeps): Promise<boolean>;
|
|
8
|
-
//# sourceMappingURL=fast-lanes-list-route.d.ts.map
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { isFastCachedLanesListRequest } from "./fast-scaffold.js";
|
|
2
|
-
import { fastFlagEnabled } from "./native-fast-route.js";
|
|
3
|
-
import { registerOutpostLaneSnapshotRefreshChild } from "./lib/lanes/outpost-snapshot-refresh.js";
|
|
4
|
-
import { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG } from "./lib/lanes/outpost-hire-cache.js";
|
|
5
|
-
import { LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lib/lanes/flag-keys.js";
|
|
6
|
-
/** Returns false unless this exact list form is safely enabled in the local flag snapshot. */
|
|
7
|
-
export async function runFastLanesListRequest(argv, env = process.env, deps = {
|
|
8
|
-
flagEnabled: fastFlagEnabled,
|
|
9
|
-
runFastList: async () => {
|
|
10
|
-
const { runFastCachedLanesList } = await import("./fast-lanes-list.js");
|
|
11
|
-
await runFastCachedLanesList();
|
|
12
|
-
},
|
|
13
|
-
runPlacementList: async () => {
|
|
14
|
-
const { runFastCachedLanesListFromEntrypoint } = await import("./commands/lanes.js");
|
|
15
|
-
await runFastCachedLanesListFromEntrypoint();
|
|
16
|
-
},
|
|
17
|
-
}) {
|
|
18
|
-
registerOutpostLaneSnapshotRefreshChild(env);
|
|
19
|
-
if (!isFastCachedLanesListRequest(argv))
|
|
20
|
-
return false;
|
|
21
|
-
if (env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1")
|
|
22
|
-
return false;
|
|
23
|
-
if (!await deps.flagEnabled(LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, env))
|
|
24
|
-
return false;
|
|
25
|
-
const lanePlacementEnabled = await deps.flagEnabled(LANE_OUTPOST_LOAD_BALANCING_FLAG, env);
|
|
26
|
-
if (lanePlacementEnabled)
|
|
27
|
-
await deps.runPlacementList();
|
|
28
|
-
else
|
|
29
|
-
await deps.runFastList();
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=fast-lanes-list-route.js.map
|