@indigoai-us/hq-cli 5.345.127 → 5.345.128
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 +35 -0
- package/assets/scaffold/core/scripts/handoff-knowledge-commit.sh +123 -0
- package/assets/scaffold/core/scripts/hq-detach.sh +169 -0
- package/dist/command-catalog.generated.d.ts +40 -2
- package/dist/command-catalog.generated.js +48 -2
- package/dist/commands/auth.js +19 -2
- package/dist/commands/core-bucket-c-handoff-knowledge-fast.d.ts +2 -0
- package/dist/commands/core-bucket-c-handoff-knowledge-fast.js +10 -0
- package/dist/commands/core-bucket-c-handoff-knowledge-native.d.ts +46 -0
- package/dist/commands/core-bucket-c-handoff-knowledge-native.js +284 -0
- package/dist/commands/core-bucket-c-hq-detach-fast.d.ts +2 -0
- package/dist/commands/core-bucket-c-hq-detach-fast.js +10 -0
- package/dist/commands/core-bucket-c-hq-detach-native.d.ts +43 -0
- package/dist/commands/core-bucket-c-hq-detach-native.js +241 -0
- package/dist/commands/core.js +12 -0
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-shared.d.ts +7 -0
- package/dist/commands/lanes.js +113 -3
- package/dist/commands/skill.js +9 -4
- package/dist/fast-scaffold.js +28 -0
- package/dist/lib/capacity/lane-login.js +1 -0
- package/dist/lib/lanes/accounts.js +2 -2
- package/dist/lib/lanes/host-capacity.d.ts +4 -0
- package/dist/lib/lanes/host-capacity.js +10 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/loop.d.ts +8 -0
- package/dist/lib/lanes/loop.js +35 -0
- package/dist/lib/lanes/quota-notice.d.ts +11 -11
- package/dist/lib/lanes/quota-notice.js +20 -29
- package/dist/lib/lanes/quota.d.ts +69 -50
- package/dist/lib/lanes/quota.js +146 -127
- package/dist/lib/lanes/reconcile.d.ts +14 -0
- package/dist/lib/lanes/reconcile.js +77 -23
- package/dist/lib/lanes/watcher.d.ts +8 -0
- package/dist/lib/lanes/watcher.js +62 -20
- package/dist/utils/resident-package-preload.js +12 -4
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { parseUserYaml } from "../../utils/user-yaml-error.js";
|
|
11
|
-
import { compareLaneQueueOrder, compareLaneQueueOrderWithLeads, freeClaudeAccountSlots, leadLaneIds, hasActiveAdmissionReservation, hasActivePendingResume, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, } from "./admission.js";
|
|
11
|
+
import { compareLaneQueueOrder, compareLaneQueueOrderWithLeads, freeClaudeAccountSlots, leadLaneIds, hasActiveAdmissionReservation, hasActivePendingResume, laneAdmissionCapReason, laneOccupiesRunningSlot, laneRunsOnHost, withAllLaneAdmissionLocks, } from "./admission.js";
|
|
12
12
|
import { accountStatus, isCredentialProven, resolveAccount } from "./accounts.js";
|
|
13
13
|
import { effectiveAccountHome } from "./account-home.js";
|
|
14
14
|
import { enforceRegistryApprovalGate } from "./approval-gate.js";
|
|
@@ -34,7 +34,7 @@ import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LE
|
|
|
34
34
|
import { isFailedQuotaKeepAliveLane, providerSupportsSessionResume } from "./delivery.js";
|
|
35
35
|
import { LANE_PROVIDERS, isIsoDateTime, isTerminalLaneState } from "./types.js";
|
|
36
36
|
import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, loadQuotaState, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
37
|
-
import {
|
|
37
|
+
import { deliverQuotaNotice, quotaLaneNotice } from "./quota-notice.js";
|
|
38
38
|
import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
39
39
|
import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
|
|
40
40
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
|
|
@@ -1724,32 +1724,16 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1724
1724
|
? classifyLaneExit({ envelope: null, hasPartialOutput: false, invalidEnvelope })
|
|
1725
1725
|
: classification;
|
|
1726
1726
|
let questionRefusalCode = null;
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
: null;
|
|
1727
|
+
// A usage limit fails only this lane; the senior decides on resume.
|
|
1728
|
+
const accountRetryReason = !quota && providerRejection && classification.reason === "account_not_logged_in"
|
|
1729
|
+
? "account_unavailable"
|
|
1730
|
+
: null;
|
|
1732
1731
|
if (!deps.dryRun &&
|
|
1733
1732
|
quota &&
|
|
1734
1733
|
classified.account &&
|
|
1735
1734
|
(classification.reason === "account_quota_exhausted" ||
|
|
1736
1735
|
classification.reason === "account_model_unavailable")) {
|
|
1737
|
-
|
|
1738
|
-
now,
|
|
1739
|
-
unconfirmedHoldMs: providerQuotaDefaultHoldMs(loadLanesConfig(hqRoot).providers[classified.provider]),
|
|
1740
|
-
});
|
|
1741
|
-
if (classification.reason === "account_quota_exhausted") {
|
|
1742
|
-
const notice = quotaHoldNotice(quotaState, classified.provider, classified.account, lane.lane_id);
|
|
1743
|
-
if (notice) {
|
|
1744
|
-
try {
|
|
1745
|
-
deliverQuotaHoldNotice(hqRoot, lane, notice, now.toISOString());
|
|
1746
|
-
}
|
|
1747
|
-
catch (err) {
|
|
1748
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1749
|
-
process.stderr.write(`hq lanes reconcile: quota hold notice failed for ${lane.lane_id}: ${message}\n`);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1736
|
+
recordAccountQuota(hqRoot, classified.provider, classified.account, quota, now);
|
|
1753
1737
|
}
|
|
1754
1738
|
if (!deps.dryRun &&
|
|
1755
1739
|
accountRetryReason &&
|
|
@@ -1886,6 +1870,22 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1886
1870
|
result.record.model) {
|
|
1887
1871
|
clearAccountQuotaAfterSuccessfulTurnBestEffort(hqRoot, result.record.provider, result.record.account, result.record.model, new Date(nowIso));
|
|
1888
1872
|
}
|
|
1873
|
+
if (!deps.dryRun &&
|
|
1874
|
+
result.changed &&
|
|
1875
|
+
quota &&
|
|
1876
|
+
(result.record.terminal_reason === "account_quota_exhausted" ||
|
|
1877
|
+
result.record.terminal_reason === "account_model_unavailable")) {
|
|
1878
|
+
// The dead worker's exit is classified here, not in its watcher, so
|
|
1879
|
+
// the senior notice goes out here, once, after the terminal write and
|
|
1880
|
+
// keyed to the new version. No account hold is set.
|
|
1881
|
+
try {
|
|
1882
|
+
deliverQuotaNotice(hqRoot, result.record, quotaLaneNotice(result.record, quota), nowIso);
|
|
1883
|
+
}
|
|
1884
|
+
catch (err) {
|
|
1885
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1886
|
+
process.stderr.write(`hq lanes reconcile: quota notice failed for ${lane.lane_id}: ${message}\n`);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
1889
|
if (result.changed) {
|
|
1890
1890
|
summary.dead_host.push({
|
|
1891
1891
|
id: lane.lane_id,
|
|
@@ -3875,4 +3875,58 @@ export async function wakeAnsweredLaneNow(hqRoot, laneId, deps) {
|
|
|
3875
3875
|
error: failure?.why ?? `Lane ${laneId} did not leave awaiting_input after its questions were answered`,
|
|
3876
3876
|
};
|
|
3877
3877
|
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Run queue admission in-process, without the rest of a reconcile pass.
|
|
3880
|
+
*
|
|
3881
|
+
* Commands that change capacity (create, stop, cancel, clear-quota, a lane
|
|
3882
|
+
* exit) call this so queued lanes start without `hq daemon`. It takes the
|
|
3883
|
+
* reconcile lock and every provider admission lock, so it is safe to run
|
|
3884
|
+
* while a daemon is also reconciling: whichever runs second sees the lanes
|
|
3885
|
+
* the first one already admitted.
|
|
3886
|
+
*/
|
|
3887
|
+
export async function admitQueuedNow(hqRoot, deps) {
|
|
3888
|
+
const summary = emptySummary(deps.dryRun);
|
|
3889
|
+
const now = deps.now();
|
|
3890
|
+
// A queued lane is worth an admission attempt only while its provider has
|
|
3891
|
+
// a free running slot here. Account and profile caps are left to
|
|
3892
|
+
// admitQueued, which can move a lane to another free account.
|
|
3893
|
+
const hasAdmittableQueued = (lanes) => {
|
|
3894
|
+
const queued = lanes.filter((lane) => lane.state === "queued" && laneRunsHere(lane, deps.host));
|
|
3895
|
+
if (queued.length === 0)
|
|
3896
|
+
return false;
|
|
3897
|
+
const config = loadLanesConfig(hqRoot);
|
|
3898
|
+
const nowMs = now.getTime();
|
|
3899
|
+
return queued.some((lane) => {
|
|
3900
|
+
const providerRunning = lanes.filter((other) => other.lane_id !== lane.lane_id && other.provider === lane.provider &&
|
|
3901
|
+
laneOccupiesRunningSlot(other, nowMs) && laneRunsHere(other, deps.host)).length;
|
|
3902
|
+
return providerRunning < config.providers[lane.provider].max_running;
|
|
3903
|
+
});
|
|
3904
|
+
};
|
|
3905
|
+
// Skip the lock and the CPU sample when nothing can start here.
|
|
3906
|
+
if (!hasAdmittableQueued(loadLaneRecords(hqRoot, emptySummary(deps.dryRun)))) {
|
|
3907
|
+
return { admitted: [], errors: [] };
|
|
3908
|
+
}
|
|
3909
|
+
const throttleDeps = deps.throttleDeps ?? defaultThrottleDeps(deps.env ?? process.env);
|
|
3910
|
+
await withReconcileLock(hqRoot, async () => {
|
|
3911
|
+
const previousState = readThrottleState(throttleStatePath(hqRoot), throttleDeps);
|
|
3912
|
+
const previousSnapshot = previousState === "missing" || previousState === "unreadable"
|
|
3913
|
+
? undefined
|
|
3914
|
+
: previousState.cpu_snapshot;
|
|
3915
|
+
const cpuObservation = deps.cpuBusyFraction !== undefined
|
|
3916
|
+
? undefined
|
|
3917
|
+
: await sampleCpuBusyObservation(throttleDeps, previousSnapshot);
|
|
3918
|
+
const admissionDeps = {
|
|
3919
|
+
...deps,
|
|
3920
|
+
...(cpuObservation ? { cpuBusyFraction: cpuObservation.busy, cpuObservation } : {}),
|
|
3921
|
+
skipAdmissionWithoutCpuSample: false,
|
|
3922
|
+
};
|
|
3923
|
+
await withAllLaneAdmissionLocks(hqRoot, async () => {
|
|
3924
|
+
const lanes = loadLaneRecords(hqRoot, summary);
|
|
3925
|
+
if (!hasAdmittableQueued(lanes))
|
|
3926
|
+
return;
|
|
3927
|
+
await admitQueued(hqRoot, lanes, summary, admissionDeps, now);
|
|
3928
|
+
});
|
|
3929
|
+
});
|
|
3930
|
+
return { admitted: summary.admitted, errors: summary.errors };
|
|
3931
|
+
}
|
|
3878
3932
|
//# sourceMappingURL=reconcile.js.map
|
|
@@ -211,6 +211,14 @@ export interface WatcherLoopDeps {
|
|
|
211
211
|
retrySpawnDeps?: Omit<SpawnLaneDeps, "admissionHost">;
|
|
212
212
|
/** Active-company fallback when an unscoped lane asks from its envelope. */
|
|
213
213
|
resolveQuestionActiveCompany?: () => Promise<LaneCompanyRef>;
|
|
214
|
+
/**
|
|
215
|
+
* Admit queued lanes after this lane's exit frees its slot. Default: the
|
|
216
|
+
* in-process admitter (`admitQueuedNow`), so the queue drains without a
|
|
217
|
+
* daemon. Returns the admitted lane ids.
|
|
218
|
+
*/
|
|
219
|
+
admitQueued?: (hqRoot: string) => Promise<string[]>;
|
|
220
|
+
/** Test seam: deliver the one-time usage-limit notice to the lane's senior. */
|
|
221
|
+
deliverQuotaNotice?: (lane: LaneRecord, text: string) => Promise<void>;
|
|
214
222
|
/** Resume an atomically queued inbox after this watcher wins terminal CAS. */
|
|
215
223
|
recoverQueuedMessages?: (lane: LaneRecord) => Promise<void>;
|
|
216
224
|
/** Deliver a lane-senior escalation through the normal lane-message router. */
|
|
@@ -43,7 +43,7 @@ import { appendCappedHistory, listQuestionIds, readLane, readQuestion, retryUpda
|
|
|
43
43
|
import { enforceRegistryApprovalGate } from "./approval-gate.js";
|
|
44
44
|
import { readWorkerRunResult } from "./delivery.js";
|
|
45
45
|
import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, quotaSignalFromFields, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
46
|
-
import {
|
|
46
|
+
import { deliverQuotaNotice as deliverQuotaNoticeToSenior, quotaLaneNotice } from "./quota-notice.js";
|
|
47
47
|
import { PROVIDER_REJECTED_REASON, QUOTA_STATUS, isProviderLoginFailure, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
48
48
|
import { appendDropboxMessageAtPath, dropboxInboxHasMessages, readToolActivity, toolActivityPath } from "./dropbox.js";
|
|
49
49
|
import { loadLanesConfig, providerProgressEventMinIntervalMs, providerQuotaDefaultHoldMs } from "./config.js";
|
|
@@ -154,14 +154,15 @@ function validFailureOutputTime(value, fallback) {
|
|
|
154
154
|
: fallback;
|
|
155
155
|
}
|
|
156
156
|
export function routeProviderFailure(failure, readQuota, model, now) {
|
|
157
|
+
const structured = failure && failure.resets_at !== undefined ? { resets_at: failure.resets_at } : {};
|
|
157
158
|
if (failure && failure.status === QUOTA_STATUS && isAccountQuotaFailure(failure)) {
|
|
158
159
|
return {
|
|
159
|
-
quota: quotaSignalFromFields({ api_error_status: QUOTA_STATUS, is_error: true, result: failure.message }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now),
|
|
160
|
+
quota: quotaSignalFromFields({ api_error_status: QUOTA_STATUS, is_error: true, result: failure.message, ...structured }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now),
|
|
160
161
|
rejection: null,
|
|
161
162
|
};
|
|
162
163
|
}
|
|
163
164
|
if (failure && failure.status === null && isAccountQuotaFailure(failure)) {
|
|
164
|
-
const quota = quotaSignalFromFields({ api_error_status: undefined, is_error: true, result: failure.message }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now);
|
|
165
|
+
const quota = quotaSignalFromFields({ api_error_status: undefined, is_error: true, result: failure.message, ...structured }, model, failure.output_at ? validFailureOutputTime(failure.output_at, now) : now);
|
|
165
166
|
if (quota)
|
|
166
167
|
return { quota, rejection: null };
|
|
167
168
|
}
|
|
@@ -1167,6 +1168,28 @@ async function settleKrFreshness(hqRoot, lane, manifestTurnId, at, deps, logDeci
|
|
|
1167
1168
|
};
|
|
1168
1169
|
});
|
|
1169
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Tell the lane's senior once that a usage limit ended this lane. Called
|
|
1173
|
+
* only on the terminal write that changed the lane; the message id (lane id
|
|
1174
|
+
* and version) dedupes a retried append.
|
|
1175
|
+
*/
|
|
1176
|
+
async function deliverQuotaNotice(hqRoot, lane, quota, at, deps, logDecision) {
|
|
1177
|
+
const notice = quotaLaneNotice(lane, quota);
|
|
1178
|
+
try {
|
|
1179
|
+
if (deps.deliverQuotaNotice) {
|
|
1180
|
+
await deps.deliverQuotaNotice(lane, notice.text);
|
|
1181
|
+
logDecision({ event: "quota_notice", at, delivered_to: "test_seam" });
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const deliveredTo = deliverQuotaNoticeToSenior(hqRoot, lane, notice, at);
|
|
1185
|
+
logDecision({ event: "quota_notice", at, delivered_to: deliveredTo });
|
|
1186
|
+
}
|
|
1187
|
+
catch (err) {
|
|
1188
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1189
|
+
process.stderr.write(`hq lanes watcher: quota notice failed for ${lane.lane_id}: ${message}\n`);
|
|
1190
|
+
logDecision({ event: "quota_notice_failed", at, error: message });
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1170
1193
|
function findBlockerFreshnessSessionInbox(hqRoot, sessionId) {
|
|
1171
1194
|
let candidates;
|
|
1172
1195
|
try {
|
|
@@ -1457,6 +1480,18 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1457
1480
|
}
|
|
1458
1481
|
const progressIntervalMs = deps.progressEventMinIntervalMs ?? readProgressIntervalMs(hqRoot, readLaneSoft(hqRoot, laneId));
|
|
1459
1482
|
const spawnRepair = deps.spawnRepair ?? spawnEnvelopeRepair;
|
|
1483
|
+
const defaultAdmitQueued = async (root) => {
|
|
1484
|
+
const { admitQueuedNow } = await import("./reconcile.js");
|
|
1485
|
+
const host = (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)();
|
|
1486
|
+
const result = await admitQueuedNow(root, {
|
|
1487
|
+
now: () => new Date(now()),
|
|
1488
|
+
host,
|
|
1489
|
+
dryRun: false,
|
|
1490
|
+
spawnLaneDeps: { ...deps.retrySpawnDeps, admissionHost: host },
|
|
1491
|
+
...(deps.retrySpawnDeps?.env ? { env: deps.retrySpawnDeps.env } : {}),
|
|
1492
|
+
});
|
|
1493
|
+
return result.admitted.map((entry) => entry.id);
|
|
1494
|
+
};
|
|
1460
1495
|
const retryAccount = deps.retryAccount ?? ((root, lane, reason) => retryLaneOnAlternateAccount(root, lane, reason, "watcher", {
|
|
1461
1496
|
...deps.retrySpawnDeps,
|
|
1462
1497
|
admissionHost: (deps.resolveAdmissionHost ?? resolveProcessAdmissionHost)(),
|
|
@@ -1765,28 +1800,17 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1765
1800
|
(classification.reason === "account_quota_exhausted" ||
|
|
1766
1801
|
classification.reason === "account_model_unavailable")) {
|
|
1767
1802
|
try {
|
|
1768
|
-
|
|
1769
|
-
const quotaState = recordAccountQuota(hqRoot, quotaLane.provider, quotaLane.account, quota, {
|
|
1770
|
-
now: observedAt,
|
|
1771
|
-
unconfirmedHoldMs: providerQuotaDefaultHoldMs(loadLanesConfig(hqRoot).providers[quotaLane.provider]),
|
|
1772
|
-
});
|
|
1773
|
-
if (classification.reason === "account_quota_exhausted") {
|
|
1774
|
-
const notice = quotaHoldNotice(quotaState, quotaLane.provider, quotaLane.account, quotaLane.lane_id);
|
|
1775
|
-
if (notice) {
|
|
1776
|
-
const deliveredTo = deliverQuotaHoldNotice(hqRoot, quotaLane, notice, observedAt.toISOString());
|
|
1777
|
-
logDecision({ event: "quota_hold_notice", at: tick.emit.at, account: quotaLane.account, delivered_to: deliveredTo });
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1803
|
+
recordAccountQuota(hqRoot, quotaLane.provider, quotaLane.account, quota, new Date(now()));
|
|
1780
1804
|
}
|
|
1781
1805
|
catch (err) {
|
|
1782
1806
|
process.stderr.write(`hq lanes watcher: quota record failed for ${laneId}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1783
1807
|
}
|
|
1784
1808
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1809
|
+
// A usage limit fails only this lane: no automatic move to another
|
|
1810
|
+
// account. The senior is told and decides (owner decision 2026-10-14).
|
|
1811
|
+
const accountRetryReason = !quota && isProviderLoginFailure(providerRejection)
|
|
1812
|
+
? "account_unavailable"
|
|
1813
|
+
: null;
|
|
1790
1814
|
if (accountRetryReason && quotaLane?.account) {
|
|
1791
1815
|
try {
|
|
1792
1816
|
const retry = await retryAccount(hqRoot, quotaLane, accountRetryReason);
|
|
@@ -1945,6 +1969,24 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1945
1969
|
logDecision({ event: "keep_alive_terminal_trigger_failed", at: tick.emit.at, error: message });
|
|
1946
1970
|
}
|
|
1947
1971
|
}
|
|
1972
|
+
if (terminalWrite.changed &&
|
|
1973
|
+
(updated.terminal_reason === "account_quota_exhausted" || updated.terminal_reason === "account_model_unavailable")) {
|
|
1974
|
+
await deliverQuotaNotice(hqRoot, updated, quota ?? null, tick.emit.at, deps, logDecision);
|
|
1975
|
+
}
|
|
1976
|
+
if (terminalWrite.changed && updated.state !== "running" && updated.state !== "queued") {
|
|
1977
|
+
// The exit freed a running slot: admit queued lanes here rather
|
|
1978
|
+
// than waiting for a reconcile pass that may never run.
|
|
1979
|
+
try {
|
|
1980
|
+
const admitted = await (deps.admitQueued ?? defaultAdmitQueued)(hqRoot);
|
|
1981
|
+
if (admitted.length > 0)
|
|
1982
|
+
logDecision({ event: "queue_admitted", at: tick.emit.at, admitted });
|
|
1983
|
+
}
|
|
1984
|
+
catch (err) {
|
|
1985
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1986
|
+
process.stderr.write(`hq lanes watcher: queue admission failed after ${laneId}: ${message}\n`);
|
|
1987
|
+
logDecision({ event: "queue_admission_failed", at: tick.emit.at, error: message });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1948
1990
|
}
|
|
1949
1991
|
}
|
|
1950
1992
|
lastSignalMs = tick.nextLastSignalMs;
|
|
@@ -62,12 +62,12 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
62
62
|
],
|
|
63
63
|
"daemon-logs-follow": [],
|
|
64
64
|
"mesh-daemon": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
65
|
-
"lanes-watch": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
66
|
-
"monitor-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
67
|
-
"monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
65
|
+
"lanes-watch": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
66
|
+
"monitor-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
67
|
+
"monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
68
68
|
"agents-message-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
69
69
|
"integrations-oauth-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
70
|
-
"outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
70
|
+
"outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
71
71
|
"secrets-sandbox": [],
|
|
72
72
|
"pkg-update": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../utils/cognito-session.js", "@indigoai-us/hq-cloud"],
|
|
73
73
|
"pack-install": [
|
|
@@ -255,6 +255,8 @@ const LOADERS = {
|
|
|
255
255
|
() => import("../utils/cognito-session.js"),
|
|
256
256
|
() => import("../lib/first-sync-next-step.js"),
|
|
257
257
|
() => import("@indigoai-us/hq-cloud"),
|
|
258
|
+
// The watcher admits queued lanes in-process after a lane exits.
|
|
259
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
258
260
|
],
|
|
259
261
|
"monitor-wait": [
|
|
260
262
|
async () => {
|
|
@@ -269,6 +271,8 @@ const LOADERS = {
|
|
|
269
271
|
() => import("../utils/cognito-session.js"),
|
|
270
272
|
() => import("../lib/first-sync-next-step.js"),
|
|
271
273
|
() => import("@indigoai-us/hq-cloud"),
|
|
274
|
+
// The watcher admits queued lanes in-process after a lane exits.
|
|
275
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
272
276
|
],
|
|
273
277
|
"monitor-run": [
|
|
274
278
|
() => import("../commands/lanes.js"),
|
|
@@ -284,6 +288,8 @@ const LOADERS = {
|
|
|
284
288
|
() => import("../utils/cognito-session.js"),
|
|
285
289
|
() => import("../lib/first-sync-next-step.js"),
|
|
286
290
|
() => import("@indigoai-us/hq-cloud"),
|
|
291
|
+
// The watcher admits queued lanes in-process after a lane exits.
|
|
292
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
287
293
|
],
|
|
288
294
|
"agents-message-wait": [
|
|
289
295
|
async () => {
|
|
@@ -327,6 +333,8 @@ const LOADERS = {
|
|
|
327
333
|
() => import("../utils/cognito-session.js"),
|
|
328
334
|
() => import("../lib/first-sync-next-step.js"),
|
|
329
335
|
() => import("@indigoai-us/hq-cloud"),
|
|
336
|
+
// The watcher admits queued lanes in-process after a lane exits.
|
|
337
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
330
338
|
],
|
|
331
339
|
"secrets-sandbox": [],
|
|
332
340
|
"pkg-update": [
|