@indigoai-us/hq-cli 5.149.1 → 5.151.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 +10 -0
- package/dist/command-catalog.generated.d.ts +19 -0
- package/dist/command-catalog.generated.js +25 -0
- package/dist/commands/lanes.js +495 -246
- package/dist/commands/report.d.ts +15 -1
- package/dist/commands/report.js +41 -6
- package/dist/lib/lanes/admission.d.ts +24 -0
- package/dist/lib/lanes/admission.js +193 -0
- package/dist/lib/lanes/delivery.js +5 -3
- package/dist/lib/lanes/index.d.ts +2 -0
- package/dist/lib/lanes/index.js +1 -0
- package/dist/lib/lanes/reconcile.js +155 -43
- package/dist/lib/lanes/spawn.js +4 -0
- package/dist/lib/lanes/store.js +48 -1
- package/dist/lib/lanes/types.d.ts +8 -1
- package/dist/lib/lanes/types.js +15 -4
- package/package.json +1 -1
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -844,6 +844,8 @@ export function markSpawnFailure(hqRoot, laneId, reasonRaw, details) {
|
|
|
844
844
|
...current,
|
|
845
845
|
state: "failed",
|
|
846
846
|
queued_reason: undefined,
|
|
847
|
+
queued_at: undefined,
|
|
848
|
+
admission_reserved_at: undefined,
|
|
847
849
|
terminal_reason: reason,
|
|
848
850
|
terminal_details: details,
|
|
849
851
|
};
|
|
@@ -1360,6 +1362,8 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1360
1362
|
...current,
|
|
1361
1363
|
state: "running",
|
|
1362
1364
|
queued_reason: undefined,
|
|
1365
|
+
queued_at: undefined,
|
|
1366
|
+
admission_reserved_at: undefined,
|
|
1363
1367
|
// An account move's reservation ends with the claim.
|
|
1364
1368
|
pending_resume: undefined,
|
|
1365
1369
|
account: resolved.id,
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -13,7 +13,7 @@ import * as path from "node:path";
|
|
|
13
13
|
import { generateUlid } from "../mesh/live/ulid.js";
|
|
14
14
|
import { resolveLanesHqRoot } from "./hq-root.js";
|
|
15
15
|
import { LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesError, isLaneVersionConflict, } from "./errors.js";
|
|
16
|
-
import { HISTORY_CAP, HOST_KINDS, LANE_STORY_STATUSES, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isLaneState, isQuestionStatus, isSeniorKind, parseMajorSchema, REQUIRED_LANE_RECORD_FIELDS, LANE_FIELD_WRITERS, LANE_WRITERS, } from "./types.js";
|
|
16
|
+
import { HISTORY_CAP, HOST_KINDS, LANE_STORY_STATUSES, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isQueuedReason, isLaneState, isQuestionStatus, isSeniorKind, parseMajorSchema, REQUIRED_LANE_RECORD_FIELDS, LANE_FIELD_WRITERS, LANE_WRITERS, } from "./types.js";
|
|
17
17
|
import { laneStories } from "./stories.js";
|
|
18
18
|
export const LANES_STORE_REL = path.join("workspace", "lanes");
|
|
19
19
|
export const LANE_RECORDS_DIR = "lanes";
|
|
@@ -245,6 +245,25 @@ function requireString(value, field) {
|
|
|
245
245
|
}
|
|
246
246
|
return value;
|
|
247
247
|
}
|
|
248
|
+
function requireTimestamp(value, field, filePath) {
|
|
249
|
+
const parsedAt = typeof value === "string" ? Date.parse(value) : NaN;
|
|
250
|
+
const datePart = typeof value === "string" ? value.slice(0, 10) : "";
|
|
251
|
+
const datePartAt = /^\d{4}-\d{2}-\d{2}$/.test(datePart)
|
|
252
|
+
? Date.parse(`${datePart}T00:00:00.000Z`)
|
|
253
|
+
: NaN;
|
|
254
|
+
if (typeof value !== "string" ||
|
|
255
|
+
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) ||
|
|
256
|
+
!Number.isFinite(parsedAt) ||
|
|
257
|
+
!Number.isFinite(datePartAt) ||
|
|
258
|
+
new Date(datePartAt).toISOString().slice(0, 10) !== datePart) {
|
|
259
|
+
throw new LaneSchemaError(`Expected ISO timestamp for ${field}`, {
|
|
260
|
+
path: filePath,
|
|
261
|
+
field,
|
|
262
|
+
value,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
248
267
|
function requireInt(value, field) {
|
|
249
268
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
250
269
|
throw new LaneSchemaError(`Expected integer for ${field}`, { field });
|
|
@@ -371,6 +390,31 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
371
390
|
requireInt(raw.priority, "priority");
|
|
372
391
|
requireString(raw.project_id, "project_id");
|
|
373
392
|
requireString(raw.story_id, "story_id");
|
|
393
|
+
requireTimestamp(raw.created_at, "created_at", filePath);
|
|
394
|
+
requireTimestamp(raw.updated_at, "updated_at", filePath);
|
|
395
|
+
if (raw.queued_reason !== undefined && !isQueuedReason(raw.queued_reason)) {
|
|
396
|
+
throw new LaneSchemaError(`Unknown queued reason ${JSON.stringify(raw.queued_reason)}`, {
|
|
397
|
+
path: filePath,
|
|
398
|
+
field: "queued_reason",
|
|
399
|
+
value: raw.queued_reason,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
if (raw.queued_at !== undefined)
|
|
403
|
+
requireTimestamp(raw.queued_at, "queued_at", filePath);
|
|
404
|
+
if (raw.admission_reserved_at !== undefined) {
|
|
405
|
+
requireTimestamp(raw.admission_reserved_at, "admission_reserved_at", filePath);
|
|
406
|
+
}
|
|
407
|
+
if (raw.admission_accounts !== undefined) {
|
|
408
|
+
if (!Array.isArray(raw.admission_accounts) ||
|
|
409
|
+
raw.admission_accounts.length === 0 ||
|
|
410
|
+
raw.admission_accounts.some((account) => typeof account !== "string" || account.trim() === "") ||
|
|
411
|
+
new Set(raw.admission_accounts).size !== raw.admission_accounts.length) {
|
|
412
|
+
throw new LaneSchemaError("admission_accounts must be a non-empty array of unique account ids", { path: filePath, field: "admission_accounts" });
|
|
413
|
+
}
|
|
414
|
+
if (typeof raw.account !== "string" || !raw.admission_accounts.includes(raw.account)) {
|
|
415
|
+
throw new LaneSchemaError("account must be present in admission_accounts", { path: filePath, field: "admission_accounts", account: raw.account });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
374
418
|
if (!isLaneState(raw.state)) {
|
|
375
419
|
throw new LaneSchemaError(`Unknown lane state ${JSON.stringify(raw.state)}`, { path: filePath, value: raw.state });
|
|
376
420
|
}
|
|
@@ -704,6 +748,9 @@ export function createLane(hqRoot, init, deps = defaultStoreDeps()) {
|
|
|
704
748
|
version: 1,
|
|
705
749
|
created_at: init.created_at ?? now,
|
|
706
750
|
updated_at: init.updated_at ?? now,
|
|
751
|
+
...(init.state === "queued"
|
|
752
|
+
? { queued_at: init.queued_at ?? init.created_at ?? now }
|
|
753
|
+
: {}),
|
|
707
754
|
senior_history: init.senior_history ?? [],
|
|
708
755
|
attribution_history: init.attribution_history ?? [],
|
|
709
756
|
};
|
|
@@ -166,8 +166,9 @@ export declare const TERMINAL_LANE_STATES: readonly ["done", "failed", "interrup
|
|
|
166
166
|
export type TerminalLaneState = (typeof TERMINAL_LANE_STATES)[number];
|
|
167
167
|
export declare const LANE_PROVIDERS: readonly ["claude", "codex", "grok"];
|
|
168
168
|
export type LaneProvider = (typeof LANE_PROVIDERS)[number];
|
|
169
|
-
export declare const QUEUED_REASONS: readonly ["cap", "queue_position", "throttle", "provider_binary_missing"];
|
|
169
|
+
export declare const QUEUED_REASONS: readonly ["cap", "provider_cap", "account_cap", "profile_cap", "queue_position", "throttle", "provider_binary_missing"];
|
|
170
170
|
export type QueuedReason = (typeof QUEUED_REASONS)[number];
|
|
171
|
+
export declare function isQueuedReason(value: unknown): value is QueuedReason;
|
|
171
172
|
export declare const HOST_KINDS: readonly ["local", "outpost"];
|
|
172
173
|
export type HostKind = (typeof HOST_KINDS)[number];
|
|
173
174
|
export declare const SENIOR_KINDS: readonly ["session", "lane", "user"];
|
|
@@ -270,6 +271,10 @@ export interface LaneRecord {
|
|
|
270
271
|
senior_history: SeniorHistoryEntry[];
|
|
271
272
|
state: LaneState;
|
|
272
273
|
queued_reason?: QueuedReason;
|
|
274
|
+
/** Time the current queue turn began; old records fall back to created_at. */
|
|
275
|
+
queued_at?: string;
|
|
276
|
+
/** Short-lived slot reservation between create persistence and worker spawn. */
|
|
277
|
+
admission_reserved_at?: string;
|
|
273
278
|
/**
|
|
274
279
|
* Set by `hq lanes pickup` when it stores an outpost lane and cleared by
|
|
275
280
|
* the account bind once the model probe has finished. While set, the
|
|
@@ -293,6 +298,8 @@ export interface LaneRecord {
|
|
|
293
298
|
worker_profile_ignored?: string[];
|
|
294
299
|
provider: LaneProvider;
|
|
295
300
|
account?: string;
|
|
301
|
+
/** Eligible accounts for a queue entry created without an explicit account. */
|
|
302
|
+
admission_accounts?: string[];
|
|
296
303
|
/**
|
|
297
304
|
* Every operator move of the lane's session to another account of the
|
|
298
305
|
* same provider (`hq lanes resume|message --account`), newest last, capped
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -95,7 +95,18 @@ export const TERMINAL_LANE_STATES = [
|
|
|
95
95
|
"killed",
|
|
96
96
|
];
|
|
97
97
|
export const LANE_PROVIDERS = ["claude", "codex", "grok"];
|
|
98
|
-
export const QUEUED_REASONS = [
|
|
98
|
+
export const QUEUED_REASONS = [
|
|
99
|
+
"cap",
|
|
100
|
+
"provider_cap",
|
|
101
|
+
"account_cap",
|
|
102
|
+
"profile_cap",
|
|
103
|
+
"queue_position",
|
|
104
|
+
"throttle",
|
|
105
|
+
"provider_binary_missing",
|
|
106
|
+
];
|
|
107
|
+
export function isQueuedReason(value) {
|
|
108
|
+
return typeof value === "string" && QUEUED_REASONS.includes(value);
|
|
109
|
+
}
|
|
99
110
|
export const HOST_KINDS = ["local", "outpost"];
|
|
100
111
|
export const SENIOR_KINDS = ["session", "lane", "user"];
|
|
101
112
|
export const ENVELOPE_DECISIONS = ["done", "ask", "blocked"];
|
|
@@ -204,16 +215,16 @@ export const LANE_FIELD_WRITERS = {
|
|
|
204
215
|
schema_version: ["creator"], lane_id: ["creator"], company: ["creator"],
|
|
205
216
|
project_id: ["command"], story_id: ["command"], stories: ["creator", "command", "watcher", "reconcile", "spawn"], thread_id: ["creator", "command", "spawn"], mesh_company: ["creator"],
|
|
206
217
|
depth: ["command"], senior: ["command"], senior_history: ["command"],
|
|
207
|
-
state: ["creator", "command", "watcher", "reconcile", "spawn"], queued_reason: ["creator", "command", "spawn", "reconcile"],
|
|
218
|
+
state: ["creator", "command", "watcher", "reconcile", "spawn"], queued_reason: ["creator", "command", "spawn", "reconcile"], queued_at: ["creator", "command", "spawn"], admission_reserved_at: ["creator", "command", "reconcile", "spawn"],
|
|
208
219
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
209
220
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
210
221
|
priority: ["command"], worker: ["creator"], worker_path: ["creator"], worker_pack: ["creator"], worker_profile: ["creator"], worker_profile_hash: ["creator", "spawn"], worker_profile_ignored: ["creator", "spawn"],
|
|
211
|
-
provider: ["creator"], account: ["creator", "spawn", "command"], account_history: ["command"], pending_resume: ["command", "spawn"], host: ["creator"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
222
|
+
provider: ["creator"], account: ["creator", "spawn", "command", "reconcile"], admission_accounts: ["creator", "command"], account_history: ["command"], pending_resume: ["command", "spawn"], host: ["creator"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
212
223
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
|
|
213
224
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn"], effort_source: ["creator", "spawn"], fast_mode: ["spawn"],
|
|
214
225
|
last_message_at: ["command"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
215
226
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
216
|
-
admitted_reason: ["reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
227
|
+
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
217
228
|
quota_reset_at: ["watcher", "reconcile"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
218
229
|
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], provider_home_at_spawn: ["spawn"], last_resume: ["spawn"], last_run_usage: ["watcher"], envelope_repair: ["watcher", "reconcile"], last_progress_event_at: ["watcher"],
|
|
219
230
|
};
|