tines 0.0.142 → 0.0.144
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/dist/index.js +238 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3506,6 +3506,17 @@ function isStaleTierOverride(builtinModel, overrideModel) {
|
|
|
3506
3506
|
if (!builtinModel || !overrideModel || builtinModel === overrideModel) return false;
|
|
3507
3507
|
return (MODEL_PREDECESSORS[builtinModel] ?? []).includes(overrideModel);
|
|
3508
3508
|
}
|
|
3509
|
+
function ageLabel(at, now = Date.now()) {
|
|
3510
|
+
if (!Number.isFinite(at)) return "\u2014";
|
|
3511
|
+
const seconds = Math.max(0, Math.round((now - at) / 1e3));
|
|
3512
|
+
if (seconds < 60) return `${seconds}s`;
|
|
3513
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
3514
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
|
|
3515
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
3516
|
+
}
|
|
3517
|
+
function prUrlOf(v) {
|
|
3518
|
+
return v.pr_repo_url && v.pr_number !== null ? `${v.pr_repo_url}/pull/${v.pr_number}` : null;
|
|
3519
|
+
}
|
|
3509
3520
|
function runDurationLabel(run, now = Date.now()) {
|
|
3510
3521
|
if (!run.started_at) return "\u2014";
|
|
3511
3522
|
const seconds = Math.max(0, Math.round(((run.ended_at ?? now) - run.started_at) / 1e3));
|
|
@@ -4029,6 +4040,7 @@ function createApiClient(options) {
|
|
|
4029
4040
|
deleteRoutingRule: (id) => request("DELETE", `/api/v1/routing-rules/${id}`),
|
|
4030
4041
|
// Supervisor settings
|
|
4031
4042
|
getSupervisorSettings: () => get("/api/v1/supervisor/settings"),
|
|
4043
|
+
getSupervisorQueue: () => get("/api/v1/supervisor/queue"),
|
|
4032
4044
|
updateSupervisorSettings: (body) => request("PUT", "/api/v1/supervisor/settings", body),
|
|
4033
4045
|
// API keys (create/revoke require a browser session, not a key)
|
|
4034
4046
|
listApiKeys: (filters = {}) => get(`/api/v1/api-keys${query(filters)}`),
|
|
@@ -4184,7 +4196,7 @@ function artifactSummary(a) {
|
|
|
4184
4196
|
case "link":
|
|
4185
4197
|
return cv.title ? `${cv.title} \u2014 ${cv.url}` : cv.url ?? "";
|
|
4186
4198
|
case "pr":
|
|
4187
|
-
return `${prRefLabel(cv)} \u2014 ${cv
|
|
4199
|
+
return `${prRefLabel(cv)} \u2014 ${prUrlOf(cv)}`;
|
|
4188
4200
|
}
|
|
4189
4201
|
}
|
|
4190
4202
|
function artifactTypeLabel(a) {
|
|
@@ -4234,6 +4246,84 @@ function commentLines(comment) {
|
|
|
4234
4246
|
const header = ` [${timestamp(comment.created_at)}] ${actorLabel(comment.actor)} (${comment.id})${comment.updated_at ? " (edited)" : ""}:`;
|
|
4235
4247
|
return ["", header, ...comment.body.split("\n").map((line) => ` ${line}`)];
|
|
4236
4248
|
}
|
|
4249
|
+
function sinceLastRunLines(since, now = Date.now()) {
|
|
4250
|
+
const prev = since.previous_run;
|
|
4251
|
+
const ended = prev.ended_at === null ? "not ended" : `ended ${ageLabel(prev.ended_at, now)} ago`;
|
|
4252
|
+
const lines = [
|
|
4253
|
+
`since the last run (${prev.state_at_start_name ?? "unknown state"}, ${prev.run_id} ${ended}):`
|
|
4254
|
+
];
|
|
4255
|
+
const t = since.transition;
|
|
4256
|
+
if (t) {
|
|
4257
|
+
const via = t.action ? `via "${t.action}"` : "moved directly";
|
|
4258
|
+
const stale = since.stale_artifacts.length > 0 ? ` \u2014 now stale: ${since.stale_artifacts.join(", ")}` : "";
|
|
4259
|
+
lines.push(
|
|
4260
|
+
` moved from ${t.from_state.name} ${via} by ${actorLabel(t.actor)} ${ageLabel(t.at, now)} ago${stale}`
|
|
4261
|
+
);
|
|
4262
|
+
}
|
|
4263
|
+
for (const c of since.comments) lines.push(...commentLines(c));
|
|
4264
|
+
if (since.comment_count > since.comments.length) {
|
|
4265
|
+
const hidden = since.comment_count - since.comments.length;
|
|
4266
|
+
lines.push(` \u2026 and ${hidden} earlier comment${hidden === 1 ? "" : "s"}`);
|
|
4267
|
+
}
|
|
4268
|
+
return lines;
|
|
4269
|
+
}
|
|
4270
|
+
function roundLines(round, now = Date.now()) {
|
|
4271
|
+
const b = round.boundary;
|
|
4272
|
+
const from = b ? `since ${actorLabel(b.actor)} ${b.action ? `moved "${b.action}"` : "moved it directly"} ${timestamp(b.at)}` : `since created ${timestamp(round.boundary_at)}`;
|
|
4273
|
+
const lines = [`round (${round.run_count} run${round.run_count === 1 ? "" : "s"} ${from}):`];
|
|
4274
|
+
for (const stage of round.stages) {
|
|
4275
|
+
const [latest, ...earlier] = stage.runs;
|
|
4276
|
+
if (!latest) continue;
|
|
4277
|
+
lines.push(` ${stage.state.name ?? stage.state.id} \u2014 ${runHeadline(latest, now)}`);
|
|
4278
|
+
if (latest.artifacts.length > 0) {
|
|
4279
|
+
lines.push(` ${latest.artifacts.map(artifactChangeLabel).join(" \xB7 ")}`);
|
|
4280
|
+
}
|
|
4281
|
+
for (const run of earlier) lines.push(` ${earlierAttemptLine(run, now)}`);
|
|
4282
|
+
if (latest.summary_comment) {
|
|
4283
|
+
lines.push(` summary (${latest.summary_comment.id}):`);
|
|
4284
|
+
for (const line of latest.summary_comment.body.split("\n")) lines.push(` ${line}`);
|
|
4285
|
+
}
|
|
4286
|
+
const earlierComments = latest.earlier_comment_ids;
|
|
4287
|
+
if (earlierComments.length > 0) {
|
|
4288
|
+
lines.push(
|
|
4289
|
+
` ${earlierComments.length} earlier comment${earlierComments.length === 1 ? "" : "s"}: ${earlierComments.join(", ")}`
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
return lines;
|
|
4294
|
+
}
|
|
4295
|
+
function runHeadline(run, now) {
|
|
4296
|
+
const cost = runCostLabel(run);
|
|
4297
|
+
return [
|
|
4298
|
+
`${run.run_id} on ${run.runner_name}`,
|
|
4299
|
+
runDurationLabel(run, now),
|
|
4300
|
+
run.outcome ?? run.status,
|
|
4301
|
+
...cost ? [cost] : [],
|
|
4302
|
+
run.transition ? `"${run.transition.action ?? "moved directly"}" \u2192 ${run.transition.to_state.name}` : "no transition"
|
|
4303
|
+
].join(" \xB7 ");
|
|
4304
|
+
}
|
|
4305
|
+
function earlierAttemptLine(run, now) {
|
|
4306
|
+
const back = run.returned_via;
|
|
4307
|
+
const how = back ? back.action ? `sent back by ${back.from_state.name} ("${back.action}")` : "moved directly back" : "never came back";
|
|
4308
|
+
return `${run.run_id} \xB7 ${runDurationLabel(run, now)} \xB7 ${run.outcome ?? run.status} \xB7 ${how}`;
|
|
4309
|
+
}
|
|
4310
|
+
function artifactChangeLabel(a) {
|
|
4311
|
+
const versions = a.from_version === null ? `v${a.to_version}` : `v${a.from_version} \u2192 v${a.to_version}`;
|
|
4312
|
+
const extra = a.pr_url ? ` (${a.pr_url})` : a.files ? ` (${a.files.length} file${a.files.length === 1 ? "" : "s"})` : "";
|
|
4313
|
+
return `${a.name} ${versions}${extra}`;
|
|
4314
|
+
}
|
|
4315
|
+
function arrivedViaLabel(via) {
|
|
4316
|
+
if (!via) return "\u2014";
|
|
4317
|
+
return via.action ?? "moved directly";
|
|
4318
|
+
}
|
|
4319
|
+
function roundSummaryLabel(summary) {
|
|
4320
|
+
if (!summary) return "\u2014";
|
|
4321
|
+
const parts = [
|
|
4322
|
+
...summary.pr_url ? [`PR ${summary.pr_url.split("/").pop()}`] : [],
|
|
4323
|
+
...summary.artifacts.map((a) => `${a.name} v${a.version}`)
|
|
4324
|
+
];
|
|
4325
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : "\u2014";
|
|
4326
|
+
}
|
|
4237
4327
|
function runRow(run) {
|
|
4238
4328
|
return [
|
|
4239
4329
|
run.id,
|
|
@@ -4257,21 +4347,20 @@ function byteSize(bytes) {
|
|
|
4257
4347
|
const rounded = unit <= 1 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
4258
4348
|
return `${unit <= 1 ? rounded : rounded.toFixed(1)} ${units[unit]}`;
|
|
4259
4349
|
}
|
|
4260
|
-
function
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
if (
|
|
4266
|
-
|
|
4267
|
-
return `${Math.floor(seconds / 86400)}d`;
|
|
4350
|
+
function ageLabel2(isoTimestamp, now = Date.now()) {
|
|
4351
|
+
return ageLabel(Date.parse(isoTimestamp), now);
|
|
4352
|
+
}
|
|
4353
|
+
function hoursLabel(ms, now = Date.now()) {
|
|
4354
|
+
const hours = Math.max(0, now - ms) / 36e5;
|
|
4355
|
+
if (hours < 48) return `${hours < 10 ? hours.toFixed(1) : Math.round(hours)}h`;
|
|
4356
|
+
return `${Math.round(hours / 24)}d`;
|
|
4268
4357
|
}
|
|
4269
4358
|
function keptWorkspaceRow(kept, sizeBytes, now = Date.now()) {
|
|
4270
4359
|
return [
|
|
4271
4360
|
kept.run_id,
|
|
4272
4361
|
kept.issue_ref ?? "\u2014",
|
|
4273
4362
|
kept.status,
|
|
4274
|
-
|
|
4363
|
+
ageLabel2(kept.kept_at, now),
|
|
4275
4364
|
byteSize(sizeBytes),
|
|
4276
4365
|
kept.path
|
|
4277
4366
|
];
|
|
@@ -5270,6 +5359,14 @@ allowed actions: ${allowed.length ? allowed.join(", ") : "none (terminal state)"
|
|
|
5270
5359
|
for (const line of rest) console.log(` ${line}`);
|
|
5271
5360
|
}
|
|
5272
5361
|
}
|
|
5362
|
+
if (issue.since_last_run && issue.effective_state.category === "active") {
|
|
5363
|
+
console.log("");
|
|
5364
|
+
for (const line of sinceLastRunLines(issue.since_last_run)) console.log(line);
|
|
5365
|
+
}
|
|
5366
|
+
if (issue.round && issue.effective_state.category === "awaiting_human") {
|
|
5367
|
+
console.log("");
|
|
5368
|
+
for (const line of roundLines(issue.round)) console.log(line);
|
|
5369
|
+
}
|
|
5273
5370
|
if (issue.comments.length > 0) {
|
|
5274
5371
|
console.log(`
|
|
5275
5372
|
comments (${issue.comments.length}):`);
|
|
@@ -5390,6 +5487,25 @@ function register2(program3) {
|
|
|
5390
5487
|
);
|
|
5391
5488
|
printList(res, opts, (items) => {
|
|
5392
5489
|
if (items.length === 0) return console.log(opts.ready ? "no ready issues" : "no issues");
|
|
5490
|
+
if (opts.category === "awaiting_human") {
|
|
5491
|
+
table([
|
|
5492
|
+
["REF", "TITLE", "STATE", "WAITING", "VIA", "ROUND", ""],
|
|
5493
|
+
...items.map((i) => [
|
|
5494
|
+
`${i.project_name}/${i.number}`,
|
|
5495
|
+
i.title,
|
|
5496
|
+
i.effective_state.name,
|
|
5497
|
+
ageLabel2(new Date(i.state_entered_at).toISOString()),
|
|
5498
|
+
arrivedViaLabel(i.arrived_via),
|
|
5499
|
+
roundSummaryLabel(i.round_summary ?? null),
|
|
5500
|
+
[
|
|
5501
|
+
i.open_blockers.length > 0 ? "blocked" : "",
|
|
5502
|
+
i.duplicate_of ? "dup" : "",
|
|
5503
|
+
...i.labels.map((l) => `[${l.name}]`)
|
|
5504
|
+
].filter(Boolean).join(" ")
|
|
5505
|
+
])
|
|
5506
|
+
]);
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5393
5509
|
table([
|
|
5394
5510
|
["REF", "TITLE", "STATE", "CATEGORY", "LAST ACTIVITY", ""],
|
|
5395
5511
|
...items.map((i) => [
|
|
@@ -8258,20 +8374,108 @@ recent instances:`);
|
|
|
8258
8374
|
}
|
|
8259
8375
|
|
|
8260
8376
|
// src/commands/supervisor.ts
|
|
8377
|
+
function queueBlocks(groups) {
|
|
8378
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
8379
|
+
for (const g of groups) {
|
|
8380
|
+
const key = `${g.verdict}|${g.runner_id ?? ""}`;
|
|
8381
|
+
const seen = byKey.get(key);
|
|
8382
|
+
if (seen) {
|
|
8383
|
+
seen.count += g.count;
|
|
8384
|
+
seen.groups.push(g);
|
|
8385
|
+
seen.binding ??= g.binding;
|
|
8386
|
+
} else {
|
|
8387
|
+
byKey.set(key, {
|
|
8388
|
+
verdict: g.verdict,
|
|
8389
|
+
runnerName: g.runner_name,
|
|
8390
|
+
binding: g.binding,
|
|
8391
|
+
count: g.count,
|
|
8392
|
+
groups: [g]
|
|
8393
|
+
});
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
return [...byKey.values()].sort((a, b) => b.count - a.count);
|
|
8397
|
+
}
|
|
8398
|
+
function queueHeadline(block) {
|
|
8399
|
+
const runner = block.runnerName ?? "the routed runner";
|
|
8400
|
+
switch (block.verdict) {
|
|
8401
|
+
case "ok":
|
|
8402
|
+
return "dispatching next pass";
|
|
8403
|
+
case "at_capacity":
|
|
8404
|
+
return block.binding?.kind === "max_concurrent" ? `at capacity on ${runner} (${block.binding.current}/${block.binding.limit})` : `at capacity on ${runner}`;
|
|
8405
|
+
case "quota_exhausted":
|
|
8406
|
+
if (block.binding?.kind === "global_cap")
|
|
8407
|
+
return `global cap reached (${block.binding.current}/${block.binding.limit})`;
|
|
8408
|
+
if (block.binding?.kind === "state_roster")
|
|
8409
|
+
return `roster limit reached (${block.binding.current}/${block.binding.limit})`;
|
|
8410
|
+
return "quota exhausted";
|
|
8411
|
+
case "offline":
|
|
8412
|
+
return `${runner} offline`;
|
|
8413
|
+
case "paused":
|
|
8414
|
+
return `${runner} paused`;
|
|
8415
|
+
case "draining":
|
|
8416
|
+
return `${runner} draining`;
|
|
8417
|
+
case "backing_off":
|
|
8418
|
+
return `${runner} backing off`;
|
|
8419
|
+
case "no_rule":
|
|
8420
|
+
return "no matching routing rule";
|
|
8421
|
+
case "no_targets":
|
|
8422
|
+
return "the matching rule has no targets";
|
|
8423
|
+
case "ambiguous_rule":
|
|
8424
|
+
return "two routing rules tie";
|
|
8425
|
+
case "pin_missing":
|
|
8426
|
+
return "pinned to a runner that is gone";
|
|
8427
|
+
case "automation_off":
|
|
8428
|
+
return "automation is off";
|
|
8429
|
+
case "parked":
|
|
8430
|
+
return "parked";
|
|
8431
|
+
}
|
|
8432
|
+
}
|
|
8433
|
+
function queueFix(block) {
|
|
8434
|
+
const runner = block.runnerName ?? "the routed runner";
|
|
8435
|
+
switch (block.verdict) {
|
|
8436
|
+
case "at_capacity":
|
|
8437
|
+
return `raise the cap on ${runner} \u2014 restart its daemon with tines runner daemon --max-concurrent N, or edit the runner on the Agents page`;
|
|
8438
|
+
case "quota_exhausted":
|
|
8439
|
+
return block.binding?.kind === "state_roster" ? "raise the roster limit \u2014 tines supervisor quota roster --default <n> --state <workflow>/<state>=<n> (this replaces the whole roster, so restate every override you keep)" : "raise the cap \u2014 tines supervisor quota global <n> \u2014 or switch to a per-state roster with tines supervisor quota roster";
|
|
8440
|
+
case "offline":
|
|
8441
|
+
return `start the daemon on that machine \u2014 tines runner daemon`;
|
|
8442
|
+
case "paused":
|
|
8443
|
+
return `tines runners resume ${runner}`;
|
|
8444
|
+
case "no_rule":
|
|
8445
|
+
return "add a routing rule for that state \u2014 tines routing set <runner> --state <workflow>/<state>";
|
|
8446
|
+
case "no_targets":
|
|
8447
|
+
case "ambiguous_rule":
|
|
8448
|
+
return "tines routing list, then edit the rule";
|
|
8449
|
+
case "pin_missing":
|
|
8450
|
+
return "clear the pin on those issues";
|
|
8451
|
+
case "automation_off":
|
|
8452
|
+
return "tines supervisor enable";
|
|
8453
|
+
case "backing_off":
|
|
8454
|
+
return "retries automatically; check the daemon log if it keeps failing";
|
|
8455
|
+
default:
|
|
8456
|
+
return null;
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8261
8459
|
function register10(program3) {
|
|
8262
8460
|
const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
|
|
8263
8461
|
withCommon(
|
|
8264
8462
|
supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")
|
|
8265
8463
|
).action(async (opts) => {
|
|
8266
8464
|
const api = client(opts);
|
|
8267
|
-
const [settings, runnersRes, workflows, activeRunItems] = await Promise.all([
|
|
8465
|
+
const [settings, runnersRes, workflows, activeRunItems, queue] = await Promise.all([
|
|
8268
8466
|
api.getSupervisorSettings(),
|
|
8269
8467
|
api.listRunners(),
|
|
8270
8468
|
api.listWorkflows(),
|
|
8271
|
-
listAll((page) => api.listRuns({ active: true, ...page }))
|
|
8469
|
+
listAll((page) => api.listRuns({ active: true, ...page })),
|
|
8470
|
+
api.getSupervisorQueue()
|
|
8272
8471
|
]);
|
|
8273
8472
|
if (opts.json) {
|
|
8274
|
-
return printJson({
|
|
8473
|
+
return printJson({
|
|
8474
|
+
settings,
|
|
8475
|
+
runners: runnersRes.items,
|
|
8476
|
+
active_runs: activeRunItems,
|
|
8477
|
+
queue
|
|
8478
|
+
});
|
|
8275
8479
|
}
|
|
8276
8480
|
const stateNames = /* @__PURE__ */ new Map();
|
|
8277
8481
|
for (const wf of workflows.items) {
|
|
@@ -8285,6 +8489,27 @@ function register10(program3) {
|
|
|
8285
8489
|
`utilization: ${utilizationLabel(settings.quota, activeRunItems, (id) => stateNames.get(id) ?? id)}`
|
|
8286
8490
|
);
|
|
8287
8491
|
console.log(`attempt limit: ${settings.attempt_limit} strikes, then the issue parks`);
|
|
8492
|
+
if (queue.waiting === 0) {
|
|
8493
|
+
console.log("waiting: nothing");
|
|
8494
|
+
} else {
|
|
8495
|
+
console.log(`waiting: ${queue.waiting} ${queue.waiting === 1 ? "issue" : "issues"}`);
|
|
8496
|
+
for (const block of queueBlocks(queue.groups)) {
|
|
8497
|
+
const states = block.groups.map(
|
|
8498
|
+
(g) => `${stateNames.get(g.state_id) ?? g.state_name} ${g.count} (oldest ${hoursLabel(g.oldest_entered_at)})`
|
|
8499
|
+
).join(" \xB7 ");
|
|
8500
|
+
console.log(` ${queueHeadline(block)}: ${states}`);
|
|
8501
|
+
const fix = queueFix(block);
|
|
8502
|
+
if (fix) console.log(` fix: ${fix}`);
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8505
|
+
console.log(
|
|
8506
|
+
queue.parked.count === 0 ? "parked: none" : `parked: ${queue.parked.count}, oldest ${hoursLabel(queue.parked.oldest_entered_at ?? Date.now())}`
|
|
8507
|
+
);
|
|
8508
|
+
if (queue.awaiting_human.count > 0) {
|
|
8509
|
+
console.log(
|
|
8510
|
+
`awaiting you: ${queue.awaiting_human.count} issues, oldest ${hoursLabel(queue.awaiting_human.oldest_entered_at ?? Date.now())}`
|
|
8511
|
+
);
|
|
8512
|
+
}
|
|
8288
8513
|
if (runnersRes.items.length === 0) {
|
|
8289
8514
|
console.log("runners: none");
|
|
8290
8515
|
} else {
|