@indigoai-us/hq-cli 5.186.0 → 5.188.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 +21 -0
- package/dist/command-catalog.generated.d.ts +43 -0
- package/dist/command-catalog.generated.js +56 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +633 -10
- package/dist/commands/mesh.js +28 -7
- package/dist/lib/lanes/claude-account-balance.d.ts +82 -0
- package/dist/lib/lanes/claude-account-balance.js +253 -0
- package/dist/lib/lanes/host-capacity.d.ts +19 -0
- package/dist/lib/lanes/host-capacity.js +68 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +9 -0
- package/dist/lib/lanes/outpost-dispatch.js +19 -0
- package/dist/lib/lanes/store.js +86 -0
- package/dist/lib/lanes/types.d.ts +48 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/mesh/live/report-summary.js +13 -0
- package/dist/lib/mesh/project-board.d.ts +9 -0
- package/dist/lib/mesh/project-board.js +9 -0
- package/package.json +1 -1
package/dist/lib/lanes/store.js
CHANGED
|
@@ -473,6 +473,9 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
473
473
|
path: filePath,
|
|
474
474
|
});
|
|
475
475
|
}
|
|
476
|
+
if (raw.account_selection !== undefined) {
|
|
477
|
+
parseLaneAccountSelection(raw.account_selection, filePath);
|
|
478
|
+
}
|
|
476
479
|
if (raw.placement_default !== undefined) {
|
|
477
480
|
const placement = raw.placement_default;
|
|
478
481
|
if (!isPlainObject(placement) ||
|
|
@@ -494,6 +497,89 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
494
497
|
// a parse/rewrite cycle (spec §5.1 field-preservation).
|
|
495
498
|
return raw;
|
|
496
499
|
}
|
|
500
|
+
function parseLaneAccountSelection(value, filePath) {
|
|
501
|
+
const failure = (field) => new LaneSchemaError(`Invalid account_selection.${field}`, {
|
|
502
|
+
path: filePath,
|
|
503
|
+
field: `account_selection.${field}`,
|
|
504
|
+
});
|
|
505
|
+
if (!isPlainObject(value))
|
|
506
|
+
throw failure("must be an object");
|
|
507
|
+
const selection = value;
|
|
508
|
+
if (typeof selection.account !== "string" || selection.account.trim() === "")
|
|
509
|
+
throw failure("account");
|
|
510
|
+
if (selection.signal !== "usage" && selection.signal !== "lane_count" && selection.signal !== "explicit")
|
|
511
|
+
throw failure("signal");
|
|
512
|
+
if (selection.reason !== "least_loaded" && selection.reason !== "explicit")
|
|
513
|
+
throw failure("reason");
|
|
514
|
+
if (typeof selection.selected_at !== "string" || !Number.isFinite(Date.parse(selection.selected_at)))
|
|
515
|
+
throw failure("selected_at");
|
|
516
|
+
const parseHost = (host, field) => {
|
|
517
|
+
if (!isPlainObject(host) || !isHostKind(host.kind) || typeof host.id !== "string" || host.id.trim() === "")
|
|
518
|
+
throw failure(field);
|
|
519
|
+
};
|
|
520
|
+
parseHost(selection.target_host, "target_host");
|
|
521
|
+
if (!Array.isArray(selection.considered) || !Array.isArray(selection.skipped_accounts) || !Array.isArray(selection.unavailable_hosts)) {
|
|
522
|
+
throw failure("arrays");
|
|
523
|
+
}
|
|
524
|
+
for (const [index, rowValue] of selection.considered.entries()) {
|
|
525
|
+
if (!isPlainObject(rowValue))
|
|
526
|
+
throw failure(`considered.${index}`);
|
|
527
|
+
const row = rowValue;
|
|
528
|
+
if (typeof row.account !== "string" || row.account.trim() === "" || typeof row.eligible !== "boolean" || typeof row.live_lanes !== "number" || !Number.isInteger(row.live_lanes) || row.live_lanes < 0 || typeof row.counts_complete !== "boolean" || !Array.isArray(row.hosts))
|
|
529
|
+
throw failure(`considered.${index}`);
|
|
530
|
+
if (row.ineligible_reason !== undefined && typeof row.ineligible_reason !== "string")
|
|
531
|
+
throw failure(`considered.${index}.ineligible_reason`);
|
|
532
|
+
if (row.usage !== null) {
|
|
533
|
+
if (!isPlainObject(row.usage))
|
|
534
|
+
throw failure(`considered.${index}.usage`);
|
|
535
|
+
for (const key of ["five_hour_percent", "weekly_percent"]) {
|
|
536
|
+
const percent = row.usage[key];
|
|
537
|
+
if (percent !== undefined && (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0 || percent > 100))
|
|
538
|
+
throw failure(`considered.${index}.usage.${key}`);
|
|
539
|
+
}
|
|
540
|
+
if (row.usage.sampled_at !== undefined && (typeof row.usage.sampled_at !== "string" || !Number.isFinite(Date.parse(row.usage.sampled_at))))
|
|
541
|
+
throw failure(`considered.${index}.usage.sampled_at`);
|
|
542
|
+
}
|
|
543
|
+
for (const [hostIndex, hostValue] of row.hosts.entries()) {
|
|
544
|
+
if (!isPlainObject(hostValue))
|
|
545
|
+
throw failure(`considered.${index}.hosts.${hostIndex}`);
|
|
546
|
+
const host = hostValue;
|
|
547
|
+
parseHost(host.host, `considered.${index}.hosts.${hostIndex}.host`);
|
|
548
|
+
if (typeof host.enabled !== "boolean" || !(host.logged_in === null || typeof host.logged_in === "boolean"))
|
|
549
|
+
throw failure(`considered.${index}.hosts.${hostIndex}.status`);
|
|
550
|
+
if (!(host.live_lanes === null || (typeof host.live_lanes === "number" && Number.isInteger(host.live_lanes) && host.live_lanes >= 0)))
|
|
551
|
+
throw failure(`considered.${index}.hosts.${hostIndex}.live_lanes`);
|
|
552
|
+
if (!isPlainObject(host.lane_counts))
|
|
553
|
+
throw failure(`considered.${index}.hosts.${hostIndex}.lane_counts`);
|
|
554
|
+
for (const key of ["running", "queued", "awaiting_input"]) {
|
|
555
|
+
const count = host.lane_counts[key];
|
|
556
|
+
if (!(count === null || (typeof count === "number" && Number.isInteger(count) && count >= 0)))
|
|
557
|
+
throw failure(`considered.${index}.hosts.${hostIndex}.lane_counts.${key}`);
|
|
558
|
+
}
|
|
559
|
+
if (!["lane_list", "capacity", "capacity_partial", "unavailable"].includes(String(host.count_source)))
|
|
560
|
+
throw failure(`considered.${index}.hosts.${hostIndex}.count_source`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
for (const [index, rowValue] of selection.skipped_accounts.entries()) {
|
|
564
|
+
if (!isPlainObject(rowValue))
|
|
565
|
+
throw failure(`skipped_accounts.${index}`);
|
|
566
|
+
const row = rowValue;
|
|
567
|
+
if (typeof row.account !== "string" || row.account.trim() === "" || typeof row.reason !== "string" || row.reason.trim() === "")
|
|
568
|
+
throw failure(`skipped_accounts.${index}`);
|
|
569
|
+
parseHost(row.host, `skipped_accounts.${index}.host`);
|
|
570
|
+
}
|
|
571
|
+
for (const [index, rowValue] of selection.unavailable_hosts.entries()) {
|
|
572
|
+
if (!isPlainObject(rowValue))
|
|
573
|
+
throw failure(`unavailable_hosts.${index}`);
|
|
574
|
+
const row = rowValue;
|
|
575
|
+
if (typeof row.reason !== "string" || row.reason.trim() === "")
|
|
576
|
+
throw failure(`unavailable_hosts.${index}`);
|
|
577
|
+
if (isPlainObject(row.host) && row.host.kind === "unknown" && row.host.id === "unknown")
|
|
578
|
+
continue;
|
|
579
|
+
parseHost(row.host, `unavailable_hosts.${index}.host`);
|
|
580
|
+
}
|
|
581
|
+
return selection;
|
|
582
|
+
}
|
|
497
583
|
export function parseQuestionRecord(raw, filePath) {
|
|
498
584
|
if (!isPlainObject(raw)) {
|
|
499
585
|
throw new LaneSchemaError("Question record must be a JSON object", {
|
|
@@ -230,6 +230,52 @@ export interface LaneHost {
|
|
|
230
230
|
kind: HostKind;
|
|
231
231
|
id: string;
|
|
232
232
|
}
|
|
233
|
+
export interface LaneAccountSelectionHostCount {
|
|
234
|
+
host: LaneHost;
|
|
235
|
+
enabled: boolean;
|
|
236
|
+
logged_in: boolean | null;
|
|
237
|
+
live_lanes: number | null;
|
|
238
|
+
lane_counts: {
|
|
239
|
+
running: number | null;
|
|
240
|
+
queued: number | null;
|
|
241
|
+
awaiting_input: number | null;
|
|
242
|
+
};
|
|
243
|
+
count_source: "lane_list" | "capacity" | "capacity_partial" | "unavailable";
|
|
244
|
+
}
|
|
245
|
+
export interface LaneAccountSelectionConsidered {
|
|
246
|
+
account: string;
|
|
247
|
+
eligible: boolean;
|
|
248
|
+
ineligible_reason?: string;
|
|
249
|
+
live_lanes: number;
|
|
250
|
+
counts_complete: boolean;
|
|
251
|
+
usage: {
|
|
252
|
+
five_hour_percent?: number;
|
|
253
|
+
weekly_percent?: number;
|
|
254
|
+
sampled_at?: string;
|
|
255
|
+
} | null;
|
|
256
|
+
hosts: LaneAccountSelectionHostCount[];
|
|
257
|
+
}
|
|
258
|
+
/** Decision evidence saved with a Claude lane when account balancing is active. */
|
|
259
|
+
export interface LaneAccountSelection {
|
|
260
|
+
account: string;
|
|
261
|
+
signal: "usage" | "lane_count" | "explicit";
|
|
262
|
+
reason: "least_loaded" | "explicit";
|
|
263
|
+
target_host: LaneHost;
|
|
264
|
+
selected_at: string;
|
|
265
|
+
considered: LaneAccountSelectionConsidered[];
|
|
266
|
+
skipped_accounts: Array<{
|
|
267
|
+
account: string;
|
|
268
|
+
host: LaneHost;
|
|
269
|
+
reason: string;
|
|
270
|
+
}>;
|
|
271
|
+
unavailable_hosts: Array<{
|
|
272
|
+
host: LaneHost | {
|
|
273
|
+
kind: "unknown";
|
|
274
|
+
id: "unknown";
|
|
275
|
+
};
|
|
276
|
+
reason: string;
|
|
277
|
+
}>;
|
|
278
|
+
}
|
|
233
279
|
export interface SeniorHistoryEntry {
|
|
234
280
|
from: SeniorRef;
|
|
235
281
|
to: SeniorRef;
|
|
@@ -309,6 +355,8 @@ export interface LaneRecord {
|
|
|
309
355
|
worker_policy_delivery?: "hook" | "prompt";
|
|
310
356
|
provider: LaneProvider;
|
|
311
357
|
account?: string;
|
|
358
|
+
/** Recent Claude account-selection evidence for a create or automatic resume move. */
|
|
359
|
+
account_selection?: LaneAccountSelection;
|
|
312
360
|
/** Eligible accounts for a queue entry created without an explicit account. */
|
|
313
361
|
admission_accounts?: string[];
|
|
314
362
|
/**
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -224,7 +224,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
224
224
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
225
225
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
226
226
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
227
|
-
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
227
|
+
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
228
228
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
|
|
229
229
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn"], effort_source: ["creator", "spawn"], fast_mode: ["spawn"],
|
|
230
230
|
last_message_at: ["command"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
@@ -75,14 +75,27 @@ export function extractLastAssistantText(jsonl) {
|
|
|
75
75
|
const type = typeof obj.type === "string" ? obj.type : "";
|
|
76
76
|
const role = typeof obj.role === "string" ? obj.role : "";
|
|
77
77
|
const payload = obj.payload;
|
|
78
|
+
const item = obj.item;
|
|
79
|
+
const message = obj.message;
|
|
78
80
|
let text = "";
|
|
79
81
|
if (type === "assistant" || role === "assistant") {
|
|
80
82
|
text = textFromUnknown(obj.message ?? obj.content);
|
|
81
83
|
}
|
|
84
|
+
else if (message && (message.role === "assistant" || message.type === "assistant")) {
|
|
85
|
+
text = textFromUnknown(message.content ?? message.text ?? message);
|
|
86
|
+
}
|
|
82
87
|
else if (payload &&
|
|
83
88
|
(payload.type === "agent_message" || payload.type === "assistant")) {
|
|
84
89
|
text = textFromUnknown(payload.message ?? payload.content ?? payload.text);
|
|
85
90
|
}
|
|
91
|
+
else if (type === "item.completed" &&
|
|
92
|
+
item &&
|
|
93
|
+
item.type === "agent_message") {
|
|
94
|
+
text = textFromUnknown(item.text ?? item.content ?? item.message);
|
|
95
|
+
}
|
|
96
|
+
else if (type === "result" && typeof obj.result === "string") {
|
|
97
|
+
text = obj.result;
|
|
98
|
+
}
|
|
86
99
|
const trimmed = text.trim();
|
|
87
100
|
if (trimmed)
|
|
88
101
|
last = trimmed;
|
|
@@ -4,6 +4,15 @@ export declare function mergeProjectStories(current: MeshProjectStoryInput[], pl
|
|
|
4
4
|
resetStatus?: boolean;
|
|
5
5
|
}): MeshProjectStoryInput[];
|
|
6
6
|
export declare function boardStoryStatus(story: MeshProjectStoryInput): string;
|
|
7
|
+
/**
|
|
8
|
+
* Keep only prd-sync task_status events whose Board status actually differs.
|
|
9
|
+
* Missing Board rows stay (ensure will add them). Same-status replays of
|
|
10
|
+
* already-done stories must not be sent again (US-051b).
|
|
11
|
+
*/
|
|
12
|
+
export declare function prdSyncEventsThatChangeBoard<T extends {
|
|
13
|
+
taskId: string;
|
|
14
|
+
status: string;
|
|
15
|
+
}>(events: readonly T[], boardStories: readonly MeshProjectStoryInput[]): T[];
|
|
7
16
|
/** One-line summary of live→next status diffs for existing story ids. */
|
|
8
17
|
export declare function formatStoryStatusChanges(current: MeshProjectStoryInput[], next: MeshProjectStoryInput[]): string | undefined;
|
|
9
18
|
/**
|
|
@@ -34,6 +34,15 @@ export function boardStoryStatus(story) {
|
|
|
34
34
|
return story.status;
|
|
35
35
|
return story.passes === true ? "done" : "queued";
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Keep only prd-sync task_status events whose Board status actually differs.
|
|
39
|
+
* Missing Board rows stay (ensure will add them). Same-status replays of
|
|
40
|
+
* already-done stories must not be sent again (US-051b).
|
|
41
|
+
*/
|
|
42
|
+
export function prdSyncEventsThatChangeBoard(events, boardStories) {
|
|
43
|
+
const byId = new Map(boardStories.map((story) => [story.id, boardStoryStatus(story)]));
|
|
44
|
+
return events.filter((event) => byId.get(event.taskId) !== event.status);
|
|
45
|
+
}
|
|
37
46
|
/** One-line summary of live→next status diffs for existing story ids. */
|
|
38
47
|
export function formatStoryStatusChanges(current, next) {
|
|
39
48
|
const currentById = new Map(current.map((story) => [story.id, story]));
|