@indigoai-us/hq-cli 5.292.0 → 5.294.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 +40 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
- package/dist/command-catalog.generated.d.ts +45 -24
- package/dist/command-catalog.generated.js +57 -30
- 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/lanes-autopilot.d.ts +11 -0
- package/dist/commands/lanes-autopilot.js +336 -0
- package/dist/commands/lanes-move.js +54 -1
- package/dist/commands/lanes-questions.js +12 -5
- package/dist/commands/lanes-shared.d.ts +17 -0
- package/dist/commands/lanes-stories.d.ts +14 -0
- package/dist/commands/lanes-stories.js +308 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +158 -314
- package/dist/fast-core.js +53 -25
- package/dist/fast-scaffold.d.ts +15 -2
- package/dist/fast-scaffold.js +167 -7
- package/dist/lib/core-utils/sentry-report.js +2 -2
- 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/daemon/run.d.ts +11 -0
- package/dist/lib/daemon/run.js +17 -1
- package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
- package/dist/lib/lanes/autopilot-metrics.js +301 -0
- package/dist/lib/lanes/autopilot.d.ts +13 -1
- package/dist/lib/lanes/autopilot.js +271 -28
- package/dist/lib/lanes/grants.d.ts +1 -1
- package/dist/lib/lanes/grants.js +7 -5
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +5 -0
- package/dist/lib/lanes/keep-alive.js +9 -4
- package/dist/lib/lanes/questions.d.ts +2 -1
- package/dist/lib/lanes/questions.js +2 -0
- package/dist/lib/lanes/reconcile.js +186 -9
- package/dist/lib/lanes/spawn.js +4 -0
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +53 -4
- package/dist/lib/lanes/types.d.ts +13 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -1
- package/dist/native-fast-route.d.ts +3 -1
- package/dist/native-fast-route.js +10 -8
- 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.d.ts +7 -0
- package/dist/utils/self-update.js +5 -1
- package/dist/utils/version-gate.d.ts +15 -0
- package/dist/utils/version-gate.js +46 -2
- package/package.json +1 -1
|
@@ -19,19 +19,19 @@ import { loadWorkerProfile } from "./profile.js";
|
|
|
19
19
|
import { terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
|
|
20
20
|
import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, unindexedClaudeSessionLiveness, } from "./liveness.js";
|
|
21
21
|
import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, readPidRecord, readProcStarttime, readSpawnManifest, markSpawnFailure, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
|
|
22
|
-
import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
|
|
22
|
+
import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, listQuestionIds, updateLane, updateQuestion, } from "./store.js";
|
|
23
23
|
import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
|
|
24
24
|
import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
25
25
|
import { resolveLaneCompany } from "./lane-company.js";
|
|
26
26
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
27
27
|
import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
|
|
28
|
-
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, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
28
|
+
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
29
29
|
import { providerSupportsSessionResume } from "./delivery.js";
|
|
30
30
|
import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./quota.js";
|
|
31
31
|
import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
32
32
|
import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
|
|
33
33
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
|
|
34
|
-
import { laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
34
|
+
import { activeLaneStories, laneStories, storyStatusForEnvelope } from "./stories.js";
|
|
35
35
|
import { resolveManagerStatusGateForLane } from "./manager-status-gate.js";
|
|
36
36
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
37
37
|
import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
@@ -962,8 +962,9 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
962
962
|
const readRegistryFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
|
|
963
963
|
const keepAliveFlagCompanies = new Map();
|
|
964
964
|
for (const lane of lanes) {
|
|
965
|
-
if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
|
|
965
|
+
if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "blocked" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
|
|
966
966
|
(lane.keep_alive !== true && lane.worker_profile === undefined) ||
|
|
967
|
+
(lane.state === "blocked" && lane.keep_alive !== true) ||
|
|
967
968
|
!laneRunsHere(lane, deps.host) ||
|
|
968
969
|
(lane.state === "done" && (lane.terminal_reason === PROVIDER_REJECTED_REASON ||
|
|
969
970
|
lane.terminal_reason === "account_not_logged_in" ||
|
|
@@ -985,6 +986,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
985
986
|
keepAliveFlagCompanies.set(key, company);
|
|
986
987
|
}
|
|
987
988
|
const keepAliveFlagByCompany = new Map();
|
|
989
|
+
const blockedResumeFlagByCompany = new Map();
|
|
988
990
|
const supersessionLineageAwareByCompany = new Map();
|
|
989
991
|
const env = deps.env ?? process.env;
|
|
990
992
|
await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
|
|
@@ -992,11 +994,19 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
992
994
|
company: company ?? undefined,
|
|
993
995
|
env,
|
|
994
996
|
};
|
|
995
|
-
const
|
|
997
|
+
const hasBlockedKeepAliveLane = lanes.some((lane) => lane.state === "blocked" &&
|
|
998
|
+
lane.keep_alive === true &&
|
|
999
|
+
laneRunsHere(lane, startDeps.host) &&
|
|
1000
|
+
keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
|
|
1001
|
+
const [keepAliveEnabled, lineageAware, blockedResumeEnabled] = await Promise.all([
|
|
996
1002
|
readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
|
|
997
1003
|
readRegistryFlag(LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, options),
|
|
1004
|
+
hasBlockedKeepAliveLane
|
|
1005
|
+
? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
|
|
1006
|
+
: Promise.resolve(false),
|
|
998
1007
|
]);
|
|
999
1008
|
keepAliveFlagByCompany.set(key, keepAliveEnabled);
|
|
1009
|
+
blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
|
|
1000
1010
|
supersessionLineageAwareByCompany.set(key, lineageAware);
|
|
1001
1011
|
}));
|
|
1002
1012
|
const admissionThrottleDeps = startDeps.throttleDeps ?? defaultThrottleDeps(startDeps.env ?? process.env);
|
|
@@ -1016,7 +1026,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
1016
1026
|
await withAllLaneAdmissionLocks(hqRoot, async () => {
|
|
1017
1027
|
const freshLanes = loadLaneRecords(hqRoot, summary);
|
|
1018
1028
|
const freshQuestions = loadQuestionRecords(hqRoot, summary);
|
|
1019
|
-
await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany);
|
|
1029
|
+
await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany);
|
|
1020
1030
|
const drainLanes = loadLaneRecords(hqRoot, summary);
|
|
1021
1031
|
await admitQueued(hqRoot, drainLanes, summary, admissionDeps, now);
|
|
1022
1032
|
await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, admissionDeps);
|
|
@@ -1666,7 +1676,8 @@ function keepAliveProgressResetByHuman(lane) {
|
|
|
1666
1676
|
return true;
|
|
1667
1677
|
const messageMs = Date.parse(lane.last_message_at ?? "");
|
|
1668
1678
|
const resumeMs = lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_RESUME_TEXT ||
|
|
1669
|
-
lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT
|
|
1679
|
+
lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT ||
|
|
1680
|
+
lane.last_resume?.text === LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT
|
|
1670
1681
|
? Number.NaN
|
|
1671
1682
|
: Date.parse(lane.last_resume?.at ?? "");
|
|
1672
1683
|
return Number.isFinite(lastAutoMs) &&
|
|
@@ -1779,7 +1790,169 @@ function latestEnvelopeMtimeMs(hqRoot, lane, ref) {
|
|
|
1779
1790
|
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 });
|
|
1780
1791
|
}
|
|
1781
1792
|
}
|
|
1782
|
-
async function
|
|
1793
|
+
async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, now, keepAliveFlags, blockedFlags, companyForLane, lanes, retiredLaneIds, lineageAwareByCompany) {
|
|
1794
|
+
const company = companyForLane(lane);
|
|
1795
|
+
if (company === null) {
|
|
1796
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_company_unresolved" });
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
const key = keepAliveCompanyKey(company);
|
|
1800
|
+
if (keepAliveFlags.get(key) !== true || blockedFlags.get(key) !== true) {
|
|
1801
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_blocked" });
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
if (lane.terminal_reason === "approval_gate_violated" || lane.last_envelope?.blocked_reason === "approval_gate_violated") {
|
|
1805
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_approval_gate_violated" });
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
if (lane.last_envelope?.decision !== "blocked") {
|
|
1809
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_missing_blocked_envelope" });
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
if (retiredLaneIds === null) {
|
|
1813
|
+
recordKeepAliveSkip(hqRoot, lane, "retired_list_invalid", summary, deps);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const exclusion = keepAliveExclusion(lane, lanes, retiredLaneIds, lineageAwareByCompany.get(key) === true, companyForLane);
|
|
1817
|
+
if (exclusion !== undefined) {
|
|
1818
|
+
recordKeepAliveSkip(hqRoot, lane, exclusion, summary, deps);
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const terminalAt = lane.last_envelope.at;
|
|
1822
|
+
const terminalMs = Date.parse(terminalAt ?? "");
|
|
1823
|
+
if (!Number.isFinite(terminalMs)) {
|
|
1824
|
+
recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, "blocked terminal time is invalid");
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (activeLaneStories(lane).length === 0) {
|
|
1828
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_no_open_stories" });
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
let pendingQuestion = questions.some((question) => question.lane_id === lane.lane_id && question.status === "pending");
|
|
1832
|
+
try {
|
|
1833
|
+
for (const id of listQuestionIds(hqRoot)) {
|
|
1834
|
+
const question = readQuestion(hqRoot, id);
|
|
1835
|
+
if (question.lane_id === lane.lane_id && question.status === "pending")
|
|
1836
|
+
pendingQuestion = true;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
catch (error) {
|
|
1840
|
+
markDirty(summary, { kind: "lane", id: lane.lane_id, why: "blocked keep-alive question read failed: " + (error instanceof Error ? error.message : String(error)) });
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
const blockedHistory = lane.status_gate_history ?? [];
|
|
1844
|
+
const priorResumes = lane.keep_alive_blocked_resume_streak ?? 0;
|
|
1845
|
+
const alreadyEscalated = blockedHistory.some((entry) => entry.event === "keep_alive_blocked_escalated" && entry.terminal_at === terminalAt);
|
|
1846
|
+
if (pendingQuestion) {
|
|
1847
|
+
appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, now.toISOString(), "open_question", deps);
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (priorResumes >= 2) {
|
|
1851
|
+
if (!alreadyEscalated) {
|
|
1852
|
+
casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" && current.last_envelope?.at === terminalAt, (current) => ({
|
|
1853
|
+
...current,
|
|
1854
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1855
|
+
event: "keep_alive_blocked_escalated",
|
|
1856
|
+
at: now.toISOString(),
|
|
1857
|
+
terminal_at: terminalAt,
|
|
1858
|
+
}),
|
|
1859
|
+
}), deps.dryRun);
|
|
1860
|
+
}
|
|
1861
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_blocked_escalated" });
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if ((lane.keep_alive_resume_count ?? 0) >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES || lane.keep_alive_exhausted_at !== undefined) {
|
|
1865
|
+
if (lane.keep_alive_exhausted_at === undefined && !deps.dryRun) {
|
|
1866
|
+
casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" &&
|
|
1867
|
+
(current.keep_alive_resume_count ?? 0) >= LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, (current) => ({ ...current, keep_alive_exhausted_at: now.toISOString() }), false);
|
|
1868
|
+
}
|
|
1869
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_exhausted" });
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
if (now.getTime() - terminalMs < LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS) {
|
|
1873
|
+
appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, now.toISOString(), "blocked_grace", deps);
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
const latest = deps.readKeepAliveRecheckLane?.(hqRoot, lane.lane_id) ?? readLane(hqRoot, lane.lane_id);
|
|
1877
|
+
if (latest.version !== lane.version || latest.state !== "blocked" || latest.last_envelope?.decision !== "blocked" || latest.last_envelope.at !== terminalAt)
|
|
1878
|
+
return;
|
|
1879
|
+
let hasOpenQuestion;
|
|
1880
|
+
try {
|
|
1881
|
+
hasOpenQuestion = listQuestionIds(hqRoot).some((id) => {
|
|
1882
|
+
const question = readQuestion(hqRoot, id);
|
|
1883
|
+
return question.lane_id === lane.lane_id && question.status === "pending";
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
catch (error) {
|
|
1887
|
+
markDirty(summary, { kind: "lane", id: lane.lane_id, why: "blocked keep-alive question recheck failed: " + (error instanceof Error ? error.message : String(error)) });
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
if (hasOpenQuestion) {
|
|
1891
|
+
appendBlockedKeepAliveHistory(hqRoot, latest, terminalAt, now.toISOString(), "open_question", deps);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
let current = latest;
|
|
1895
|
+
if (current.provider === "claude" && deps.prepareKeepAliveClaudeResume !== undefined) {
|
|
1896
|
+
try {
|
|
1897
|
+
const prepared = await deps.prepareKeepAliveClaudeResume(current);
|
|
1898
|
+
current = prepared.lane;
|
|
1899
|
+
}
|
|
1900
|
+
catch (error) {
|
|
1901
|
+
markDirty(summary, { kind: "lane", id: lane.lane_id, why: `blocked keep-alive account preparation failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
const afterPreparation = deps.readKeepAliveRecheckLane?.(hqRoot, lane.lane_id) ?? readLane(hqRoot, lane.lane_id);
|
|
1905
|
+
if (afterPreparation.state !== "blocked" || afterPreparation.last_envelope?.decision !== "blocked" || afterPreparation.last_envelope.at !== terminalAt)
|
|
1906
|
+
return;
|
|
1907
|
+
current = afterPreparation;
|
|
1908
|
+
}
|
|
1909
|
+
const expectedVersion = current.version;
|
|
1910
|
+
const result = queueLaneTerminalResume(current, now.toISOString(), LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, { allowBlockedResume: true });
|
|
1911
|
+
if (!result.resumeQueued) {
|
|
1912
|
+
if (result.resumeSkippedReason !== undefined) {
|
|
1913
|
+
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_" + result.resumeSkippedReason });
|
|
1914
|
+
}
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
const queued = casLaneIf(hqRoot, latest.lane_id, (latestRecord) => latestRecord.version === expectedVersion && latestRecord.state === "blocked" && latestRecord.last_envelope?.decision === "blocked" && latestRecord.last_envelope.at === terminalAt, (current) => ({
|
|
1918
|
+
...result.lane,
|
|
1919
|
+
version: current.version,
|
|
1920
|
+
keep_alive_blocked_resume_streak: priorResumes + 1,
|
|
1921
|
+
keep_alive_resume_count: (current.keep_alive_resume_count ?? 0) + 1,
|
|
1922
|
+
keep_alive_last_resume_at: now.toISOString(),
|
|
1923
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1924
|
+
event: "keep_alive_blocked_resumed",
|
|
1925
|
+
at: now.toISOString(),
|
|
1926
|
+
terminal_at: terminalAt,
|
|
1927
|
+
resume_queued: true,
|
|
1928
|
+
}),
|
|
1929
|
+
}), deps.dryRun);
|
|
1930
|
+
if (queued.changed) {
|
|
1931
|
+
summary.resumed.push({ id: lane.lane_id, from: "blocked", to: "queued", reason: "keep_alive_blocked" });
|
|
1932
|
+
if (!deps.dryRun) {
|
|
1933
|
+
recordKeepAliveEvent(hqRoot, lane.lane_id, summary, {
|
|
1934
|
+
event: "keep_alive_resume",
|
|
1935
|
+
at: now.toISOString(),
|
|
1936
|
+
attempt: (lane.keep_alive_resume_count ?? 0) + 1,
|
|
1937
|
+
resumed_from: "blocked",
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, deps) {
|
|
1943
|
+
if (deps.dryRun || lane.status_gate_history?.some((entry) => entry.event === "keep_alive_blocked_grace" && entry.terminal_at === terminalAt && entry.resume_skipped_reason === reason))
|
|
1944
|
+
return;
|
|
1945
|
+
casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" && current.last_envelope?.at === terminalAt, (current) => ({
|
|
1946
|
+
...current,
|
|
1947
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1948
|
+
event: "keep_alive_blocked_grace",
|
|
1949
|
+
at,
|
|
1950
|
+
terminal_at: terminalAt,
|
|
1951
|
+
resume_skipped_reason: reason,
|
|
1952
|
+
}),
|
|
1953
|
+
}), false);
|
|
1954
|
+
}
|
|
1955
|
+
async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany) {
|
|
1783
1956
|
const nowMs = now.getTime();
|
|
1784
1957
|
const nowIso = now.toISOString();
|
|
1785
1958
|
const config = loadLanesConfig(hqRoot);
|
|
@@ -1792,7 +1965,7 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
1792
1965
|
resolvedCompanyByLaneVersion.set(key, company);
|
|
1793
1966
|
return company;
|
|
1794
1967
|
};
|
|
1795
|
-
const keepAliveEnabled = [...flagByCompany.values()].some((enabled) => enabled);
|
|
1968
|
+
const keepAliveEnabled = [...flagByCompany.values()].some((enabled) => enabled) || [...blockedResumeFlagByCompany.values()].some(Boolean);
|
|
1796
1969
|
let retiredLaneIds = null;
|
|
1797
1970
|
if (keepAliveEnabled) {
|
|
1798
1971
|
try {
|
|
@@ -1813,6 +1986,10 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
1813
1986
|
recordKeepAliveSkip(hqRoot, originalLane, "terminal_state", summary, deps);
|
|
1814
1987
|
continue;
|
|
1815
1988
|
}
|
|
1989
|
+
if (originalLane.state === "blocked" && originalLane.keep_alive === true) {
|
|
1990
|
+
await resumeBlockedKeepAlive(hqRoot, originalLane, questions, summary, deps, now, flagByCompany, blockedResumeFlagByCompany, companyForLane, lanes, retiredLaneIds, supersessionLineageAwareByCompany);
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1816
1993
|
if ((originalLane.state !== "done" && originalLane.state !== "interrupted") ||
|
|
1817
1994
|
(originalLane.keep_alive !== true && originalLane.worker_profile === undefined))
|
|
1818
1995
|
continue;
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -28,6 +28,7 @@ import { registryWorkerRecord } from "./registry-persona.js";
|
|
|
28
28
|
import { resolveMachineIdentity, } from "./machine-identity.js";
|
|
29
29
|
import { appendCappedHistory, listLaneIds, readLane, retryUpdateLane } from "./store.js";
|
|
30
30
|
import { managerStatusGateHeartbeat } from "./manager-status-gate.js";
|
|
31
|
+
import { LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT } from "./keep-alive.js";
|
|
31
32
|
import { copySessionFiles, planSessionCopy } from "./move-account.js";
|
|
32
33
|
import { ensureSessionHome } from "./session-relink.js";
|
|
33
34
|
import { MESSAGE_REPLAY_STATE_NAME } from "./message-replay.js";
|
|
@@ -2133,6 +2134,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2133
2134
|
: {}),
|
|
2134
2135
|
};
|
|
2135
2136
|
}
|
|
2137
|
+
if (resume && pendingResume?.text !== LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT) {
|
|
2138
|
+
next.keep_alive_blocked_resume_streak = 0;
|
|
2139
|
+
}
|
|
2136
2140
|
return next;
|
|
2137
2141
|
});
|
|
2138
2142
|
if (!claimed) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* compare-and-set on `version`. Histories are capped at 50 at the append site.
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
+
import { type OutpostIdentity } from "./admission-host.js";
|
|
10
11
|
import { type AttributionHistoryEntry, type LaneRecord, type LaneWriter, type QuestionRecord, type RoutingHistoryEntry, type SeniorHistoryEntry } from "./types.js";
|
|
11
12
|
export declare const LANES_STORE_REL: string;
|
|
12
13
|
export declare const LANE_RECORDS_DIR = "lanes";
|
|
@@ -15,6 +16,7 @@ export declare const PENDING_INDEX_DIR: string;
|
|
|
15
16
|
export interface StoreDeps {
|
|
16
17
|
now: () => Date;
|
|
17
18
|
hostname: () => string;
|
|
19
|
+
outpostIdentity: () => OutpostIdentity;
|
|
18
20
|
mkdirSync: typeof fs.mkdirSync;
|
|
19
21
|
readFileSync: typeof fs.readFileSync;
|
|
20
22
|
writeFileSync: typeof fs.writeFileSync;
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -14,6 +14,7 @@ import semver from "semver";
|
|
|
14
14
|
import { CLI_VERSION } from "../../cli-version.js";
|
|
15
15
|
import { generateUlid } from "../mesh/live/ulid.js";
|
|
16
16
|
import { resolveLanesHqRoot } from "./hq-root.js";
|
|
17
|
+
import { readOutpostIdentity } from "./admission-host.js";
|
|
17
18
|
import { LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesError, isLaneVersionConflict, } from "./errors.js";
|
|
18
19
|
import { HISTORY_CAP, HOST_KINDS, LANE_STORY_STATUSES, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isQueuedReason, isLaneState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseMajorSchema, REQUIRED_LANE_RECORD_FIELDS, LANE_FIELD_WRITERS, LANE_WRITERS, } from "./types.js";
|
|
19
20
|
import { laneStories } from "./stories.js";
|
|
@@ -29,6 +30,7 @@ function defaultStoreDeps() {
|
|
|
29
30
|
return {
|
|
30
31
|
now: () => new Date(),
|
|
31
32
|
hostname: () => os.hostname(),
|
|
33
|
+
outpostIdentity: () => readOutpostIdentity(),
|
|
32
34
|
mkdirSync: fs.mkdirSync.bind(fs),
|
|
33
35
|
readFileSync: fs.readFileSync.bind(fs),
|
|
34
36
|
writeFileSync: fs.writeFileSync.bind(fs),
|
|
@@ -481,6 +483,16 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
481
483
|
value: raw.keep_alive_resume_count,
|
|
482
484
|
});
|
|
483
485
|
}
|
|
486
|
+
if (raw.keep_alive_blocked_resume_streak !== undefined &&
|
|
487
|
+
(typeof raw.keep_alive_blocked_resume_streak !== "number" ||
|
|
488
|
+
!Number.isSafeInteger(raw.keep_alive_blocked_resume_streak) ||
|
|
489
|
+
raw.keep_alive_blocked_resume_streak < 0 || raw.keep_alive_blocked_resume_streak > 2)) {
|
|
490
|
+
throw new LaneSchemaError("keep_alive_blocked_resume_streak must be an integer from 0 through 2", {
|
|
491
|
+
path: filePath,
|
|
492
|
+
field: "keep_alive_blocked_resume_streak",
|
|
493
|
+
value: raw.keep_alive_blocked_resume_streak,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
484
496
|
for (const field of [
|
|
485
497
|
"keep_alive_opted_in_at",
|
|
486
498
|
"keep_alive_last_resume_at",
|
|
@@ -938,6 +950,12 @@ export function parseQuestionRecord(raw, filePath) {
|
|
|
938
950
|
}
|
|
939
951
|
requireString(raw.question_id, "question_id");
|
|
940
952
|
requireString(raw.lane_id, "lane_id");
|
|
953
|
+
if (raw.priority !== undefined && raw.priority !== null && raw.priority !== "P0" && raw.priority !== "P1" && raw.priority !== "P2") {
|
|
954
|
+
throw new LaneSchemaError("priority must be P0, P1, or P2 when present", {
|
|
955
|
+
path: filePath,
|
|
956
|
+
value: raw.priority,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
941
959
|
requireInt(raw.version, "version");
|
|
942
960
|
requireInt(raw.hop, "hop");
|
|
943
961
|
parseOptionalCompanyField(raw, filePath);
|
|
@@ -964,7 +982,11 @@ export function parseQuestionRecord(raw, filePath) {
|
|
|
964
982
|
value: delivered_at,
|
|
965
983
|
});
|
|
966
984
|
}
|
|
967
|
-
return {
|
|
985
|
+
return {
|
|
986
|
+
...raw,
|
|
987
|
+
delivered_at,
|
|
988
|
+
...(raw.priority === null ? { priority: undefined } : {}),
|
|
989
|
+
};
|
|
968
990
|
}
|
|
969
991
|
/**
|
|
970
992
|
* HQ Sync sibling-twin grammar from `@indigoai-us/hq-cloud` `buildConflictPath`:
|
|
@@ -1218,9 +1240,36 @@ export function readAllLanes(hqRoot, deps = defaultStoreDeps()) {
|
|
|
1218
1240
|
...deps,
|
|
1219
1241
|
conflictSidecarPathsFor: (recordPath) => sidecarsByRecord.get(path.basename(recordPath)) ?? [],
|
|
1220
1242
|
};
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1243
|
+
const records = [];
|
|
1244
|
+
for (const laneId of laneIds) {
|
|
1245
|
+
try {
|
|
1246
|
+
records.push(readLane(hqRoot, laneId, indexedDeps));
|
|
1247
|
+
}
|
|
1248
|
+
catch (err) {
|
|
1249
|
+
if (!(err instanceof LaneSchemaError))
|
|
1250
|
+
throw err;
|
|
1251
|
+
const filePath = laneRecordPath(hqRoot, laneId);
|
|
1252
|
+
const raw = readJsonFile(filePath, indexedDeps);
|
|
1253
|
+
const host = raw !== null && typeof raw === "object" && !Array.isArray(raw)
|
|
1254
|
+
? raw.host
|
|
1255
|
+
: undefined;
|
|
1256
|
+
const hostRecord = host !== null && typeof host === "object" && !Array.isArray(host)
|
|
1257
|
+
? host
|
|
1258
|
+
: undefined;
|
|
1259
|
+
const outpostId = hostRecord?.kind === "outpost" &&
|
|
1260
|
+
typeof hostRecord.id === "string" && hostRecord.id.trim() !== ""
|
|
1261
|
+
? hostRecord.id
|
|
1262
|
+
: undefined;
|
|
1263
|
+
const identity = indexedDeps.outpostIdentity();
|
|
1264
|
+
const isRemoteMirror = outpostId !== undefined && (identity.kind === "not_outpost" ||
|
|
1265
|
+
(identity.kind === "id" && identity.id !== outpostId));
|
|
1266
|
+
if (!isRemoteMirror)
|
|
1267
|
+
throw err;
|
|
1268
|
+
const field = typeof err.details.field === "string" ? err.details.field : "unknown_field";
|
|
1269
|
+
process.stderr.write(`lanes store: skipping malformed mirrored lane ${laneId} field ${field}: ${err.message}\n`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return records.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at) ||
|
|
1224
1273
|
a.lane_id.localeCompare(b.lane_id));
|
|
1225
1274
|
}
|
|
1226
1275
|
export function tryReadQuestion(hqRoot, questionId, deps = defaultStoreDeps()) {
|
|
@@ -400,6 +400,13 @@ export interface KrFreshnessState {
|
|
|
400
400
|
/** Stable fingerprint of the last stalled KPI set, which is resumed once. */
|
|
401
401
|
stalled_fingerprint?: string;
|
|
402
402
|
}
|
|
403
|
+
export interface KeepAliveBlockedHistoryEntry {
|
|
404
|
+
event: "keep_alive_blocked_grace" | "keep_alive_blocked_resumed" | "keep_alive_blocked_escalated";
|
|
405
|
+
at: string;
|
|
406
|
+
terminal_at: string;
|
|
407
|
+
resume_skipped_reason?: "open_question" | "blocked_grace";
|
|
408
|
+
resume_queued?: boolean;
|
|
409
|
+
}
|
|
403
410
|
export interface KrFreshnessHistoryEntry {
|
|
404
411
|
event: "kr_stalled";
|
|
405
412
|
turn_id: string;
|
|
@@ -411,7 +418,7 @@ export interface KrFreshnessHistoryEntry {
|
|
|
411
418
|
resume_queued?: boolean;
|
|
412
419
|
resume_skipped_reason?: "open_question" | "blocked" | "miss_limit" | "no_provider_session";
|
|
413
420
|
}
|
|
414
|
-
export type LaneGateHistoryEntry = ManagerStatusGateHistoryEntry | BlockerFreshnessHistoryEntry | KrFreshnessHistoryEntry;
|
|
421
|
+
export type LaneGateHistoryEntry = ManagerStatusGateHistoryEntry | BlockerFreshnessHistoryEntry | KrFreshnessHistoryEntry | KeepAliveBlockedHistoryEntry;
|
|
415
422
|
export interface LaneStory {
|
|
416
423
|
project_id: string;
|
|
417
424
|
story_id: string;
|
|
@@ -484,6 +491,8 @@ export interface LaneRecord {
|
|
|
484
491
|
keep_alive_blocked_reason?: "not_opted_in" | "missing_opt_in_time" | "envelope_before_opt_in" | "interruption_before_opt_in" | "missing_interruption_time" | "retired" | "superseded" | "retired_list_invalid" | "terminal_state";
|
|
485
492
|
/** Consecutive automatic keep-alive resumes since the last human reset. */
|
|
486
493
|
keep_alive_resume_count?: number;
|
|
494
|
+
/** Blocked keep-alive resumes in the current blocked episode. */
|
|
495
|
+
keep_alive_blocked_resume_streak?: number;
|
|
487
496
|
/** Time the most recent automatic keep-alive resume was queued. */
|
|
488
497
|
keep_alive_last_resume_at?: string;
|
|
489
498
|
/** Earliest time the next consecutive automatic resume may be queued. */
|
|
@@ -782,12 +791,15 @@ export interface QuestionAnswer {
|
|
|
782
791
|
* question is flagged for the operator; nothing answers it automatically.
|
|
783
792
|
*/
|
|
784
793
|
export type QuestionKind = "continue";
|
|
794
|
+
export type QuestionPriority = "P0" | "P1" | "P2";
|
|
785
795
|
export interface QuestionRecord {
|
|
786
796
|
schema_version: typeof QUESTION_SCHEMA_VERSION | string;
|
|
787
797
|
question_id: string;
|
|
788
798
|
lane_id: string;
|
|
789
799
|
/** Set by the raiser when the options are only continue/stop. */
|
|
790
800
|
question_kind?: QuestionKind;
|
|
801
|
+
/** Urgency selected by the lane author; absent on pre-priority records. */
|
|
802
|
+
priority?: QuestionPriority;
|
|
791
803
|
/** `null` when the originating lane has no company. */
|
|
792
804
|
company: LaneCompanyRef | null;
|
|
793
805
|
project_id: string;
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -225,11 +225,11 @@ export const LANE_FIELD_WRITERS = {
|
|
|
225
225
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
226
226
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
227
227
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
228
|
-
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
228
|
+
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
229
229
|
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
230
230
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command"],
|
|
231
231
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
232
|
-
last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
232
|
+
last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
233
233
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
234
234
|
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command", "reconcile"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
235
235
|
quota_reset_at: ["watcher", "reconcile"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
@@ -411,6 +411,8 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
411
411
|
}), current.registry_worker_approval_history ?? []);
|
|
412
412
|
}
|
|
413
413
|
if (envelope) {
|
|
414
|
+
if (envelope.decision !== "blocked")
|
|
415
|
+
next.keep_alive_blocked_resume_streak = 0;
|
|
414
416
|
next.last_envelope = {
|
|
415
417
|
ref: envelopeRef,
|
|
416
418
|
decision: envelope.decision,
|
|
@@ -1536,7 +1538,7 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1536
1538
|
updated = await settleKrFreshness(hqRoot, updated, manifest.turn_id, tick.emit.at, deps, logDecision);
|
|
1537
1539
|
}
|
|
1538
1540
|
const managerStatusFollowupQueued = manifest.turn_id !== undefined &&
|
|
1539
|
-
updated.status_gate_history?.some((entry) => entry.turn_id === manifest.turn_id &&
|
|
1541
|
+
updated.status_gate_history?.some((entry) => "turn_id" in entry && entry.turn_id === manifest.turn_id &&
|
|
1540
1542
|
((entry.event === "status_missed" && entry.resume_queued === true) ||
|
|
1541
1543
|
(entry.event === "blocker_stale" && entry.resume_queued === true) ||
|
|
1542
1544
|
(entry.event === "kr_stalled" && entry.resume_queued === true))) === true;
|
|
@@ -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
|
|
@@ -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
|
}
|
|
@@ -167,6 +167,13 @@ export interface SelfUpdateDeps {
|
|
|
167
167
|
* aside for the whole install. Read only when an npm global install is due.
|
|
168
168
|
*/
|
|
169
169
|
stagedSelfUpdate?: () => Promise<boolean>;
|
|
170
|
+
/**
|
|
171
|
+
* Runs installs through a copy of the installer's supervisor outside the
|
|
172
|
+
* package until disposed. Defaults to {@link detachUpdateSupervisor}.
|
|
173
|
+
*/
|
|
174
|
+
detachSupervisor?: () => {
|
|
175
|
+
dispose(): void;
|
|
176
|
+
} | null;
|
|
170
177
|
}
|
|
171
178
|
/**
|
|
172
179
|
* Startup path: the running CLI is behind npm `latest`, so update in place and
|