@taskless-app/swarm 0.6.20 → 0.6.21
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/cli.js +61 -5
- package/dist/overmind.js +231 -24
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2373,7 +2373,7 @@ var init_package = __esm({
|
|
|
2373
2373
|
"package.json"() {
|
|
2374
2374
|
package_default = {
|
|
2375
2375
|
name: "@taskless-app/swarm",
|
|
2376
|
-
version: "0.6.
|
|
2376
|
+
version: "0.6.21",
|
|
2377
2377
|
type: "module",
|
|
2378
2378
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2379
2379
|
keywords: [
|
|
@@ -2423,7 +2423,7 @@ var init_package = __esm({
|
|
|
2423
2423
|
execa: "^10.0.0",
|
|
2424
2424
|
zod: "3.25.76"
|
|
2425
2425
|
},
|
|
2426
|
-
gitHead: "
|
|
2426
|
+
gitHead: "584411cbe69583a8727e89caa8ae7e626f8fd8ca"
|
|
2427
2427
|
};
|
|
2428
2428
|
}
|
|
2429
2429
|
});
|
|
@@ -8510,10 +8510,24 @@ async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
|
|
|
8510
8510
|
}
|
|
8511
8511
|
}
|
|
8512
8512
|
var NO_CHECKS_STDERR = /no checks reported/i;
|
|
8513
|
+
var CiAccessDeniedError = class extends Error {
|
|
8514
|
+
detail;
|
|
8515
|
+
constructor(detail) {
|
|
8516
|
+
super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
|
|
8517
|
+
this.name = "CiAccessDeniedError";
|
|
8518
|
+
this.detail = detail;
|
|
8519
|
+
}
|
|
8520
|
+
};
|
|
8521
|
+
var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
|
|
8522
|
+
function firstLine(s) {
|
|
8523
|
+
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
|
|
8524
|
+
}
|
|
8513
8525
|
var realChecksRunner = async (pr) => {
|
|
8514
8526
|
const r = await $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
|
|
8515
8527
|
const stdout = r.stdout ?? "";
|
|
8516
|
-
|
|
8528
|
+
const stderr = r.stderr ?? "";
|
|
8529
|
+
if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
|
|
8530
|
+
if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
|
|
8517
8531
|
return stdout;
|
|
8518
8532
|
};
|
|
8519
8533
|
async function prCheckRows(pr, run) {
|
|
@@ -8522,13 +8536,17 @@ async function prCheckRows(pr, run) {
|
|
|
8522
8536
|
const rows = JSON.parse(out);
|
|
8523
8537
|
if (!Array.isArray(rows)) return null;
|
|
8524
8538
|
return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
|
|
8525
|
-
} catch {
|
|
8539
|
+
} catch (e) {
|
|
8540
|
+
if (e instanceof CiAccessDeniedError) return { denied: e.detail };
|
|
8526
8541
|
return null;
|
|
8527
8542
|
}
|
|
8528
8543
|
}
|
|
8544
|
+
function isDenied(rows) {
|
|
8545
|
+
return rows != null && !Array.isArray(rows);
|
|
8546
|
+
}
|
|
8529
8547
|
async function ghChecks(pr, run = realChecksRunner) {
|
|
8530
8548
|
const rows = await prCheckRows(pr, run);
|
|
8531
|
-
if (!rows || rows.length === 0) return null;
|
|
8549
|
+
if (!rows || isDenied(rows) || rows.length === 0) return null;
|
|
8532
8550
|
let pass = 0, fail = 0, pending2 = 0;
|
|
8533
8551
|
for (const r of rows) {
|
|
8534
8552
|
if (r.bucket === "pass") pass++;
|
|
@@ -11084,6 +11102,44 @@ init_config();
|
|
|
11084
11102
|
init_agentToken();
|
|
11085
11103
|
init_linkFailure();
|
|
11086
11104
|
|
|
11105
|
+
// src/toolFailure.ts
|
|
11106
|
+
init_linkFailure();
|
|
11107
|
+
var TOOL_FAILURE = {
|
|
11108
|
+
/** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
|
|
11109
|
+
NOT_FOUND: "not_found",
|
|
11110
|
+
/** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
|
|
11111
|
+
FORBIDDEN: "forbidden",
|
|
11112
|
+
/** Токен роя не состоит в воркспейсе, которым искали. */
|
|
11113
|
+
WORKSPACE_MISMATCH: "workspace_mismatch",
|
|
11114
|
+
/** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
|
|
11115
|
+
UNREACHABLE: "unreachable",
|
|
11116
|
+
/** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
|
|
11117
|
+
UNAUTHORIZED: "unauthorized",
|
|
11118
|
+
/** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
|
|
11119
|
+
TOOL_ERROR: "tool_error",
|
|
11120
|
+
/** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
|
|
11121
|
+
EMPTY: "empty"
|
|
11122
|
+
};
|
|
11123
|
+
var TOOL_FAILURE_TEXT = {
|
|
11124
|
+
[TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
|
|
11125
|
+
[TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
11126
|
+
[TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
|
|
11127
|
+
[TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
|
|
11128
|
+
[TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
|
|
11129
|
+
[TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
11130
|
+
[TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
|
|
11131
|
+
};
|
|
11132
|
+
var TOOL_FAILURE_RETRIABLE = {
|
|
11133
|
+
[TOOL_FAILURE.NOT_FOUND]: false,
|
|
11134
|
+
[TOOL_FAILURE.FORBIDDEN]: false,
|
|
11135
|
+
[TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
|
|
11136
|
+
[TOOL_FAILURE.UNREACHABLE]: true,
|
|
11137
|
+
// Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
|
|
11138
|
+
[TOOL_FAILURE.UNAUTHORIZED]: false,
|
|
11139
|
+
[TOOL_FAILURE.TOOL_ERROR]: false,
|
|
11140
|
+
[TOOL_FAILURE.EMPTY]: false
|
|
11141
|
+
};
|
|
11142
|
+
|
|
11087
11143
|
// src/workspace.ts
|
|
11088
11144
|
init_config();
|
|
11089
11145
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
|
package/dist/overmind.js
CHANGED
|
@@ -4527,6 +4527,78 @@ var init_oauth = __esm({
|
|
|
4527
4527
|
}
|
|
4528
4528
|
});
|
|
4529
4529
|
|
|
4530
|
+
// src/toolFailure.ts
|
|
4531
|
+
function trimDetail(s) {
|
|
4532
|
+
const t = s.trim();
|
|
4533
|
+
return t.length <= FAILURE_DETAIL_MAX ? t : `${t.slice(0, FAILURE_DETAIL_MAX)}\u2026`;
|
|
4534
|
+
}
|
|
4535
|
+
function classifyToolFailure(raw) {
|
|
4536
|
+
if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
|
|
4537
|
+
if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
|
|
4538
|
+
if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
|
|
4539
|
+
return TOOL_FAILURE.TOOL_ERROR;
|
|
4540
|
+
}
|
|
4541
|
+
function classifyThrown(e) {
|
|
4542
|
+
const { kind, detail } = explainLinkFailure(e);
|
|
4543
|
+
const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
|
|
4544
|
+
// «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
|
|
4545
|
+
// и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
|
|
4546
|
+
classifyToolFailure(detail)
|
|
4547
|
+
);
|
|
4548
|
+
return { code, detail };
|
|
4549
|
+
}
|
|
4550
|
+
function describeToolFailure(f) {
|
|
4551
|
+
const text = TOOL_FAILURE_TEXT[f.code];
|
|
4552
|
+
const detail = trimDetail(f.detail);
|
|
4553
|
+
return detail ? `${text} (${detail})` : text;
|
|
4554
|
+
}
|
|
4555
|
+
var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, FAILURE_DETAIL_MAX, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
|
|
4556
|
+
var init_toolFailure = __esm({
|
|
4557
|
+
"src/toolFailure.ts"() {
|
|
4558
|
+
"use strict";
|
|
4559
|
+
init_linkFailure();
|
|
4560
|
+
TOOL_FAILURE = {
|
|
4561
|
+
/** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
|
|
4562
|
+
NOT_FOUND: "not_found",
|
|
4563
|
+
/** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
|
|
4564
|
+
FORBIDDEN: "forbidden",
|
|
4565
|
+
/** Токен роя не состоит в воркспейсе, которым искали. */
|
|
4566
|
+
WORKSPACE_MISMATCH: "workspace_mismatch",
|
|
4567
|
+
/** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
|
|
4568
|
+
UNREACHABLE: "unreachable",
|
|
4569
|
+
/** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
|
|
4570
|
+
UNAUTHORIZED: "unauthorized",
|
|
4571
|
+
/** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
|
|
4572
|
+
TOOL_ERROR: "tool_error",
|
|
4573
|
+
/** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
|
|
4574
|
+
EMPTY: "empty"
|
|
4575
|
+
};
|
|
4576
|
+
TOOL_FAILURE_TEXT = {
|
|
4577
|
+
[TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
|
|
4578
|
+
[TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
4579
|
+
[TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
|
|
4580
|
+
[TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
|
|
4581
|
+
[TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
|
|
4582
|
+
[TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
4583
|
+
[TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
|
|
4584
|
+
};
|
|
4585
|
+
TOOL_FAILURE_RETRIABLE = {
|
|
4586
|
+
[TOOL_FAILURE.NOT_FOUND]: false,
|
|
4587
|
+
[TOOL_FAILURE.FORBIDDEN]: false,
|
|
4588
|
+
[TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
|
|
4589
|
+
[TOOL_FAILURE.UNREACHABLE]: true,
|
|
4590
|
+
// Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
|
|
4591
|
+
[TOOL_FAILURE.UNAUTHORIZED]: false,
|
|
4592
|
+
[TOOL_FAILURE.TOOL_ERROR]: false,
|
|
4593
|
+
[TOOL_FAILURE.EMPTY]: false
|
|
4594
|
+
};
|
|
4595
|
+
FAILURE_DETAIL_MAX = 300;
|
|
4596
|
+
NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
|
|
4597
|
+
FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
|
|
4598
|
+
MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
|
|
4530
4602
|
// src/authState.ts
|
|
4531
4603
|
function noteTokenRejected(nowMs = Date.now()) {
|
|
4532
4604
|
if (!denial) {
|
|
@@ -4623,25 +4695,44 @@ function isClosingTaskCategory(category) {
|
|
|
4623
4695
|
function taskAuthor(u) {
|
|
4624
4696
|
return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
|
|
4625
4697
|
}
|
|
4698
|
+
function toolErrorText(r) {
|
|
4699
|
+
const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
|
|
4700
|
+
return fromContent || JSON.stringify(r);
|
|
4701
|
+
}
|
|
4626
4702
|
function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl = fetch) {
|
|
4627
4703
|
const ws = workspaceId ? { workspaceId } : {};
|
|
4628
4704
|
const idCache = /* @__PURE__ */ new Map();
|
|
4629
4705
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
4630
|
-
async function
|
|
4706
|
+
async function callTool(name, args) {
|
|
4631
4707
|
try {
|
|
4632
4708
|
const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
|
|
4633
4709
|
if (r?.isError) {
|
|
4634
|
-
|
|
4635
|
-
|
|
4710
|
+
const raw = toolErrorText(r);
|
|
4711
|
+
const detail = trimDetail(raw);
|
|
4712
|
+
console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
|
|
4713
|
+
return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
|
|
4636
4714
|
}
|
|
4637
|
-
return r;
|
|
4715
|
+
return { ok: true, res: r };
|
|
4638
4716
|
} catch (e) {
|
|
4639
|
-
|
|
4640
|
-
|
|
4717
|
+
const failure = classifyThrown(e);
|
|
4718
|
+
console.error(`[taskless] \u0442\u0443\u043B ${name} \u0443\u043F\u0430\u043B:`, failure.detail);
|
|
4719
|
+
return { ok: false, failure };
|
|
4641
4720
|
}
|
|
4642
4721
|
}
|
|
4643
|
-
async function
|
|
4644
|
-
const
|
|
4722
|
+
async function safeCall(name, args) {
|
|
4723
|
+
const out = await callTool(name, args);
|
|
4724
|
+
return out.ok ? out.res : null;
|
|
4725
|
+
}
|
|
4726
|
+
async function getTaskDetailed(idOrSlug) {
|
|
4727
|
+
const out = await callTool("get_task", { id: idOrSlug });
|
|
4728
|
+
if (!out.ok) return { ok: false, failure: out.failure };
|
|
4729
|
+
const body = taskBodyFrom(out.res, idOrSlug);
|
|
4730
|
+
if (!body) {
|
|
4731
|
+
return { ok: false, failure: { code: TOOL_FAILURE.EMPTY, detail: "get_task: task \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435" } };
|
|
4732
|
+
}
|
|
4733
|
+
return { ok: true, task: body };
|
|
4734
|
+
}
|
|
4735
|
+
function taskBodyFrom(res, idOrSlug) {
|
|
4645
4736
|
const task = res?.structuredContent?.task ?? null;
|
|
4646
4737
|
if (!task?.id) return null;
|
|
4647
4738
|
const slug = task.prefix && task.number != null ? `${task.prefix}-${task.number}` : idOrSlug;
|
|
@@ -4671,6 +4762,9 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4671
4762
|
idCache.set(body.id, body.id);
|
|
4672
4763
|
return body;
|
|
4673
4764
|
}
|
|
4765
|
+
async function getTask(idOrSlug) {
|
|
4766
|
+
return taskBodyFrom(await safeCall("get_task", { id: idOrSlug }), idOrSlug);
|
|
4767
|
+
}
|
|
4674
4768
|
async function getDocument(id) {
|
|
4675
4769
|
const res = await safeCall("get_document", { id });
|
|
4676
4770
|
const doc = res?.structuredContent?.document ?? null;
|
|
@@ -4762,6 +4856,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4762
4856
|
}
|
|
4763
4857
|
const self = {
|
|
4764
4858
|
getTask,
|
|
4859
|
+
getTaskDetailed,
|
|
4765
4860
|
...fetchFile ? { fetchFile } : {},
|
|
4766
4861
|
startTask: (idOrSlug) => transition("start_task", idOrSlug),
|
|
4767
4862
|
// TASWM-294: intent — «человек, посмотри», а не «Готово» (см. докблок в TasklessClient).
|
|
@@ -4857,6 +4952,10 @@ function parseWorkspaces(res) {
|
|
|
4857
4952
|
return arr.filter((w) => !!w && typeof w.id === "string" && typeof w.slug === "string").map((w) => ({ id: w.id, slug: w.slug, name: typeof w.name === "string" ? w.name : w.slug, role: w.role }));
|
|
4858
4953
|
}
|
|
4859
4954
|
function requireTaskBody(task, slug, workspaceId) {
|
|
4955
|
+
if (task && "ok" in task) {
|
|
4956
|
+
if (task.ok) return task.task;
|
|
4957
|
+
throw new TaskBodyUnavailableError(slug, workspaceId, task.failure);
|
|
4958
|
+
}
|
|
4860
4959
|
if (!task) throw new TaskBodyUnavailableError(slug, workspaceId);
|
|
4861
4960
|
return task;
|
|
4862
4961
|
}
|
|
@@ -5088,16 +5187,25 @@ var init_taskless = __esm({
|
|
|
5088
5187
|
init_oauth();
|
|
5089
5188
|
init_agentToken();
|
|
5090
5189
|
init_linkFailure();
|
|
5190
|
+
init_toolFailure();
|
|
5091
5191
|
init_authState();
|
|
5092
5192
|
init_workspace();
|
|
5093
5193
|
SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
|
|
5094
5194
|
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
|
|
5095
5195
|
TaskBodyUnavailableError = class extends Error {
|
|
5096
|
-
|
|
5196
|
+
/** Код причины — по нему решают, повторять ли (TOOL_FAILURE_RETRIABLE), а не по тексту. */
|
|
5197
|
+
code;
|
|
5198
|
+
// TASWM-484: причина называется, а не перечисляется. Прежний текст («задачи там нет
|
|
5199
|
+
// ЛИБО токену её не видно») заставлял человека проверять обе версии руками — 21.08 на
|
|
5200
|
+
// этом ушло полдня, и обе оказались не теми. Теперь код приходит от классификатора
|
|
5201
|
+
// ответа прода, а человеку достаётся объяснение и то, что прод сказал дословно.
|
|
5202
|
+
constructor(slug, workspaceId, failure) {
|
|
5203
|
+
const f = failure ?? { code: TOOL_FAILURE.EMPTY, detail: "" };
|
|
5097
5204
|
super(
|
|
5098
|
-
`\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: ${slug} \u0432 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 ${workspaceId || "(\u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0438\u043D \u0434\u0435\u043C\u043E\u043D\u0430)"} \u2014
|
|
5205
|
+
`\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: ${slug} \u0432 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 ${workspaceId || "(\u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0438\u043D \u0434\u0435\u043C\u043E\u043D\u0430)"} \u2014 ${describeToolFailure(f)}. \u0414\u0440\u043E\u043D \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442: \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0441\u043B\u0430\u0433\u0443, \u0431\u0435\u0437 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u044F \u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u0432, \u0434\u043E\u0440\u043E\u0436\u0435 \u0438 \u0432\u0440\u0435\u0434\u043D\u0435\u0435, \u0447\u0435\u043C \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0442\u044C (TASWM-288).`
|
|
5099
5206
|
);
|
|
5100
5207
|
this.name = "TaskBodyUnavailableError";
|
|
5208
|
+
this.code = f.code;
|
|
5101
5209
|
}
|
|
5102
5210
|
};
|
|
5103
5211
|
TASK_CONTEXT_MAX_DOCS = 5;
|
|
@@ -10768,10 +10876,24 @@ function fullCommitHeadline(headline, body) {
|
|
|
10768
10876
|
return head.slice(0, -1) + tail2;
|
|
10769
10877
|
}
|
|
10770
10878
|
var NO_CHECKS_STDERR = /no checks reported/i;
|
|
10879
|
+
var CiAccessDeniedError = class extends Error {
|
|
10880
|
+
detail;
|
|
10881
|
+
constructor(detail) {
|
|
10882
|
+
super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
|
|
10883
|
+
this.name = "CiAccessDeniedError";
|
|
10884
|
+
this.detail = detail;
|
|
10885
|
+
}
|
|
10886
|
+
};
|
|
10887
|
+
var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
|
|
10888
|
+
function firstLine(s) {
|
|
10889
|
+
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
|
|
10890
|
+
}
|
|
10771
10891
|
var realChecksRunner = async (pr) => {
|
|
10772
10892
|
const r = await $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
|
|
10773
10893
|
const stdout = r.stdout ?? "";
|
|
10774
|
-
|
|
10894
|
+
const stderr = r.stderr ?? "";
|
|
10895
|
+
if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
|
|
10896
|
+
if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
|
|
10775
10897
|
return stdout;
|
|
10776
10898
|
};
|
|
10777
10899
|
async function prCheckRows(pr, run) {
|
|
@@ -10780,13 +10902,17 @@ async function prCheckRows(pr, run) {
|
|
|
10780
10902
|
const rows = JSON.parse(out);
|
|
10781
10903
|
if (!Array.isArray(rows)) return null;
|
|
10782
10904
|
return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
|
|
10783
|
-
} catch {
|
|
10905
|
+
} catch (e) {
|
|
10906
|
+
if (e instanceof CiAccessDeniedError) return { denied: e.detail };
|
|
10784
10907
|
return null;
|
|
10785
10908
|
}
|
|
10786
10909
|
}
|
|
10910
|
+
function isDenied(rows) {
|
|
10911
|
+
return rows != null && !Array.isArray(rows);
|
|
10912
|
+
}
|
|
10787
10913
|
async function ghChecks(pr, run = realChecksRunner) {
|
|
10788
10914
|
const rows = await prCheckRows(pr, run);
|
|
10789
|
-
if (!rows || rows.length === 0) return null;
|
|
10915
|
+
if (!rows || isDenied(rows) || rows.length === 0) return null;
|
|
10790
10916
|
let pass = 0, fail = 0, pending2 = 0;
|
|
10791
10917
|
for (const r of rows) {
|
|
10792
10918
|
if (r.bucket === "pass") pass++;
|
|
@@ -10817,6 +10943,7 @@ function runIdFromLink(link) {
|
|
|
10817
10943
|
async function prCiStatus(pr, run = realChecksRunner) {
|
|
10818
10944
|
const rows = await prCheckRows(pr, run);
|
|
10819
10945
|
if (!rows) return null;
|
|
10946
|
+
if (isDenied(rows)) return { bucket: "denied", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, detail: rows.denied };
|
|
10820
10947
|
if (rows.length === 0) return { bucket: "none", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0 };
|
|
10821
10948
|
const total = rows.length;
|
|
10822
10949
|
const pass = rows.filter((r) => r.bucket === "pass").length;
|
|
@@ -13312,7 +13439,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
13312
13439
|
// package.json
|
|
13313
13440
|
var package_default = {
|
|
13314
13441
|
name: "@taskless-app/swarm",
|
|
13315
|
-
version: "0.6.
|
|
13442
|
+
version: "0.6.21",
|
|
13316
13443
|
type: "module",
|
|
13317
13444
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
13318
13445
|
keywords: [
|
|
@@ -13362,7 +13489,7 @@ var package_default = {
|
|
|
13362
13489
|
execa: "^10.0.0",
|
|
13363
13490
|
zod: "3.25.76"
|
|
13364
13491
|
},
|
|
13365
|
-
gitHead: "
|
|
13492
|
+
gitHead: "584411cbe69583a8727e89caa8ae7e626f8fd8ca"
|
|
13366
13493
|
};
|
|
13367
13494
|
|
|
13368
13495
|
// src/version.ts
|
|
@@ -15866,6 +15993,9 @@ var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u04
|
|
|
15866
15993
|
var CI_NO_CHECKS_MARK = "\u0443 PR \u043D\u0435\u0442 \u0447\u0435\u043A\u043E\u0432 \u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442";
|
|
15867
15994
|
var CI_WAIT_POLL_MARK = "\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A";
|
|
15868
15995
|
var CI_POLL_LIMIT_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E \u043F\u043E\u0442\u043E\u043B\u043A\u0443";
|
|
15996
|
+
var CI_DENIED_LIMIT = Number(process.env.SWARM_CI_DENIED_LIMIT) || 3;
|
|
15997
|
+
var CI_DENIED_MARK = "\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)";
|
|
15998
|
+
var CI_DENIED_STOP_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 (403)";
|
|
15869
15999
|
var CI_WAIT_LIMIT_MS = Number(process.env.SWARM_CI_WAIT_LIMIT_MS) || 45 * 60 * 1e3;
|
|
15870
16000
|
var CI_POLL_LIMIT = Math.max(
|
|
15871
16001
|
Number(process.env.SWARM_CI_POLL_LIMIT) || 90,
|
|
@@ -16363,8 +16493,13 @@ function buildCiWakeNudge(summary) {
|
|
|
16363
16493
|
].join("\n");
|
|
16364
16494
|
}
|
|
16365
16495
|
function buildCiReadyNudge(prUrl, over) {
|
|
16366
|
-
const counts = over.kind
|
|
16367
|
-
const head = over.kind === "none" ? `\u2139\uFE0F \u0416\u0434\u0430\u0442\u044C CI \u043D\u0430 PR ${prUrl} \u041D\u0415\u0427\u0415\u0413\u041E: \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0443 PR \u043D\u0435\u0442, \u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E workflow \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0443\u0442 \u043D\u0435 \u0431\u044B\u0432\u0430\u0435\u0442. \u0420\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.` : over.kind === "gaveup" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0442\u0430\u043A \u0438 \u041D\u0415 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u2014 CI \u043E\u0442\u0432\u0430\u043B\u0438\u043B\u0441\u044F \u0438\u043B\u0438 \u043D\u0430\u043C\u0435\u0440\u0442\u0432\u043E \u0432\u0438\u0441\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, \u0438 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C (\u0441\u0440\u043E\u043A\u0438 \u0438 \u0441\u0447\u0451\u0442\u0447\u0438\u043A \u2014 \u0432 \u0441\u043E\u0431\u044B\u0442\u0438\u044F\u0445 \u043F\u0440\u043E\u0433\u043E\u043D\u0430). \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D). \u041D\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u043F\u0440\u043E \u044D\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C.` :
|
|
16496
|
+
const counts = (over.kind === "pass" || over.kind === "none") && over.ci.total > 0 ? ` (${over.ci.pass}/${over.ci.total})` : "";
|
|
16497
|
+
const head = over.kind === "none" ? `\u2139\uFE0F \u0416\u0434\u0430\u0442\u044C CI \u043D\u0430 PR ${prUrl} \u041D\u0415\u0427\u0415\u0413\u041E: \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0443 PR \u043D\u0435\u0442, \u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E workflow \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0443\u0442 \u043D\u0435 \u0431\u044B\u0432\u0430\u0435\u0442. \u0420\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.` : over.kind === "gaveup" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0442\u0430\u043A \u0438 \u041D\u0415 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u2014 CI \u043E\u0442\u0432\u0430\u043B\u0438\u043B\u0441\u044F \u0438\u043B\u0438 \u043D\u0430\u043C\u0435\u0440\u0442\u0432\u043E \u0432\u0438\u0441\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, \u0438 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C (\u0441\u0440\u043E\u043A\u0438 \u0438 \u0441\u0447\u0451\u0442\u0447\u0438\u043A \u2014 \u0432 \u0441\u043E\u0431\u044B\u0442\u0438\u044F\u0445 \u043F\u0440\u043E\u0433\u043E\u043D\u0430). \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D). \u041D\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u043F\u0440\u043E \u044D\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C.` : (
|
|
16498
|
+
// TASWM-483: чеки, скорее всего, ЕСТЬ и даже зелёные — их просто не видно рою (403).
|
|
16499
|
+
// Врать «CI не прошёл» тут так же нельзя, как «CI зелёный»: единственная честная
|
|
16500
|
+
// формулировка — «не проверялся», и она обязана доехать до вердикта.
|
|
16501
|
+
over.kind === "denied" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0440\u043E\u044E \u041D\u0415 \u0412\u0418\u0414\u041D\u042B: GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0442\u043A\u0430\u0437\u043E\u043C \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C \u2014 403${over.detail ? ` (${over.detail})` : ""}. \u0427\u0435\u043A\u0438 \u043D\u0430 PR, \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E, \u0435\u0441\u0442\u044C \u0438 \u0434\u0430\u0436\u0435 \u0437\u0435\u043B\u0451\u043D\u044B\u0435, \u043D\u043E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0438\u0445 \u0440\u043E\u0439 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442, \u043F\u043E\u043A\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043D\u0435 \u0432\u044B\u0434\u0430\u0441\u0442 \u0434\u043E\u0441\u0442\u0443\u043F; \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C. \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \xABCI \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0441\u044F: \u0440\u043E\u044E \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)\xBB. \u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438.` : `\u2705 \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u0438 \u043E\u043D\u0438 \u0437\u0435\u043B\u0451\u043D\u044B\u0435${counts} \u2014 \u0440\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u0436\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0447\u0435\u0433\u043E.`
|
|
16502
|
+
);
|
|
16368
16503
|
return [
|
|
16369
16504
|
head,
|
|
16370
16505
|
"",
|
|
@@ -16834,6 +16969,27 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
|
|
|
16834
16969
|
return;
|
|
16835
16970
|
}
|
|
16836
16971
|
const ci = await deps.ci.checks(run.pr_url).catch(() => null);
|
|
16972
|
+
if (ci?.bucket === "denied") {
|
|
16973
|
+
const seen = ciDeniedStreak(deps.db, run.id) + 1;
|
|
16974
|
+
if (seen < CI_DENIED_LIMIT) {
|
|
16975
|
+
deps.db.addEvent(
|
|
16976
|
+
run.id,
|
|
16977
|
+
"ci_checked",
|
|
16978
|
+
`CI \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${CI_DENIED_MARK}${ci.detail ? ` \u2014 ${ci.detail}` : ""}; \u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${seen}/${CI_DENIED_LIMIT} [${CI_WAIT_POLL_MARK}]`
|
|
16979
|
+
);
|
|
16980
|
+
return;
|
|
16981
|
+
}
|
|
16982
|
+
closeCiWatch(
|
|
16983
|
+
deps,
|
|
16984
|
+
run,
|
|
16985
|
+
CI_DENIED_STOP_MARK,
|
|
16986
|
+
`GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 403 \u043D\u0430 \u0447\u0442\u0435\u043D\u0438\u0435 \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432${ci.detail ? ` (${ci.detail})` : ""} \u2014 ${seen} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434. \u042D\u0442\u043E \u043D\u0435 \u0441\u0431\u043E\u0439 \u0441\u0435\u0442\u0438 \u0438 \u043D\u0435 \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u044B\u0439 CI: \u0443 \u0440\u043E\u044F \u043D\u0435\u0442 \u043F\u0440\u0430\u0432 \u043D\u0430 \u0447\u0435\u043A\u0438 \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0442\u044C \u0431\u0435\u0441\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u043E. \u041F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u044F \u043D\u0435 \u0432\u0438\u0436\u0443 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u044E; \u0440\u0435\u0432\u044C\u044E \u043F\u043E\u0439\u0434\u0451\u0442 \u0411\u0415\u0417 CI (\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 \u044D\u0442\u043E \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435), \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u044B\u0434\u0430\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E.`,
|
|
16987
|
+
`${droneLabel(run)}: GitHub \u043D\u0435 \u0434\u0430\u0451\u0442 \u0440\u043E\u044E \u0447\u0438\u0442\u0430\u0442\u044C \u0447\u0435\u043A\u0438 (403) \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043D\u0435 \u0432\u0438\u0436\u0443, \u0437\u043E\u0432\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0438 \u0432\u0435\u0434\u0443 \u0440\u0435\u0432\u044C\u044E \u0431\u0435\u0437 CI.`
|
|
16988
|
+
);
|
|
16989
|
+
await escalateCiDenied(deps, run, ci.detail ?? null, seen);
|
|
16990
|
+
await wakeReviewerOnCiReady(deps, run, { kind: "denied", ...ci.detail ? { detail: ci.detail } : {} });
|
|
16991
|
+
return;
|
|
16992
|
+
}
|
|
16837
16993
|
if (ci?.bucket === "none" && !repoHasCi(deps, run)) {
|
|
16838
16994
|
closeCiWatch(
|
|
16839
16995
|
deps,
|
|
@@ -16877,7 +17033,27 @@ function ciWatchMark(base3, cycle) {
|
|
|
16877
17033
|
return `${base3} [\u0446\u0438\u043A\u043B ${cycle}]`;
|
|
16878
17034
|
}
|
|
16879
17035
|
function isCiWatchClose(text) {
|
|
16880
|
-
return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK);
|
|
17036
|
+
return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK) || text.includes(CI_DENIED_STOP_MARK);
|
|
17037
|
+
}
|
|
17038
|
+
function ciDeniedStreak(db, runId) {
|
|
17039
|
+
return ciStreak(db, runId, CI_DENIED_MARK).count;
|
|
17040
|
+
}
|
|
17041
|
+
async function escalateCiDenied(deps, run, detail, polls) {
|
|
17042
|
+
const why = `${CI_DENIED_MARK}: GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 403 \u043D\u0430 \u0447\u0442\u0435\u043D\u0438\u0435 \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 PR${detail ? ` (${detail})` : ""} \u2014 ${polls} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434. \u0420\u043E\u0439 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C PR \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443\u043C\u0435\u0435\u0442, \u0430 \u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u2014 \u043D\u0435\u0442: \u043F\u0440\u0430\u0432\u0430 \u0432\u044B\u0434\u0430\u043D\u044B \u0447\u0430\u0441\u0442\u0438\u0447\u043D\u043E. \u041E\u043F\u0440\u043E\u0441 CI \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u043F\u0440\u0435\u043A\u0440\u0430\u0449\u0451\u043D, \u0440\u0435\u0432\u044C\u044E \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E.`;
|
|
17043
|
+
deps.db.addEvent(run.id, "anomaly", why);
|
|
17044
|
+
if (!deps.watchdog?.notify) return;
|
|
17045
|
+
const signal = {
|
|
17046
|
+
key: `ci_denied:${run.id}:${run.review_cycle}`,
|
|
17047
|
+
title: signalTitle(run, "GitHub \u043D\u0435 \u0434\u0430\u0451\u0442 \u0440\u043E\u044E \u0447\u0438\u0442\u0430\u0442\u044C \u0447\u0435\u043A\u0438 (403)"),
|
|
17048
|
+
body: `${run.task_slug}: ${polls} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0438\u0441\u044C 403${detail ? ` (${detail})` : ""} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0440\u043E\u0439 \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u0438 \u0436\u0434\u0430\u0442\u044C \u0438\u0445 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0420\u0435\u0432\u044C\u044E \u043F\u043E\u0439\u0434\u0451\u0442 \u0431\u0435\u0437 CI (\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 \u044D\u0442\u043E \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435). \u0412\u044B\u0434\u0430\u0439 \u0434\u043E\u0441\u0442\u0443\u043F: GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E. PR: ${run.pr_url ?? "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D"}.`,
|
|
17049
|
+
runId: run.id,
|
|
17050
|
+
...hrefOf(run.task_url)
|
|
17051
|
+
};
|
|
17052
|
+
try {
|
|
17053
|
+
await deps.watchdog.notify(signal);
|
|
17054
|
+
} catch (e) {
|
|
17055
|
+
console.error(`[overmind] \u0441\u0438\u0433\u043D\u0430\u043B \u043E\u0431 \u043E\u0442\u043A\u0430\u0437\u0435 GitHub \u043F\u043E \u0447\u0435\u043A\u0430\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id} \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B:`, e);
|
|
17056
|
+
}
|
|
16881
17057
|
}
|
|
16882
17058
|
function ciWatchClosed(db, run) {
|
|
16883
17059
|
return ciWatchCloseKind(db, run) != null;
|
|
@@ -16886,20 +17062,31 @@ var NO_CHECKS_STATUS = { bucket: "none", failing: [], failingRunIds: [], total:
|
|
|
16886
17062
|
function ciWatchCloseKind(db, run) {
|
|
16887
17063
|
const noChecks = ciWatchMark(CI_NO_CHECKS_MARK, run.review_cycle);
|
|
16888
17064
|
const gaveUp = ciWatchMark(CI_POLL_LIMIT_MARK, run.review_cycle);
|
|
17065
|
+
const denied = ciWatchMark(CI_DENIED_STOP_MARK, run.review_cycle);
|
|
16889
17066
|
for (const e of db.events(run.id)) {
|
|
16890
17067
|
const text = e.text ?? "";
|
|
16891
17068
|
if (text.includes(noChecks)) return { kind: "none", ci: NO_CHECKS_STATUS };
|
|
16892
17069
|
if (text.includes(gaveUp)) return { kind: "gaveup" };
|
|
17070
|
+
if (text.includes(denied)) {
|
|
17071
|
+
const detail = deniedDetailFrom(text);
|
|
17072
|
+
return { kind: "denied", ...detail ? { detail } : {} };
|
|
17073
|
+
}
|
|
16893
17074
|
}
|
|
16894
17075
|
return null;
|
|
16895
17076
|
}
|
|
17077
|
+
function deniedDetailFrom(text) {
|
|
17078
|
+
return text.match(/на чтение чек-ранов \(([^)]+)\)/)?.[1];
|
|
17079
|
+
}
|
|
16896
17080
|
function ciPollStreak(db, runId) {
|
|
17081
|
+
return ciStreak(db, runId, CI_WAIT_POLL_MARK);
|
|
17082
|
+
}
|
|
17083
|
+
function ciStreak(db, runId, mark) {
|
|
16897
17084
|
const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked" || isCiWatchClose(e.text ?? ""));
|
|
16898
17085
|
let count = 0;
|
|
16899
17086
|
let sinceMs = null;
|
|
16900
17087
|
for (let i = evs.length - 1; i >= 0; i -= 1) {
|
|
16901
17088
|
const e = evs[i];
|
|
16902
|
-
if (e.kind !== "ci_checked" || !(e.text ?? "").includes(
|
|
17089
|
+
if (e.kind !== "ci_checked" || !(e.text ?? "").includes(mark)) break;
|
|
16903
17090
|
count += 1;
|
|
16904
17091
|
sinceMs = sqliteMs(e.at);
|
|
16905
17092
|
}
|
|
@@ -16954,6 +17141,10 @@ function ciReadyLabel(over) {
|
|
|
16954
17141
|
// (isCiWatchClose/ciWatchCloseKind ищут его по подстроке), и событие о том, что
|
|
16955
17142
|
// побудка ждёт слота, не должно выглядеть вторым решением о закрытии.
|
|
16956
17143
|
"\u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0430\u043A \u0438 \u043D\u0435 \u0434\u043E\u0436\u0434\u0430\u043B\u0438\u0441\u044C, \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C CI"
|
|
17144
|
+
) : over.kind === "denied" ? (
|
|
17145
|
+
// По той же причине не дословно CI_DENIED_STOP_MARK (TASWM-483) — но «403» в тексте
|
|
17146
|
+
// остаётся: по журналу должна быть видна причина, а не «проверок нет».
|
|
17147
|
+
"\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)"
|
|
16957
17148
|
) : `CI \u0437\u0435\u043B\u0451\u043D\u044B\u0439 (${over.ci.pass}/${over.ci.total})`;
|
|
16958
17149
|
}
|
|
16959
17150
|
async function wakeReviewerOnCiReady(deps, run, over) {
|
|
@@ -18384,8 +18575,8 @@ async function tickOnce(deps) {
|
|
|
18384
18575
|
const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
|
|
18385
18576
|
const ci = ciWatchOff ? null : await deps.ci.checks(run.pr_url).catch(() => null);
|
|
18386
18577
|
const noCiRepo = ci?.bucket === "none" && !repoHasCi(deps, run);
|
|
18387
|
-
if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none")) {
|
|
18388
|
-
const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : `CI ${ci.pass}/${ci.total}`;
|
|
18578
|
+
if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied")) {
|
|
18579
|
+
const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : `CI ${ci.pass}/${ci.total}`;
|
|
18389
18580
|
deps.db.updateRun(run.id, { status: "paused", stop_reason: AWAITING_CI_REASON });
|
|
18390
18581
|
deps.db.addEvent(run.id, "paused", `${AWAITING_CI_REASON} (${what}) \u2014 \u043D\u0435 \u0441\u043C\u0435\u0440\u0442\u044C: \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043A\u0438 \u0434\u043E\u0439\u0434\u0443\u0442 \u0434\u043E \u043A\u043E\u043D\u0446\u0430`);
|
|
18391
18582
|
narrate(deps, `${droneLabel(run)} \u0443\u0448\u0451\u043B \u0436\u0434\u0430\u0442\u044C CI (${what}) \u2014 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u044E, \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A.`);
|
|
@@ -19466,7 +19657,18 @@ async function runDaemon(opts) {
|
|
|
19466
19657
|
// TASWM-472: третья ось — ОЧЕРЕДЬ. Вливаем по одному и только на свежей базе; ридер
|
|
19467
19658
|
// свежести ходит в gh (`compare/base...head` → behind_by). Ключа нет при
|
|
19468
19659
|
// SWARM_MERGE_QUEUE=0 — тогда гейт отвечает ровно как до TASWM-472.
|
|
19469
|
-
|
|
19660
|
+
// TASWM-483: отказ по правам (403) очереди мержа приходит как «спросить не удалось» (null),
|
|
19661
|
+
// а не как отдельная ось: для НЕЁ это одно и то же — цвет проверок неизвестен, значит
|
|
19662
|
+
// вливать нельзя (checks_unknown, см. докблок computeMergeQueue про несимметричную цену).
|
|
19663
|
+
// Отличать 403 от таймаута нужно тому, кто ЖДЁТ и повторяет (handleCi), а очередь не ждёт.
|
|
19664
|
+
...SWARM_MERGE_QUEUE ? {
|
|
19665
|
+
baseSync: (url) => prBaseSync(url),
|
|
19666
|
+
checks: async (url) => {
|
|
19667
|
+
const ci = await prCiStatus(url);
|
|
19668
|
+
if (ci == null || ci.bucket === "denied") return null;
|
|
19669
|
+
return { bucket: ci.bucket };
|
|
19670
|
+
}
|
|
19671
|
+
} : {}
|
|
19470
19672
|
});
|
|
19471
19673
|
const deps = {
|
|
19472
19674
|
db,
|
|
@@ -19478,7 +19680,9 @@ async function runDaemon(opts) {
|
|
|
19478
19680
|
const repoOfRun = normalizeRepo(run.repo || opts.repo);
|
|
19479
19681
|
const taskClient = tlFor(run.workspace_id);
|
|
19480
19682
|
const [rawTask, verifyMode, projectBase, zoneNotes] = await Promise.all([
|
|
19481
|
-
|
|
19683
|
+
// TASWM-484: с причиной — на этом чтении стоит fail-loud ниже, и именно его
|
|
19684
|
+
// сообщение читает человек, когда дрон не поднялся.
|
|
19685
|
+
taskClient ? taskClient.getTaskDetailed(run.task_slug) : Promise.resolve(null),
|
|
19482
19686
|
verifyModeReader ? verifyModeReader(repoOfRun) : Promise.resolve(void 0),
|
|
19483
19687
|
baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve(""),
|
|
19484
19688
|
// TASWM-466: память зоны прогона — тем же параллельным чтением, что и остальные
|
|
@@ -19735,7 +19939,7 @@ async function runDaemon(opts) {
|
|
|
19735
19939
|
// fresh-оживление строит промпт с нуля, и без тела задачи это тот же слепой дрон.
|
|
19736
19940
|
getTask: async (slug, ws) => {
|
|
19737
19941
|
const client = tlFor(ws);
|
|
19738
|
-
return client ? requireTaskBody(await client.
|
|
19942
|
+
return client ? requireTaskBody(await client.getTaskDetailed(slug), slug, ws) : null;
|
|
19739
19943
|
},
|
|
19740
19944
|
...verifyModeReader ? { verifyMode: verifyModeReader } : {},
|
|
19741
19945
|
// TASWM-466: тот же ридер памяти зоны, что на спавне — оживлённый дрон строит
|
|
@@ -20186,6 +20390,9 @@ export {
|
|
|
20186
20390
|
CHANGES_WAKE_LIMIT,
|
|
20187
20391
|
CHANGES_WAKE_MARK,
|
|
20188
20392
|
CI_CHECK_COOLDOWN_MS,
|
|
20393
|
+
CI_DENIED_LIMIT,
|
|
20394
|
+
CI_DENIED_MARK,
|
|
20395
|
+
CI_DENIED_STOP_MARK,
|
|
20189
20396
|
CI_NO_CHECKS_MARK,
|
|
20190
20397
|
CI_POLL_LIMIT,
|
|
20191
20398
|
CI_POLL_LIMIT_MARK,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"execa": "^10.0.0",
|
|
39
39
|
"zod": "3.25.76"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "584411cbe69583a8727e89caa8ae7e626f8fd8ca",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
|
|
44
44
|
"swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",
|