@indigoai-us/hq-cli 5.293.0 → 5.295.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 +39 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
- package/dist/command-catalog.generated.d.ts +54 -33
- package/dist/command-catalog.generated.js +68 -42
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-command-catalog.js +3 -2
- package/dist/commands/core-delegate-w1b.d.ts +2 -0
- package/dist/commands/core-delegate-w1b.js +28 -2
- package/dist/commands/core-delegate.d.ts +3 -1
- package/dist/commands/core-delegate.js +35 -5
- package/dist/commands/core-handoff-fast.d.ts +18 -0
- package/dist/commands/core-handoff-fast.js +103 -0
- package/dist/commands/core-handoff-open-steps.d.ts +21 -0
- package/dist/commands/core-handoff-open-steps.js +264 -0
- package/dist/commands/core-handoff.d.ts +4 -2
- package/dist/commands/core-handoff.js +22 -249
- package/dist/commands/core-jobs-native.d.ts +2 -0
- package/dist/commands/core-jobs-native.js +22 -11
- package/dist/commands/core-knowledge-prefs.d.ts +2 -0
- package/dist/commands/core-knowledge-prefs.js +13 -1
- package/dist/commands/core-ontology-native.d.ts +2 -0
- package/dist/commands/core-ontology-native.js +22 -2
- package/dist/commands/core-policy.d.ts +2 -0
- package/dist/commands/core-policy.js +18 -1
- package/dist/commands/core.js +2 -1
- package/dist/commands/daemon.d.ts +2 -1
- package/dist/commands/daemon.js +22 -7
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-autopilot.d.ts +11 -0
- package/dist/commands/lanes-autopilot.js +336 -0
- package/dist/commands/lanes-message.js +23 -5
- package/dist/commands/lanes-questions.js +8 -5
- package/dist/commands/lanes-resume.js +0 -3
- package/dist/commands/lanes-shared.d.ts +18 -2
- package/dist/commands/lanes-stories.d.ts +14 -0
- package/dist/commands/lanes-stories.js +308 -0
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +216 -481
- package/dist/fast-core.js +53 -25
- package/dist/fast-scaffold.d.ts +15 -7
- package/dist/fast-scaffold.js +167 -17
- 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/core-utils/worktree-resume.d.ts +2 -0
- package/dist/lib/core-utils/worktree-resume.js +16 -0
- package/dist/lib/daemon/migrate.d.ts +8 -1
- package/dist/lib/daemon/migrate.js +19 -7
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
- package/dist/lib/lanes/autopilot-metrics.js +301 -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/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 +44 -38
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +18 -5
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +54 -3
- package/dist/lib/lanes/types.d.ts +18 -1
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +102 -34
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/native-fast-route.d.ts +3 -1
- package/dist/native-fast-route.js +10 -8
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/fast-stream-errors.d.ts +12 -0
- package/dist/utils/fast-stream-errors.js +18 -0
- package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
- package/dist/utils/scaffold-fast-import-guard.js +6 -3
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +1 -1
- 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
|
@@ -37,7 +37,7 @@ 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
39
|
import { laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
40
|
-
import { appendCappedHistory, readLane, retryUpdateLane, withLaneHireLocks } from "./store.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",
|
|
@@ -659,26 +662,9 @@ export function missingExpectedEnvelopeFile(hqRoot, laneId, envelopePath) {
|
|
|
659
662
|
};
|
|
660
663
|
}
|
|
661
664
|
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
665
|
let target;
|
|
681
666
|
try {
|
|
667
|
+
resolveLaneCompany(hqRoot, lane);
|
|
682
668
|
target = resolveManagerStatusTarget(hqRoot, lane);
|
|
683
669
|
}
|
|
684
670
|
catch (err) {
|
|
@@ -725,8 +711,8 @@ async function settleManagerStatusGate(hqRoot, lane, manifestTurnId, at, deps, l
|
|
|
725
711
|
turn_id: gate.turn_id,
|
|
726
712
|
path: gate.status_path,
|
|
727
713
|
consecutive_misses: updated.status_gate?.consecutive_misses ?? 0,
|
|
728
|
-
resume_queued: miss.resume_queued === true,
|
|
729
|
-
resume_skipped_reason: miss.resume_skipped_reason,
|
|
714
|
+
resume_queued: "resume_queued" in miss && miss.resume_queued === true,
|
|
715
|
+
resume_skipped_reason: "resume_skipped_reason" in miss ? miss.resume_skipped_reason : undefined,
|
|
730
716
|
state: updated.state,
|
|
731
717
|
});
|
|
732
718
|
}
|
|
@@ -1058,6 +1044,92 @@ function claimWatcherLoop(hqRoot, laneId, deps) {
|
|
|
1058
1044
|
});
|
|
1059
1045
|
};
|
|
1060
1046
|
}
|
|
1047
|
+
async function maybeContinueAfterMissingEnvelopeRepair(hqRoot, lane, envelope, envelopeRef, at, deps, logDecision) {
|
|
1048
|
+
const repair = lane.envelope_repair;
|
|
1049
|
+
const missingRepair = repair?.attempted === true &&
|
|
1050
|
+
repair.succeeded === true &&
|
|
1051
|
+
repair.envelope_ref === envelopeRef &&
|
|
1052
|
+
repair.first_error.startsWith("Missing expected envelope file:");
|
|
1053
|
+
if (!missingRepair || envelope === null || envelopeRef === undefined) {
|
|
1054
|
+
if (envelope?.decision === "blocked")
|
|
1055
|
+
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 });
|
|
1056
|
+
return lane;
|
|
1057
|
+
}
|
|
1058
|
+
const incompleteDone = envelope.decision === "done" && envelope.story_complete === false;
|
|
1059
|
+
const repairInstructionBlock = envelope.decision === "blocked" &&
|
|
1060
|
+
(envelope.questions?.length ?? 0) === 0 &&
|
|
1061
|
+
/(?:not to resume|do not resume|don't resume)(?: the)? task/i.test(envelope.blocked_reason ?? "");
|
|
1062
|
+
if (!incompleteDone && !repairInstructionBlock) {
|
|
1063
|
+
if (envelope.decision === "blocked")
|
|
1064
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "blocked_reason_not_repair_instruction", blocked_reason: envelope.blocked_reason });
|
|
1065
|
+
return lane;
|
|
1066
|
+
}
|
|
1067
|
+
if (lane.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
|
|
1068
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "continue_already_used" });
|
|
1069
|
+
return lane;
|
|
1070
|
+
}
|
|
1071
|
+
let pendingQuestion = false;
|
|
1072
|
+
try {
|
|
1073
|
+
for (const id of listQuestionIds(hqRoot)) {
|
|
1074
|
+
const question = readQuestion(hqRoot, id);
|
|
1075
|
+
if (question.lane_id === lane.lane_id && question.status === "pending") {
|
|
1076
|
+
pendingQuestion = true;
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
catch (error) {
|
|
1082
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "question_check_failed", error: error instanceof Error ? error.message : String(error) });
|
|
1083
|
+
return lane;
|
|
1084
|
+
}
|
|
1085
|
+
if (pendingQuestion) {
|
|
1086
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "pending_question" });
|
|
1087
|
+
return lane;
|
|
1088
|
+
}
|
|
1089
|
+
const readFlag = deps.readEnvelopeRepairContinueFlag ?? readLanesRegistryFlag;
|
|
1090
|
+
let enabled = false;
|
|
1091
|
+
try {
|
|
1092
|
+
const resolution = resolveLaneCompany(hqRoot, lane);
|
|
1093
|
+
const company = resolution.company ?? undefined;
|
|
1094
|
+
enabled = await readFlag(LANE_ENVELOPE_REPAIR_CONTINUE_FLAG, {
|
|
1095
|
+
company,
|
|
1096
|
+
...(company ? { companyUidSource: laneCompanyUidSource(resolution.source) } : {}),
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "skipped", reason: "flag_read_failed", error: error instanceof Error ? error.message : String(error) });
|
|
1101
|
+
return lane;
|
|
1102
|
+
}
|
|
1103
|
+
if (!enabled)
|
|
1104
|
+
return lane;
|
|
1105
|
+
const queued = retryUpdateLane(hqRoot, lane.lane_id, "watcher", (current) => {
|
|
1106
|
+
if (current.version !== lane.version ||
|
|
1107
|
+
(current.state !== "done" && current.state !== "blocked") ||
|
|
1108
|
+
current.last_envelope?.ref !== envelopeRef ||
|
|
1109
|
+
current.envelope_repair?.attempted !== true ||
|
|
1110
|
+
current.envelope_repair.succeeded !== true ||
|
|
1111
|
+
current.envelope_repair.envelope_ref !== envelopeRef ||
|
|
1112
|
+
current.envelope_repair.first_error !== repair.first_error ||
|
|
1113
|
+
current.last_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT)
|
|
1114
|
+
return current;
|
|
1115
|
+
const result = queueLaneTerminalResume(current, at, ENVELOPE_REPAIR_CONTINUE_TEXT, { allowBlockedResume: true });
|
|
1116
|
+
if (!result.resumeQueued || result.lane.pending_resume?.session_id === undefined)
|
|
1117
|
+
return current;
|
|
1118
|
+
return {
|
|
1119
|
+
...result.lane,
|
|
1120
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1121
|
+
event: "envelope_repair_continued",
|
|
1122
|
+
at,
|
|
1123
|
+
envelope_ref: envelopeRef,
|
|
1124
|
+
session_id: result.lane.pending_resume.session_id,
|
|
1125
|
+
}),
|
|
1126
|
+
};
|
|
1127
|
+
});
|
|
1128
|
+
if (queued.state === "queued" && queued.pending_resume?.text === ENVELOPE_REPAIR_CONTINUE_TEXT) {
|
|
1129
|
+
logDecision({ event: "envelope_repair_continue", at, outcome: "queued", envelope_ref: envelopeRef, session_id: queued.pending_resume.session_id });
|
|
1130
|
+
}
|
|
1131
|
+
return queued;
|
|
1132
|
+
}
|
|
1061
1133
|
/** Detached and synchronous classifiers share one process-identity claim. */
|
|
1062
1134
|
export async function runWatcherLoop(hqRoot, laneId, deps = {}) {
|
|
1063
1135
|
const release = claimWatcherLoop(hqRoot, laneId, deps);
|
|
@@ -1144,17 +1216,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1144
1216
|
}
|
|
1145
1217
|
const progressIntervalMs = deps.progressEventMinIntervalMs ?? readProgressIntervalMs(hqRoot, readLaneSoft(hqRoot, laneId));
|
|
1146
1218
|
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
|
-
});
|
|
1219
|
+
const retryAccount = deps.retryAccount ?? ((root, lane, reason) => retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
|
|
1220
|
+
...deps.retrySpawnDeps,
|
|
1221
|
+
admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
|
|
1222
|
+
}));
|
|
1158
1223
|
let lastSignalMs = now();
|
|
1159
1224
|
let lastSeenMtime = 0;
|
|
1160
1225
|
let lastAlive = null;
|
|
@@ -1542,7 +1607,10 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1542
1607
|
((entry.event === "status_missed" && entry.resume_queued === true) ||
|
|
1543
1608
|
(entry.event === "blocker_stale" && entry.resume_queued === true) ||
|
|
1544
1609
|
(entry.event === "kr_stalled" && entry.resume_queued === true))) === true;
|
|
1545
|
-
if (terminalWrite.changed &&
|
|
1610
|
+
if (terminalWrite.changed && !managerStatusFollowupQueued) {
|
|
1611
|
+
updated = await maybeContinueAfterMissingEnvelopeRepair(hqRoot, updated, effectiveEnvelope ?? null, latest?.ref, tick.emit.at, deps, logDecision);
|
|
1612
|
+
}
|
|
1613
|
+
if (terminalWrite.changed && deps.recoverQueuedMessages && !managerStatusFollowupQueued && updated.state !== "queued") {
|
|
1546
1614
|
try {
|
|
1547
1615
|
if (dropboxInboxHasMessages(hqRoot, laneId)) {
|
|
1548
1616
|
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,7 @@
|
|
|
1
1
|
/** Default-off flag check for native entrypoint routes. */
|
|
2
2
|
export declare function nativeFastRouteEnabled(argv: readonly string[], env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
3
3
|
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
4
|
-
export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv
|
|
4
|
+
export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
|
|
5
|
+
numericEnvironmentOverride?: boolean;
|
|
6
|
+
}): Promise<boolean>;
|
|
5
7
|
//# sourceMappingURL=native-fast-route.d.ts.map
|
|
@@ -8,8 +8,8 @@ export async function nativeFastRouteEnabled(argv, env = process.env) {
|
|
|
8
8
|
return fastFlagEnabled(parsed.entry.flagKey, env);
|
|
9
9
|
}
|
|
10
10
|
/** Reads a default-off fast-route gate from its local override or fresh snapshot. */
|
|
11
|
-
export async function fastFlagEnabled(flagKey, env = process.env) {
|
|
12
|
-
const override = nativeRouteOverride(flagKey, env);
|
|
11
|
+
export async function fastFlagEnabled(flagKey, env = process.env, options = {}) {
|
|
12
|
+
const override = nativeRouteOverride(flagKey, env, options.numericEnvironmentOverride !== false);
|
|
13
13
|
if (override !== null)
|
|
14
14
|
return override;
|
|
15
15
|
try {
|
|
@@ -29,15 +29,17 @@ export async function fastFlagEnabled(flagKey, env = process.env) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
/** The route's documented CLI override also accepts the conventional 1/0. */
|
|
32
|
-
function nativeRouteOverride(flagKey, env) {
|
|
32
|
+
function nativeRouteOverride(flagKey, env, numericEnvironmentOverride) {
|
|
33
33
|
const override = readLocalOverride(flagKey, env);
|
|
34
34
|
if (override !== null)
|
|
35
35
|
return override;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
if (numericEnvironmentOverride) {
|
|
37
|
+
const raw = env[localOverrideEnvVar(flagKey)];
|
|
38
|
+
if (raw === "1")
|
|
39
|
+
return true;
|
|
40
|
+
if (raw === "0")
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
41
43
|
return null;
|
|
42
44
|
}
|
|
43
45
|
//# sourceMappingURL=native-fast-route.js.map
|
|
@@ -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.
|
|
@@ -99,5 +99,5 @@ export declare const CONTRIBUTION_KEYS: ContributionKey[];
|
|
|
99
99
|
*/
|
|
100
100
|
export declare function payloadFor(key: ContributionKey, item: string): string;
|
|
101
101
|
/** Keys whose contributions are wired by a host symlink (skip `merge` rows). */
|
|
102
|
-
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "
|
|
102
|
+
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "commands" | "skills" | "knowledge" | "hooks" | "policies" | "scripts")[];
|
|
103
103
|
//# sourceMappingURL=contribution-table.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type ErrorEventStream = {
|
|
2
|
+
on: (event: "error", listener: (err: unknown) => void) => unknown;
|
|
3
|
+
};
|
|
4
|
+
export type FastStreamErrorDependencies = {
|
|
5
|
+
stdout: ErrorEventStream;
|
|
6
|
+
stderr: ErrorEventStream;
|
|
7
|
+
exit: (code: number) => void;
|
|
8
|
+
};
|
|
9
|
+
/** Match the full CLI's clean handling when a pipe consumer closes early. */
|
|
10
|
+
export declare function installFastPathStreamErrorHandlers(deps?: FastStreamErrorDependencies): void;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=fast-stream-errors.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isEpipe } from "./epipe.js";
|
|
2
|
+
/** Match the full CLI's clean handling when a pipe consumer closes early. */
|
|
3
|
+
export function installFastPathStreamErrorHandlers(deps = {
|
|
4
|
+
stdout: process.stdout,
|
|
5
|
+
stderr: process.stderr,
|
|
6
|
+
exit: (code) => process.exit(code),
|
|
7
|
+
}) {
|
|
8
|
+
const handle = (err) => {
|
|
9
|
+
if (isEpipe(err)) {
|
|
10
|
+
deps.exit(0);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
throw err;
|
|
14
|
+
};
|
|
15
|
+
deps.stdout.on("error", handle);
|
|
16
|
+
deps.stderr.on("error", handle);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=fast-stream-errors.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare function forbiddenFastPathModules(loadedModules: readonly string[], distDirectory: string): string[];
|
|
2
|
-
export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string): void;
|
|
1
|
+
export declare function forbiddenFastPathModules(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): string[];
|
|
2
|
+
export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): void;
|
|
3
3
|
//# sourceMappingURL=scaffold-fast-import-guard.d.ts.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
export function forbiddenFastPathModules(loadedModules, distDirectory) {
|
|
3
|
+
export function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = []) {
|
|
4
4
|
const dist = path.resolve(distDirectory);
|
|
5
5
|
const main = path.join(dist, "main.js");
|
|
6
6
|
const commands = `${path.join(dist, "commands")}${path.sep}`;
|
|
7
7
|
const flagRegistry = path.join(dist, "lib", "flag-registry.js");
|
|
8
8
|
const coreCommand = path.join(dist, "commands", "core.js");
|
|
9
|
+
const allowedCommands = new Set(allowedCommandModules.map((module) => path.resolve(module)));
|
|
9
10
|
return [...new Set(loadedModules.map((loaded) => {
|
|
10
11
|
if (loaded.startsWith("file:")) {
|
|
11
12
|
try {
|
|
@@ -22,10 +23,12 @@ export function forbiddenFastPathModules(loadedModules, distDirectory) {
|
|
|
22
23
|
path.resolve(loaded) === flagRegistry ||
|
|
23
24
|
path.resolve(loaded) === coreCommand ||
|
|
24
25
|
path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}node${path.sep}`))
|
|
26
|
+
.filter((loaded) => !allowedCommands.has(path.resolve(loaded)) ||
|
|
27
|
+
path.resolve(loaded) === main || path.resolve(loaded) === coreCommand)
|
|
25
28
|
.sort();
|
|
26
29
|
}
|
|
27
|
-
export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory) {
|
|
28
|
-
const forbidden = forbiddenFastPathModules(loadedModules, distDirectory);
|
|
30
|
+
export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory, allowedCommandModules = []) {
|
|
31
|
+
const forbidden = forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules);
|
|
29
32
|
if (forbidden.length > 0) {
|
|
30
33
|
throw new Error(`fast core loaded forbidden command modules:\n${forbidden.join("\n")}`);
|
|
31
34
|
}
|
|
@@ -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,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
|