@indigoai-us/hq-cli 5.345.128 → 5.345.130
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 +22 -0
- package/assets/scaffold/core/scripts/lib/codex-output-schema.mjs +304 -0
- package/assets/scaffold/core/scripts/workflow-runner.mjs +2890 -0
- package/dist/command-catalog.generated.d.ts +29 -0
- package/dist/command-catalog.generated.js +34 -0
- package/dist/commands/core-bucket-c-work-mesh-offer-native.js +8 -2
- package/dist/commands/core-pipeline-workflow-runner-native.d.ts +175 -0
- package/dist/commands/core-pipeline-workflow-runner-native.js +3237 -0
- package/dist/commands/core-workflow-run-command.d.ts +4 -0
- package/dist/commands/core-workflow-run-command.js +41 -0
- package/dist/commands/core.js +4 -0
- package/dist/commands/daemon.js +2 -0
- package/dist/commands/lanes-accounts.js +0 -1
- package/dist/commands/lanes.js +26 -14
- package/dist/commands/mesh.js +32 -19
- package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +3 -5
- package/dist/lib/bot/inbox-poll-backoff-flag.js +20 -12
- package/dist/lib/bot/inbox-wake.d.ts +18 -0
- package/dist/lib/bot/inbox-wake.js +125 -0
- package/dist/lib/bot/run.d.ts +8 -0
- package/dist/lib/bot/run.js +51 -7
- package/dist/lib/bot/runtime/messages-stream.js +31 -3
- package/dist/lib/core-utils/workflow-runner-schema.d.ts +7 -0
- package/dist/lib/core-utils/workflow-runner-schema.js +259 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +6 -3
- package/dist/lib/daemon/flag-monitor.d.ts +4 -1
- package/dist/lib/daemon/flag-monitor.js +12 -6
- package/dist/lib/daemon/flag.d.ts +2 -0
- package/dist/lib/daemon/flag.js +11 -5
- package/dist/lib/daemon/hq-child-bootstrap.mjs +47 -7
- package/dist/lib/daemon/kill-switch-flags.d.ts +5 -0
- package/dist/lib/daemon/kill-switch-flags.js +5 -0
- package/dist/lib/daemon/lanes-reconcile-loader.d.ts +7 -0
- package/dist/lib/daemon/lanes-reconcile-loader.js +24 -0
- package/dist/lib/daemon/lanes-reconcile-watcher.d.ts +17 -0
- package/dist/lib/daemon/lanes-reconcile-watcher.js +80 -0
- package/dist/lib/daemon/remote-control.d.ts +8 -5
- package/dist/lib/daemon/remote-control.js +8 -7
- package/dist/lib/daemon/run.d.ts +4 -1
- package/dist/lib/daemon/run.js +133 -20
- package/dist/lib/doctor/json-output.d.ts +15 -3
- package/dist/lib/doctor/json-output.js +13 -1
- package/dist/lib/lanes/config.d.ts +8 -4
- package/dist/lib/lanes/config.js +30 -36
- package/dist/lib/lanes/daemon-reconcile.d.ts +3 -0
- package/dist/lib/lanes/daemon-reconcile.js +8 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/reconcile-result.d.ts +10 -0
- package/dist/lib/lanes/reconcile-result.js +11 -0
- package/dist/lib/lanes/reconcile.js +3 -26
- package/dist/lib/lanes/throttle.js +5 -9
- package/dist/lib/lanes/types.d.ts +4 -5
- package/dist/lib/lanes/types.js +1 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/work-subscribe.js +8 -1
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +2 -0
- package/dist/lib/mesh/story-status.d.ts +2 -2
- package/dist/lib/mesh/story-status.js +2 -1
- package/dist/utils/resident-package-preload.js +6 -0
- package/package.json +1 -1
package/dist/lib/lanes/config.js
CHANGED
|
@@ -13,10 +13,14 @@ import { parseConfigEffort } from "./effort.js";
|
|
|
13
13
|
import { LanesConfigError } from "./errors.js";
|
|
14
14
|
import { CONFIG_SCHEMA_VERSION, LANE_PROVIDERS, isLaneProvider, parseMajorSchema, } from "./types.js";
|
|
15
15
|
export const LANES_CONFIG_REL = path.join("personal", "lanes", "config.json");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Owner directive 2026-10-14: no default lane caps or CPU/memory admission
|
|
18
|
+
* gates. A provider has no running or queued limit unless the operator sets
|
|
19
|
+
* `max_running` / `max_queued` explicitly; this value stands for "no limit"
|
|
20
|
+
* and stays a finite integer so it survives JSON output.
|
|
21
|
+
*/
|
|
22
|
+
export const UNLIMITED_LANES = Number.MAX_SAFE_INTEGER;
|
|
23
|
+
export const DEFAULT_MAX_QUEUED = UNLIMITED_LANES;
|
|
20
24
|
/**
|
|
21
25
|
* Owner directive 2026-09-22: "if it's been an hour since last msg, always
|
|
22
26
|
* run compact". Override per provider with `compact_after_ms`.
|
|
@@ -69,9 +73,9 @@ export const DEFAULT_LIVENESS_WINDOWS = {
|
|
|
69
73
|
silent_lost_ms: DEFAULT_SILENT_LOST_MS,
|
|
70
74
|
};
|
|
71
75
|
export const DEFAULT_PROVIDER_MAX_RUNNING = {
|
|
72
|
-
claude:
|
|
73
|
-
codex:
|
|
74
|
-
grok:
|
|
76
|
+
claude: UNLIMITED_LANES,
|
|
77
|
+
codex: UNLIMITED_LANES,
|
|
78
|
+
grok: UNLIMITED_LANES,
|
|
75
79
|
};
|
|
76
80
|
export const DEFAULT_ACCOUNT_HOMES = {
|
|
77
81
|
claude: "~/.claude",
|
|
@@ -152,14 +156,6 @@ function positiveInt(value, field, fallback) {
|
|
|
152
156
|
}
|
|
153
157
|
return value;
|
|
154
158
|
}
|
|
155
|
-
function nonNegativeInt(value, field, fallback) {
|
|
156
|
-
if (value === undefined)
|
|
157
|
-
return fallback;
|
|
158
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
159
|
-
throw new LanesConfigError("invalid_config", `${field} must be a non-negative integer`, { field, value });
|
|
160
|
-
}
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
159
|
function positiveNumber(value, field, fallback) {
|
|
164
160
|
if (value === undefined)
|
|
165
161
|
return fallback;
|
|
@@ -168,9 +164,9 @@ function positiveNumber(value, field, fallback) {
|
|
|
168
164
|
}
|
|
169
165
|
return value;
|
|
170
166
|
}
|
|
171
|
-
function
|
|
167
|
+
function optionalUnitInterval(value, field) {
|
|
172
168
|
if (value === undefined)
|
|
173
|
-
return
|
|
169
|
+
return undefined;
|
|
174
170
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
175
171
|
throw new LanesConfigError("invalid_config", `${field} must be a number in [0, 1]`, { field, value });
|
|
176
172
|
}
|
|
@@ -377,30 +373,35 @@ function parseProvider(provider, raw, deps) {
|
|
|
377
373
|
}
|
|
378
374
|
return parsed;
|
|
379
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* CPU and memory admission gates are opt-in. With no `throttle` keys set,
|
|
378
|
+
* admission never waits on CPU load or free memory.
|
|
379
|
+
*/
|
|
380
380
|
function parseThrottle(raw) {
|
|
381
381
|
const obj = raw === undefined ? {} : raw;
|
|
382
382
|
if (!isPlainObject(obj)) {
|
|
383
383
|
throw new LanesConfigError("invalid_config", "throttle must be an object");
|
|
384
384
|
}
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
385
|
+
const result = {};
|
|
386
|
+
const pause = optionalUnitInterval(obj.cpu_busy_pause, "throttle.cpu_busy_pause");
|
|
387
|
+
const resumeRaw = optionalUnitInterval(obj.cpu_busy_resume, "throttle.cpu_busy_resume");
|
|
388
|
+
if (pause !== undefined) {
|
|
389
|
+
const resume = resumeRaw ?? pause;
|
|
390
|
+
if (resume > pause) {
|
|
391
|
+
throw new LanesConfigError("invalid_config", "throttle.cpu_busy_resume must be <= throttle.cpu_busy_pause", { cpu_busy_pause: pause, cpu_busy_resume: resume });
|
|
392
|
+
}
|
|
393
|
+
result.cpu_busy_pause = pause;
|
|
394
|
+
result.cpu_busy_resume = resume;
|
|
389
395
|
}
|
|
390
|
-
let minMem = DEFAULT_MIN_AVAILABLE_MEM_MB;
|
|
391
396
|
if (obj.min_available_mem_mb !== undefined) {
|
|
392
397
|
if (typeof obj.min_available_mem_mb !== "number" ||
|
|
393
398
|
!Number.isFinite(obj.min_available_mem_mb) ||
|
|
394
399
|
obj.min_available_mem_mb < 0) {
|
|
395
400
|
throw new LanesConfigError("invalid_config", "throttle.min_available_mem_mb must be a non-negative number", { value: obj.min_available_mem_mb });
|
|
396
401
|
}
|
|
397
|
-
|
|
402
|
+
result.min_available_mem_mb = obj.min_available_mem_mb;
|
|
398
403
|
}
|
|
399
|
-
return
|
|
400
|
-
cpu_busy_pause: pause,
|
|
401
|
-
cpu_busy_resume: resume,
|
|
402
|
-
min_available_mem_mb: minMem,
|
|
403
|
-
};
|
|
404
|
+
return result;
|
|
404
405
|
}
|
|
405
406
|
function parseLiveness(raw) {
|
|
406
407
|
const obj = raw === undefined ? {} : raw;
|
|
@@ -458,16 +459,11 @@ export function documentedDefaultConfig(deps = defaultDeps()) {
|
|
|
458
459
|
return {
|
|
459
460
|
schema_version: CONFIG_SCHEMA_VERSION,
|
|
460
461
|
providers,
|
|
461
|
-
throttle: {
|
|
462
|
-
cpu_busy_pause: DEFAULT_CPU_BUSY_PAUSE,
|
|
463
|
-
cpu_busy_resume: DEFAULT_CPU_BUSY_RESUME,
|
|
464
|
-
min_available_mem_mb: DEFAULT_MIN_AVAILABLE_MEM_MB,
|
|
465
|
-
},
|
|
462
|
+
throttle: {},
|
|
466
463
|
liveness: { ...DEFAULT_LIVENESS_WINDOWS },
|
|
467
464
|
tracker: { checks: { ...DEFAULT_TRACKER_CHECKS } },
|
|
468
465
|
runaway_search_guard: { ...DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG },
|
|
469
466
|
low_disk_min_free_bytes: DEFAULT_LOW_DISK_MIN_FREE_BYTES,
|
|
470
|
-
lead_reserve_per_host: 1,
|
|
471
467
|
codex_rollout_compress_after_hours: DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS,
|
|
472
468
|
codex_rollout_delete_after_days: DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS,
|
|
473
469
|
session_question_timeout_secs: DEFAULT_SESSION_QUESTION_TIMEOUT_SECS,
|
|
@@ -508,7 +504,6 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
|
|
|
508
504
|
}
|
|
509
505
|
const compressAfterHours = positiveInt(raw.codex_rollout_compress_after_hours, "codex_rollout_compress_after_hours", DEFAULT_CODEX_ROLLOUT_COMPRESS_AFTER_HOURS);
|
|
510
506
|
const deleteAfterDays = positiveInt(raw.codex_rollout_delete_after_days, "codex_rollout_delete_after_days", DEFAULT_CODEX_ROLLOUT_DELETE_AFTER_DAYS);
|
|
511
|
-
const leadReservePerHost = nonNegativeInt(raw.lead_reserve_per_host, "lead_reserve_per_host", 1);
|
|
512
507
|
if (deleteAfterDays * 24 < compressAfterHours) {
|
|
513
508
|
throw new LanesConfigError("invalid_config", "codex_rollout_delete_after_days must be at least codex_rollout_compress_after_hours / 24", { codex_rollout_compress_after_hours: compressAfterHours, codex_rollout_delete_after_days: deleteAfterDays });
|
|
514
509
|
}
|
|
@@ -522,7 +517,6 @@ export function parseLanesConfig(raw, deps = defaultDeps()) {
|
|
|
522
517
|
tracker: { checks: parseTrackerChecks(raw.tracker) },
|
|
523
518
|
runaway_search_guard: parseRunawaySearchGuard(raw.runaway_search_guard),
|
|
524
519
|
low_disk_min_free_bytes: positiveInt(raw.low_disk_min_free_bytes, "low_disk_min_free_bytes", DEFAULT_LOW_DISK_MIN_FREE_BYTES),
|
|
525
|
-
lead_reserve_per_host: leadReservePerHost,
|
|
526
520
|
codex_rollout_compress_after_hours: compressAfterHours,
|
|
527
521
|
codex_rollout_delete_after_days: deleteAfterDays,
|
|
528
522
|
session_question_timeout_secs: positiveInt(raw.session_question_timeout_secs, "session_question_timeout_secs", DEFAULT_SESSION_QUESTION_TIMEOUT_SECS),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
let daemonReconcileHandler = null;
|
|
2
|
+
export function registerDaemonLanesReconcileHandler(handler) {
|
|
3
|
+
daemonReconcileHandler = handler;
|
|
4
|
+
}
|
|
5
|
+
export function daemonLanesReconcileHandler() {
|
|
6
|
+
return daemonReconcileHandler;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=daemon-reconcile.js.map
|
|
@@ -12,7 +12,7 @@ export type { HostCapacityAccount, HostCapacityProvider, HostCapacitySnapshot, H
|
|
|
12
12
|
export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
|
|
13
13
|
export type { AccountConfig, AttributionHistoryEntry, EnvelopeDecision, EnvelopeQuestion, HostKind, LaneCompanyRef, LaneAccountSelection, LaneAccountSelectionConsidered, LaneAccountSelectionHostCount, LaneEnvelope, LaneHost, LaneProvider, LanePlacementPolicy, LaneRecord, LaneStory, LaneStoryStatus, PendingResume, AccountHistoryEntry, LaneState, LanesConfig, LastEnvelopeRef, LivenessState, LivenessWindows, ProviderConfig, QuestionAnswer, QuestionBodyRef, QuestionPriority, QuestionRecord, QuestionStatus, QueuedReason, RaisedVia, RoutingHistoryEntry, RoutingReason, SeniorHistoryEntry, SeniorKind, SeniorRef, WorkerProfileRepinHistoryEntry, LaneSettingsHistoryEntry, StoryStatus, TerminalLaneState, ThrottleConfig, } from "./types.js";
|
|
14
14
|
export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
|
|
15
|
-
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS,
|
|
15
|
+
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
|
|
16
16
|
export type { ConfigLoadDeps, ConfigWriteDeps } from "./config.js";
|
|
17
17
|
export { resolveLanesHqRoot } from "./hq-root.js";
|
|
18
18
|
export type { LanesHqRootOpts } from "./hq-root.js";
|
package/dist/lib/lanes/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingR
|
|
|
8
8
|
export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
|
|
9
9
|
export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
|
|
10
10
|
export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
|
|
11
|
-
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS,
|
|
11
|
+
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
|
|
12
12
|
export { resolveLanesHqRoot } from "./hq-root.js";
|
|
13
13
|
export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
|
|
14
14
|
export { buildOutpostPickupCommand, parseOutpostPickupPayload, } from "./outpost-dispatch.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ReconcilePassResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
errors?: readonly {
|
|
4
|
+
code?: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}[];
|
|
7
|
+
}
|
|
8
|
+
/** Preserve CLI exit semantics while letting the daemon report failures to its watcher. */
|
|
9
|
+
export declare function finishReconcilePass(summary: ReconcilePassResult, mode: "cli" | "daemon", exit?: (code: number) => void): void;
|
|
10
|
+
//# sourceMappingURL=reconcile-result.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Preserve CLI exit semantics while letting the daemon report failures to its watcher. */
|
|
2
|
+
export function finishReconcilePass(summary, mode, exit = (code) => process.exit(code)) {
|
|
3
|
+
if (summary.ok)
|
|
4
|
+
return;
|
|
5
|
+
if (mode === "daemon") {
|
|
6
|
+
const detail = summary.errors?.map((error) => `${error.code ?? "unknown"}: ${error.message}`).join("; ") ?? "unknown reconcile error";
|
|
7
|
+
throw new Error(`In-process lanes reconcile failed: ${detail}`);
|
|
8
|
+
}
|
|
9
|
+
exit(1);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=reconcile-result.js.map
|
|
@@ -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,
|
|
11
|
+
import { compareLaneQueueOrder, compareLaneQueueOrderWithLeads, 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";
|
|
@@ -33,7 +33,7 @@ import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolG
|
|
|
33
33
|
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, 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, dueKeepAliveNextCheckMs, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
34
34
|
import { isFailedQuotaKeepAliveLane, providerSupportsSessionResume } from "./delivery.js";
|
|
35
35
|
import { LANE_PROVIDERS, isIsoDateTime, isTerminalLaneState } from "./types.js";
|
|
36
|
-
import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort,
|
|
36
|
+
import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
37
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";
|
|
@@ -2045,7 +2045,6 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
|
|
|
2045
2045
|
if (localQueued.length === 0 || deps.skipAdmissionWithoutCpuSample === true)
|
|
2046
2046
|
return;
|
|
2047
2047
|
const config = loadLanesConfig(hqRoot);
|
|
2048
|
-
const admissionQuotaState = loadQuotaState(hqRoot);
|
|
2049
2048
|
const candidateLeadIds = leadLaneIds(lanes);
|
|
2050
2049
|
const flagReader = deps.readRegistryFlag ?? readLanesRegistryFlag;
|
|
2051
2050
|
const leadAdmissionEnabledById = new Map();
|
|
@@ -2140,25 +2139,6 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
|
|
|
2140
2139
|
}
|
|
2141
2140
|
}
|
|
2142
2141
|
const projected = [...running, ...spawned];
|
|
2143
|
-
const laneResolution = lane.provider === "claude" ? resolveLaneCompany(hqRoot, lane) : undefined;
|
|
2144
|
-
const laneCompany = laneResolution?.company ?? undefined;
|
|
2145
|
-
const laneCompanyKey = laneCompany?.uid ?? laneCompany?.slug ?? lane.company?.uid ?? lane.company?.slug;
|
|
2146
|
-
let reserveEnabled = false;
|
|
2147
|
-
if (lane.provider === "claude" && laneCompanyKey) {
|
|
2148
|
-
let enabled = leadFlagByCompany.get(laneCompanyKey);
|
|
2149
|
-
if (!enabled) {
|
|
2150
|
-
enabled = readLanesRegistryFlagFailOff(LANE_LEAD_ADMISSION_PRIORITY_FLAG, {
|
|
2151
|
-
company: laneCompany,
|
|
2152
|
-
...(laneCompany && laneResolution ? { companyUidSource: laneCompanyUidSource(laneResolution.source) } : {}),
|
|
2153
|
-
env: deps.env,
|
|
2154
|
-
}, flagReader);
|
|
2155
|
-
leadFlagByCompany.set(laneCompanyKey, enabled);
|
|
2156
|
-
}
|
|
2157
|
-
reserveEnabled = await enabled;
|
|
2158
|
-
}
|
|
2159
|
-
const reserveBlocked = reserveEnabled && lane.provider === "claude" &&
|
|
2160
|
-
!priorityLeads.has(lane.lane_id) &&
|
|
2161
|
-
freeClaudeAccountSlots(projected, config, deps.host, now.getTime(), admissionQuotaState) <= config.lead_reserve_per_host;
|
|
2162
2142
|
const alternateAccount = availableBalancedAccount(hqRoot, lane, projected, config, deps.host, profileMaxConcurrent, now.getTime());
|
|
2163
2143
|
const admittedLane = alternateAccount !== undefined
|
|
2164
2144
|
? writeAdmissionAccount(hqRoot, lane, alternateAccount, deps.dryRun)
|
|
@@ -2166,10 +2146,7 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
|
|
|
2166
2146
|
const baseCap = laneAdmissionCapReason(running, admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
|
|
2167
2147
|
const currentCap = laneAdmissionCapReason([...running, ...spawned], admittedLane, config, deps.host, profileMaxConcurrent, now.getTime());
|
|
2168
2148
|
let blockedBy;
|
|
2169
|
-
if (
|
|
2170
|
-
blockedBy = "lead_reserve";
|
|
2171
|
-
}
|
|
2172
|
-
else if (currentCap !== undefined) {
|
|
2149
|
+
if (currentCap !== undefined) {
|
|
2173
2150
|
blockedBy = baseCap ?? "queue_position";
|
|
2174
2151
|
}
|
|
2175
2152
|
if (!blockedBy && !decision.admit)
|
|
@@ -13,7 +13,6 @@ import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as os from "node:os";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
-
import { DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_MIN_AVAILABLE_MEM_MB, } from "./config.js";
|
|
17
16
|
// Reconcile normally measures the interval between cumulative CPU snapshots.
|
|
18
17
|
// A five-second window establishes a baseline or recovers from stale counters.
|
|
19
18
|
export const CPU_SAMPLE_MS = 5_000;
|
|
@@ -294,17 +293,18 @@ export function evaluateThrottle(input) {
|
|
|
294
293
|
const pause = input.throttle.cpu_busy_pause;
|
|
295
294
|
const resume = input.throttle.cpu_busy_resume;
|
|
296
295
|
const minMem = input.throttle.min_available_mem_mb;
|
|
296
|
+
// Each gate is opt-in: an unset threshold never blocks admission.
|
|
297
297
|
let cpuBlocks = false;
|
|
298
|
-
if (input.cpuCheckEnabled && input.busy !== null) {
|
|
298
|
+
if (pause !== undefined && input.cpuCheckEnabled && input.busy !== null) {
|
|
299
299
|
if (input.state.cpuPaused) {
|
|
300
|
-
cpuBlocks = input.busy > resume;
|
|
300
|
+
cpuBlocks = input.busy > (resume ?? pause);
|
|
301
301
|
}
|
|
302
302
|
else {
|
|
303
303
|
cpuBlocks = input.busy >= pause;
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
let memBlocks = false;
|
|
307
|
-
if (input.availableMemMb !== null) {
|
|
307
|
+
if (minMem !== undefined && input.availableMemMb !== null) {
|
|
308
308
|
memBlocks = input.availableMemMb < minMem;
|
|
309
309
|
}
|
|
310
310
|
if (!cpuBlocks && !memBlocks) {
|
|
@@ -448,11 +448,7 @@ export function writeThrottleState(filePath, state, deps = defaultThrottleDeps()
|
|
|
448
448
|
throw err;
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
-
export async function decideAdmission(hqRoot, throttle = {
|
|
452
|
-
cpu_busy_pause: DEFAULT_CPU_BUSY_PAUSE,
|
|
453
|
-
cpu_busy_resume: DEFAULT_CPU_BUSY_RESUME,
|
|
454
|
-
min_available_mem_mb: DEFAULT_MIN_AVAILABLE_MEM_MB,
|
|
455
|
-
}, deps = defaultThrottleDeps(), cpuBusyFraction, cpuObservation) {
|
|
451
|
+
export async function decideAdmission(hqRoot, throttle = {}, deps = defaultThrottleDeps(), cpuBusyFraction, cpuObservation) {
|
|
456
452
|
const filePath = throttleStatePath(hqRoot);
|
|
457
453
|
const loaded = readThrottleState(filePath, deps);
|
|
458
454
|
if (loaded === "unreadable") {
|
|
@@ -1055,10 +1055,11 @@ export interface ProviderConfig {
|
|
|
1055
1055
|
quota_default_hold_ms?: number;
|
|
1056
1056
|
accounts: Record<string, AccountConfig>;
|
|
1057
1057
|
}
|
|
1058
|
+
/** Opt-in admission gates. An unset key means that gate is off. */
|
|
1058
1059
|
export interface ThrottleConfig {
|
|
1059
|
-
cpu_busy_pause
|
|
1060
|
-
cpu_busy_resume
|
|
1061
|
-
min_available_mem_mb
|
|
1060
|
+
cpu_busy_pause?: number;
|
|
1061
|
+
cpu_busy_resume?: number;
|
|
1062
|
+
min_available_mem_mb?: number;
|
|
1062
1063
|
}
|
|
1063
1064
|
/** Session liveness windows from spec §9. Missing keys keep these documented defaults. */
|
|
1064
1065
|
export interface LivenessWindows {
|
|
@@ -1096,8 +1097,6 @@ export interface LanesConfig {
|
|
|
1096
1097
|
runaway_search_guard?: RunawaySearchGuardConfig;
|
|
1097
1098
|
/** Optional free-space threshold override for the low-disk admission gate. */
|
|
1098
1099
|
low_disk_min_free_bytes?: number;
|
|
1099
|
-
/** Claude account slots kept available per host for senior lead lanes. */
|
|
1100
|
-
lead_reserve_per_host: number;
|
|
1101
1100
|
/** Retention ages for Codex session rollout maintenance. */
|
|
1102
1101
|
codex_rollout_compress_after_hours?: number;
|
|
1103
1102
|
codex_rollout_delete_after_days?: number;
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -14,7 +14,7 @@ export interface WorkCacheMqtt extends MeshMqttEventClient {
|
|
|
14
14
|
export interface WorkCacheSubscriber {
|
|
15
15
|
stop: () => Promise<void>;
|
|
16
16
|
}
|
|
17
|
-
export
|
|
17
|
+
export { AGENT_INBOX_PUSH_STATE_BASENAME } from "../paths.js";
|
|
18
18
|
export declare function startWorkCacheSubscriber(opts: {
|
|
19
19
|
meshRoot: string;
|
|
20
20
|
fetchCredentials?: PersonalCredentialsFetcher;
|
|
@@ -8,7 +8,8 @@ import { createPersonalRealtimeFetcher, } from "./credentials.js";
|
|
|
8
8
|
import { presignIotWssUrl } from "./presign.js";
|
|
9
9
|
import { applyProjectViewPush, parseProjectViewPush } from "./live-cache.js";
|
|
10
10
|
import { backoffDelayMs, RECONNECT_MIN_DELAY_MS } from "./presence.js";
|
|
11
|
-
|
|
11
|
+
import { AGENT_INBOX_PUSH_STATE_BASENAME } from "../paths.js";
|
|
12
|
+
export { AGENT_INBOX_PUSH_STATE_BASENAME } from "../paths.js";
|
|
12
13
|
const INBOX_PUSH_HEARTBEAT_MS = 5_000;
|
|
13
14
|
function readInboxWakeSequence(meshRoot, onInfo) {
|
|
14
15
|
const target = path.join(meshRoot, AGENT_INBOX_PUSH_STATE_BASENAME);
|
|
@@ -78,6 +79,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
78
79
|
let heartbeatTimer = null;
|
|
79
80
|
let generation = 0;
|
|
80
81
|
let inboxSubscribed = false;
|
|
82
|
+
let inboxAgentUid;
|
|
81
83
|
// Carry the sequence forward from the last state marker so a daemon restart
|
|
82
84
|
// cannot reuse a value the watcher has already consumed.
|
|
83
85
|
let wakeSequence = readInboxWakeSequence(opts.meshRoot, opts.onInfo);
|
|
@@ -92,6 +94,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
92
94
|
connected,
|
|
93
95
|
heartbeatAtMs: now().getTime(),
|
|
94
96
|
wakeSequence,
|
|
97
|
+
...(connected && inboxAgentUid ? { agentUid: inboxAgentUid } : {}),
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
catch (error) {
|
|
@@ -115,6 +118,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
115
118
|
if (stopped)
|
|
116
119
|
return;
|
|
117
120
|
inboxSubscribed = false;
|
|
121
|
+
inboxAgentUid = undefined;
|
|
118
122
|
clearInboxHeartbeat();
|
|
119
123
|
persistInboxState(false);
|
|
120
124
|
const gen = ++generation;
|
|
@@ -133,6 +137,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
133
137
|
bundle.topics.inbox === `hq/${bundle.actorUid}/inbox`
|
|
134
138
|
? bundle.topics.inbox
|
|
135
139
|
: undefined;
|
|
140
|
+
inboxAgentUid = inboxTopic ? bundle.actorUid : undefined;
|
|
136
141
|
if (!topic && !inboxTopic) {
|
|
137
142
|
opts.onInfo?.("work cache subscribe skipped: credentials have no supported topic");
|
|
138
143
|
return;
|
|
@@ -175,6 +180,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
175
180
|
return;
|
|
176
181
|
if (err) {
|
|
177
182
|
inboxSubscribed = false;
|
|
183
|
+
inboxAgentUid = undefined;
|
|
178
184
|
clearInboxHeartbeat();
|
|
179
185
|
persistInboxState(false);
|
|
180
186
|
opts.onInfo?.("agent inbox push subscribe failed");
|
|
@@ -212,6 +218,7 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
212
218
|
if (stopped || gen !== generation)
|
|
213
219
|
return;
|
|
214
220
|
inboxSubscribed = false;
|
|
221
|
+
inboxAgentUid = undefined;
|
|
215
222
|
clearInboxHeartbeat();
|
|
216
223
|
persistInboxState(false);
|
|
217
224
|
schedule();
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Local Work Mesh Live paths under ~/.hq/work-mesh/.
|
|
3
3
|
* Injectable root for tests (never touch the real HOME in unit tests).
|
|
4
4
|
*/
|
|
5
|
+
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
6
|
+
export declare const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
5
7
|
export declare function workMeshRoot(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
6
8
|
export declare function workMeshSpoolPath(root?: string): string;
|
|
7
9
|
export declare function workMeshHeldPath(root?: string): string;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
const WORK_MESH_DIRNAME = "work-mesh";
|
|
8
|
+
/** Content-free sequence file written by the mesh MQTT inbox subscriber. */
|
|
9
|
+
export const AGENT_INBOX_PUSH_STATE_BASENAME = "agent-inbox-push.json";
|
|
8
10
|
export function workMeshRoot(home = os.homedir(), env = process.env) {
|
|
9
11
|
const override = env.HQ_WORK_MESH_ROOT?.trim();
|
|
10
12
|
if (override)
|
|
@@ -5,8 +5,8 @@ export type BoardStoryStatus = (typeof BOARD_STORY_STATUSES)[number];
|
|
|
5
5
|
* need attention, while `dropped` is terminal; the Board has only one terminal
|
|
6
6
|
* column (`done`).
|
|
7
7
|
*/
|
|
8
|
-
export declare const LEDGER_STATES: readonly ["todo", "in_progress", "review", "waiting", "blocked", "done", "dropped"];
|
|
9
|
-
export declare const ACCEPTED_LEDGER_STATES: readonly ["todo", "in_progress", "review", "waiting", "blocked", "done", "dropped", "in_review", "verification"];
|
|
8
|
+
export declare const LEDGER_STATES: readonly ["todo", "queued", "in_progress", "review", "waiting", "blocked", "done", "dropped"];
|
|
9
|
+
export declare const ACCEPTED_LEDGER_STATES: readonly ["todo", "queued", "in_progress", "review", "waiting", "blocked", "done", "dropped", "in_review", "verification"];
|
|
10
10
|
export declare class UnknownProjectStoryStateError extends Error {
|
|
11
11
|
readonly source: string;
|
|
12
12
|
readonly storyId: string;
|
|
@@ -6,10 +6,11 @@ const boardStoryStatusSet = new Set(BOARD_STORY_STATUSES);
|
|
|
6
6
|
* column (`done`).
|
|
7
7
|
*/
|
|
8
8
|
export const LEDGER_STATES = [
|
|
9
|
-
"todo", "in_progress", "review", "waiting", "blocked", "done", "dropped",
|
|
9
|
+
"todo", "queued", "in_progress", "review", "waiting", "blocked", "done", "dropped",
|
|
10
10
|
];
|
|
11
11
|
const LEDGER_STATE_TO_BOARD_STATUS = {
|
|
12
12
|
todo: "queued",
|
|
13
|
+
queued: "queued",
|
|
13
14
|
in_progress: "in_progress",
|
|
14
15
|
review: "review",
|
|
15
16
|
// The four-column Board cannot represent waiting separately. Preserve the
|
|
@@ -53,12 +53,15 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
53
53
|
"../lib/plan-limit-nag.js",
|
|
54
54
|
"../utils/plan-gate-error.js",
|
|
55
55
|
"../lib/daemon/mcp-oauth.js",
|
|
56
|
+
"../commands/lanes.js",
|
|
57
|
+
"commander",
|
|
56
58
|
"@modelcontextprotocol/sdk/client/index.js",
|
|
57
59
|
"@modelcontextprotocol/sdk/client/streamableHttp.js",
|
|
58
60
|
"../utils/staged-npm-install.js",
|
|
59
61
|
"open",
|
|
60
62
|
"../lib/first-sync-next-step.js",
|
|
61
63
|
"@indigoai-us/hq-cloud",
|
|
64
|
+
"../lib/lanes/reconcile.js",
|
|
62
65
|
],
|
|
63
66
|
"daemon-logs-follow": [],
|
|
64
67
|
"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"],
|
|
@@ -220,12 +223,15 @@ const LOADERS = {
|
|
|
220
223
|
() => import("../lib/plan-limit-nag.js"),
|
|
221
224
|
() => import("../utils/plan-gate-error.js"),
|
|
222
225
|
() => import("../lib/daemon/mcp-oauth.js"),
|
|
226
|
+
() => import("../commands/lanes.js"),
|
|
227
|
+
() => import("commander"),
|
|
223
228
|
() => import("@modelcontextprotocol/sdk/client/index.js"),
|
|
224
229
|
() => import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
225
230
|
() => import("../utils/staged-npm-install.js"),
|
|
226
231
|
() => import("open"),
|
|
227
232
|
() => import("../lib/first-sync-next-step.js"),
|
|
228
233
|
() => import("@indigoai-us/hq-cloud"),
|
|
234
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
229
235
|
],
|
|
230
236
|
"daemon-logs-follow": [],
|
|
231
237
|
"mesh-daemon": [
|