@taskless-app/swarm 0.6.21 → 0.6.23
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 +165 -15
- package/dist/overmind.js +420 -82
- package/package.json +2 -2
- package/roles/drone/skills/swarm-verify-green/SKILL.md +33 -0
package/dist/cli.js
CHANGED
|
@@ -1131,8 +1131,8 @@ var init_runGuards = __esm({
|
|
|
1131
1131
|
function hasZoneModel(m) {
|
|
1132
1132
|
return m.tasks > 0 && m.zones.length > 0;
|
|
1133
1133
|
}
|
|
1134
|
-
function testFileFeature(
|
|
1135
|
-
const head =
|
|
1134
|
+
function testFileFeature(basename7) {
|
|
1135
|
+
const head = basename7.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
|
|
1136
1136
|
return head && !GENERIC_DIRS.has(head) ? head : null;
|
|
1137
1137
|
}
|
|
1138
1138
|
function pathZoneCandidate(path) {
|
|
@@ -1776,10 +1776,24 @@ function asTokenUsage(m) {
|
|
|
1776
1776
|
function budgetExhausted(result) {
|
|
1777
1777
|
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1778
1778
|
}
|
|
1779
|
+
function sessionAbort(result) {
|
|
1780
|
+
if (budgetExhausted(result)) return null;
|
|
1781
|
+
const reason = result.terminal_reason ?? "";
|
|
1782
|
+
const subtype = result.subtype ?? "";
|
|
1783
|
+
if (reason === "aborted_tools") return { kind: "tools", raw: reason };
|
|
1784
|
+
if (reason === "aborted_streaming" || reason === "api_error") return { kind: "link", raw: reason };
|
|
1785
|
+
if (HONEST_ENDINGS.has(subtype) || HONEST_ENDINGS.has(reason)) return null;
|
|
1786
|
+
const failed = result.is_error === true || subtype === "error_during_execution" || reason.startsWith("aborted");
|
|
1787
|
+
if (!failed) return null;
|
|
1788
|
+
const raw = reason && reason !== "completed" ? reason : subtype || reason || "\u043E\u0448\u0438\u0431\u043A\u0430 \u0431\u0435\u0437 \u043C\u0435\u0442\u043A\u0438";
|
|
1789
|
+
return { kind: "other", raw };
|
|
1790
|
+
}
|
|
1791
|
+
var HONEST_ENDINGS;
|
|
1779
1792
|
var init_claudeResult = __esm({
|
|
1780
1793
|
"src/claudeResult.ts"() {
|
|
1781
1794
|
"use strict";
|
|
1782
1795
|
init_pricing();
|
|
1796
|
+
HONEST_ENDINGS = /* @__PURE__ */ new Set(["error_max_turns"]);
|
|
1783
1797
|
}
|
|
1784
1798
|
});
|
|
1785
1799
|
|
|
@@ -1854,6 +1868,9 @@ function writeMcpConfig(spec) {
|
|
|
1854
1868
|
writeFileSync(path, JSON.stringify(cfg, null, 2));
|
|
1855
1869
|
return path;
|
|
1856
1870
|
}
|
|
1871
|
+
function abortField(abort) {
|
|
1872
|
+
return abort ? { aborted: abort } : {};
|
|
1873
|
+
}
|
|
1857
1874
|
var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
|
|
1858
1875
|
var init_claudeCode = __esm({
|
|
1859
1876
|
"src/executors/claudeCode.ts"() {
|
|
@@ -1892,7 +1909,10 @@ var init_claudeCode = __esm({
|
|
|
1892
1909
|
durationMs: spend.durationMs,
|
|
1893
1910
|
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1894
1911
|
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1895
|
-
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1912
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {},
|
|
1913
|
+
// TASWM-487: и «оборвалось» — третий факт об этом же итоге. Порядок важен: потолок
|
|
1914
|
+
// трат sessionAbort отсекает сам, поэтому оба ключа одновременно не появятся.
|
|
1915
|
+
...abortField(sessionAbort(result))
|
|
1896
1916
|
};
|
|
1897
1917
|
},
|
|
1898
1918
|
session: {
|
|
@@ -2373,7 +2393,7 @@ var init_package = __esm({
|
|
|
2373
2393
|
"package.json"() {
|
|
2374
2394
|
package_default = {
|
|
2375
2395
|
name: "@taskless-app/swarm",
|
|
2376
|
-
version: "0.6.
|
|
2396
|
+
version: "0.6.23",
|
|
2377
2397
|
type: "module",
|
|
2378
2398
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2379
2399
|
keywords: [
|
|
@@ -2423,7 +2443,7 @@ var init_package = __esm({
|
|
|
2423
2443
|
execa: "^10.0.0",
|
|
2424
2444
|
zod: "3.25.76"
|
|
2425
2445
|
},
|
|
2426
|
-
gitHead: "
|
|
2446
|
+
gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
|
|
2427
2447
|
};
|
|
2428
2448
|
}
|
|
2429
2449
|
});
|
|
@@ -5230,7 +5250,7 @@ var init_updateCli = __esm({
|
|
|
5230
5250
|
|
|
5231
5251
|
// src/cli.ts
|
|
5232
5252
|
init_cliIo();
|
|
5233
|
-
import { basename as
|
|
5253
|
+
import { basename as basename6, dirname as dirname4, join as join24 } from "path";
|
|
5234
5254
|
import { existsSync as existsSync16, readFileSync as readFileSync17, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
5235
5255
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
5236
5256
|
|
|
@@ -8302,7 +8322,7 @@ function hostLines(hosts, opts = {}) {
|
|
|
8302
8322
|
// src/web.ts
|
|
8303
8323
|
init_src();
|
|
8304
8324
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
8305
|
-
import { basename as
|
|
8325
|
+
import { basename as basename4 } from "path";
|
|
8306
8326
|
import { serve as serve2 } from "@hono/node-server";
|
|
8307
8327
|
|
|
8308
8328
|
// src/diff.ts
|
|
@@ -8498,7 +8518,123 @@ init_storm();
|
|
|
8498
8518
|
// src/gh.ts
|
|
8499
8519
|
init_src();
|
|
8500
8520
|
import { $ as $4 } from "execa";
|
|
8501
|
-
|
|
8521
|
+
import { basename as basename3 } from "path";
|
|
8522
|
+
|
|
8523
|
+
// src/logNoise.ts
|
|
8524
|
+
var DEFAULT_REMIND_MS = 10 * 6e4;
|
|
8525
|
+
function timesWord(n) {
|
|
8526
|
+
const tens = n % 100;
|
|
8527
|
+
if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
|
|
8528
|
+
return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
|
|
8529
|
+
}
|
|
8530
|
+
function makeRepeatFilter(opts = {}) {
|
|
8531
|
+
const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
|
|
8532
|
+
const streaks = /* @__PURE__ */ new Map();
|
|
8533
|
+
return {
|
|
8534
|
+
say(key, message, now) {
|
|
8535
|
+
const prev = streaks.get(key);
|
|
8536
|
+
if (!prev || prev.message !== message) {
|
|
8537
|
+
const tail = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
|
|
8538
|
+
streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
|
|
8539
|
+
return `${message}${tail}`;
|
|
8540
|
+
}
|
|
8541
|
+
prev.hidden += 1;
|
|
8542
|
+
if (now - prev.saidAt < remindAfterMs) return null;
|
|
8543
|
+
const hidden = prev.hidden;
|
|
8544
|
+
const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
|
|
8545
|
+
prev.saidAt = now;
|
|
8546
|
+
prev.hidden = 0;
|
|
8547
|
+
return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
|
|
8548
|
+
},
|
|
8549
|
+
forget(key) {
|
|
8550
|
+
streaks.delete(key);
|
|
8551
|
+
}
|
|
8552
|
+
};
|
|
8553
|
+
}
|
|
8554
|
+
|
|
8555
|
+
// src/netTimeout.ts
|
|
8556
|
+
import { basename as basename2 } from "path";
|
|
8557
|
+
var GH_CALL_TIMEOUT_MS = 15e3;
|
|
8558
|
+
var GIT_LOCAL_TIMEOUT_MS = 5e3;
|
|
8559
|
+
var HARD_CAP_GRACE_MS = 2e3;
|
|
8560
|
+
var CallTimedOutError = class extends Error {
|
|
8561
|
+
timedOut = true;
|
|
8562
|
+
constructor(call, timeoutMs) {
|
|
8563
|
+
super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
|
|
8564
|
+
this.name = "CallTimedOutError";
|
|
8565
|
+
}
|
|
8566
|
+
};
|
|
8567
|
+
function isTimedOut(outcome) {
|
|
8568
|
+
return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
|
|
8569
|
+
}
|
|
8570
|
+
function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
|
|
8571
|
+
const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
|
|
8572
|
+
if (!isTimedOut(outcome)) {
|
|
8573
|
+
timeoutNoise.forget(noiseKey(scope, call));
|
|
8574
|
+
return false;
|
|
8575
|
+
}
|
|
8576
|
+
const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
|
|
8577
|
+
if (say) warn(say);
|
|
8578
|
+
return true;
|
|
8579
|
+
}
|
|
8580
|
+
function noiseKey(scope, call) {
|
|
8581
|
+
return `timeout:${scope}:${call}`;
|
|
8582
|
+
}
|
|
8583
|
+
var timeoutNoise = makeRepeatFilter();
|
|
8584
|
+
function humanMs(ms) {
|
|
8585
|
+
return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
|
|
8586
|
+
}
|
|
8587
|
+
function destroyPipes(child) {
|
|
8588
|
+
for (const pipe of [child.stdout, child.stderr, child.stdin]) {
|
|
8589
|
+
try {
|
|
8590
|
+
pipe?.destroy?.();
|
|
8591
|
+
} catch {
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
|
|
8596
|
+
let timer;
|
|
8597
|
+
const capped = new Promise((_, reject) => {
|
|
8598
|
+
timer = setTimeout(() => {
|
|
8599
|
+
child.kill("SIGKILL");
|
|
8600
|
+
destroyPipes(child);
|
|
8601
|
+
reject(new CallTimedOutError(call, timeoutMs));
|
|
8602
|
+
}, timeoutMs + HARD_CAP_GRACE_MS);
|
|
8603
|
+
timer.unref?.();
|
|
8604
|
+
});
|
|
8605
|
+
try {
|
|
8606
|
+
const res = await Promise.race([child, capped]);
|
|
8607
|
+
noteTimedOut(scope, call, timeoutMs, res, warn);
|
|
8608
|
+
return res;
|
|
8609
|
+
} catch (e) {
|
|
8610
|
+
noteTimedOut(scope, call, timeoutMs, e, warn);
|
|
8611
|
+
throw e;
|
|
8612
|
+
} finally {
|
|
8613
|
+
if (timer) clearTimeout(timer);
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8616
|
+
function callLabel(cmd2, args, head = 4) {
|
|
8617
|
+
const shown = args.slice(0, head).join(" ");
|
|
8618
|
+
return `${cmd2} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
|
|
8619
|
+
}
|
|
8620
|
+
function gitCallLabel(repo, args) {
|
|
8621
|
+
return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
|
|
8622
|
+
}
|
|
8623
|
+
|
|
8624
|
+
// src/gh.ts
|
|
8625
|
+
async function runGh(args, timeoutMs, cwd) {
|
|
8626
|
+
const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
|
|
8627
|
+
const child = cwd ? $4({ cwd, timeout: timeoutMs })`gh ${args}` : $4({ timeout: timeoutMs })`gh ${args}`;
|
|
8628
|
+
return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
|
|
8629
|
+
}
|
|
8630
|
+
function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
|
|
8631
|
+
return (args) => runGh(args, timeoutMs);
|
|
8632
|
+
}
|
|
8633
|
+
function ghRepoRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
|
|
8634
|
+
return (args, cwd) => runGh(args, timeoutMs, cwd);
|
|
8635
|
+
}
|
|
8636
|
+
var realRunner = ghRunner();
|
|
8637
|
+
var realRepoRunner = ghRepoRunner();
|
|
8502
8638
|
async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
|
|
8503
8639
|
if (!branch || !repoDir) return [];
|
|
8504
8640
|
try {
|
|
@@ -8523,7 +8659,12 @@ function firstLine(s) {
|
|
|
8523
8659
|
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
|
|
8524
8660
|
}
|
|
8525
8661
|
var realChecksRunner = async (pr) => {
|
|
8526
|
-
const r = await
|
|
8662
|
+
const r = await withHardTimeout(
|
|
8663
|
+
$4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
|
|
8664
|
+
"gh",
|
|
8665
|
+
callLabel("gh", ["pr", "checks", pr]),
|
|
8666
|
+
15e3
|
|
8667
|
+
);
|
|
8527
8668
|
const stdout = r.stdout ?? "";
|
|
8528
8669
|
const stderr = r.stderr ?? "";
|
|
8529
8670
|
if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
|
|
@@ -9085,7 +9226,7 @@ function timelineState(db2, now = Date.now(), overmindLog = []) {
|
|
|
9085
9226
|
id: r.id,
|
|
9086
9227
|
name: r.name,
|
|
9087
9228
|
slug: r.task_slug,
|
|
9088
|
-
repo:
|
|
9229
|
+
repo: basename4(r.repo || "") || "?",
|
|
9089
9230
|
status: r.status,
|
|
9090
9231
|
area: r.area,
|
|
9091
9232
|
start: sqliteIso(r.created_at),
|
|
@@ -10862,6 +11003,14 @@ import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
|
10862
11003
|
import { isAbsolute, join as join13, sep as sep2 } from "path";
|
|
10863
11004
|
import { $ as $5 } from "execa";
|
|
10864
11005
|
init_swarmFiles();
|
|
11006
|
+
function gitNet(repo, args, timeoutMs) {
|
|
11007
|
+
return withHardTimeout(
|
|
11008
|
+
$5({ reject: false, timeout: timeoutMs })`git -C ${repo} ${args}`,
|
|
11009
|
+
"git",
|
|
11010
|
+
gitCallLabel(repo, args),
|
|
11011
|
+
timeoutMs
|
|
11012
|
+
).catch(() => null);
|
|
11013
|
+
}
|
|
10865
11014
|
var WorktreeBusyError = class extends Error {
|
|
10866
11015
|
constructor(branch, worktreePath) {
|
|
10867
11016
|
super(`worktree \u0437\u0430\u043D\u044F\u0442: ${worktreePath} (\u0432\u0435\u0442\u043A\u0430 ${branch}) \u2014 \u043F\u0440\u0438\u0431\u0435\u0440\u0438 worktree \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438 \u043F\u0440\u043E\u0433\u043E\u043D (swarm cancel ${branch})`);
|
|
@@ -11066,8 +11215,9 @@ async function remoteMainRef(repo) {
|
|
|
11066
11215
|
throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
|
|
11067
11216
|
}
|
|
11068
11217
|
var GITHUB_REMOTE_RE = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?\/?$/i;
|
|
11069
|
-
async function remoteRepoFullName(repo) {
|
|
11070
|
-
const
|
|
11218
|
+
async function remoteRepoFullName(repo, timeoutMs = GIT_LOCAL_TIMEOUT_MS) {
|
|
11219
|
+
const res = await gitNet(repo, ["remote", "get-url", "origin"], timeoutMs);
|
|
11220
|
+
const url = res?.stdout.trim() ?? "";
|
|
11071
11221
|
const m = url.match(GITHUB_REMOTE_RE);
|
|
11072
11222
|
return m ? m[1] : null;
|
|
11073
11223
|
}
|
|
@@ -11094,7 +11244,7 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
|
|
|
11094
11244
|
|
|
11095
11245
|
// src/shotUpload.ts
|
|
11096
11246
|
import { readFile as fsReadFile } from "fs/promises";
|
|
11097
|
-
import { basename as
|
|
11247
|
+
import { basename as basename5, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
11098
11248
|
var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
11099
11249
|
|
|
11100
11250
|
// src/taskless.ts
|
|
@@ -12194,7 +12344,7 @@ var thisHost = loadHostIdentity({ home: SWARM_HOME, envHostId: process.env.SWARM
|
|
|
12194
12344
|
function fmtRun(r, opts = {}) {
|
|
12195
12345
|
const mark = STATUS_MARK[r.status] ?? "?";
|
|
12196
12346
|
const area = r.area ? ` [${r.area}]` : "";
|
|
12197
|
-
const repo = r.repo ? ` (${
|
|
12347
|
+
const repo = r.repo ? ` (${basename6(r.repo)})` : "";
|
|
12198
12348
|
const q = r.status === "blocked" ? " \u2190 \u0436\u0434\u0451\u0442 \u043E\u0442\u0432\u0435\u0442\u0430" : "";
|
|
12199
12349
|
const why = r.status === "stopped" && r.stop_reason ? ` \u2014 ${r.stop_reason}` : "";
|
|
12200
12350
|
const priority = r.priority ? ` \u2691${r.priority > 0 ? "+" : ""}${r.priority}` : "";
|
|
@@ -12952,7 +13102,7 @@ switch (cmd) {
|
|
|
12952
13102
|
process.exit(1);
|
|
12953
13103
|
}
|
|
12954
13104
|
const run = db.getRun(runId);
|
|
12955
|
-
if (run) console.log(`#${run.id} ${run.name} ${run.task_slug}${run.repo ? ` (${
|
|
13105
|
+
if (run) console.log(`#${run.id} ${run.name} ${run.task_slug}${run.repo ? ` (${basename6(run.repo)})` : ""}`);
|
|
12956
13106
|
const events = db.events(runId);
|
|
12957
13107
|
if (events.length === 0) console.log("\u0441\u043E\u0431\u044B\u0442\u0438\u0439 \u043D\u0435\u0442");
|
|
12958
13108
|
else events.forEach((e) => console.log(`${e.at} ${e.kind}${e.text ? " " + e.text : ""}`));
|
package/dist/overmind.js
CHANGED
|
@@ -1212,8 +1212,8 @@ var init_runGuards = __esm({
|
|
|
1212
1212
|
function hasZoneModel(m) {
|
|
1213
1213
|
return m.tasks > 0 && m.zones.length > 0;
|
|
1214
1214
|
}
|
|
1215
|
-
function testFileFeature(
|
|
1216
|
-
const head =
|
|
1215
|
+
function testFileFeature(basename7) {
|
|
1216
|
+
const head = basename7.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
|
|
1217
1217
|
return head && !GENERIC_DIRS.has(head) ? head : null;
|
|
1218
1218
|
}
|
|
1219
1219
|
function pathZoneCandidate(path) {
|
|
@@ -1850,10 +1850,24 @@ function asTokenUsage(m) {
|
|
|
1850
1850
|
function budgetExhausted(result) {
|
|
1851
1851
|
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1852
1852
|
}
|
|
1853
|
+
function sessionAbort(result) {
|
|
1854
|
+
if (budgetExhausted(result)) return null;
|
|
1855
|
+
const reason = result.terminal_reason ?? "";
|
|
1856
|
+
const subtype = result.subtype ?? "";
|
|
1857
|
+
if (reason === "aborted_tools") return { kind: "tools", raw: reason };
|
|
1858
|
+
if (reason === "aborted_streaming" || reason === "api_error") return { kind: "link", raw: reason };
|
|
1859
|
+
if (HONEST_ENDINGS.has(subtype) || HONEST_ENDINGS.has(reason)) return null;
|
|
1860
|
+
const failed = result.is_error === true || subtype === "error_during_execution" || reason.startsWith("aborted");
|
|
1861
|
+
if (!failed) return null;
|
|
1862
|
+
const raw = reason && reason !== "completed" ? reason : subtype || reason || "\u043E\u0448\u0438\u0431\u043A\u0430 \u0431\u0435\u0437 \u043C\u0435\u0442\u043A\u0438";
|
|
1863
|
+
return { kind: "other", raw };
|
|
1864
|
+
}
|
|
1865
|
+
var HONEST_ENDINGS;
|
|
1853
1866
|
var init_claudeResult = __esm({
|
|
1854
1867
|
"src/claudeResult.ts"() {
|
|
1855
1868
|
"use strict";
|
|
1856
1869
|
init_pricing();
|
|
1870
|
+
HONEST_ENDINGS = /* @__PURE__ */ new Set(["error_max_turns"]);
|
|
1857
1871
|
}
|
|
1858
1872
|
});
|
|
1859
1873
|
|
|
@@ -1928,6 +1942,9 @@ function writeMcpConfig(spec) {
|
|
|
1928
1942
|
writeFileSync2(path, JSON.stringify(cfg, null, 2));
|
|
1929
1943
|
return path;
|
|
1930
1944
|
}
|
|
1945
|
+
function abortField(abort) {
|
|
1946
|
+
return abort ? { aborted: abort } : {};
|
|
1947
|
+
}
|
|
1931
1948
|
var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
|
|
1932
1949
|
var init_claudeCode = __esm({
|
|
1933
1950
|
"src/executors/claudeCode.ts"() {
|
|
@@ -1966,7 +1983,10 @@ var init_claudeCode = __esm({
|
|
|
1966
1983
|
durationMs: spend.durationMs,
|
|
1967
1984
|
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1968
1985
|
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1969
|
-
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1986
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {},
|
|
1987
|
+
// TASWM-487: и «оборвалось» — третий факт об этом же итоге. Порядок важен: потолок
|
|
1988
|
+
// трат sessionAbort отсекает сам, поэтому оба ключа одновременно не появятся.
|
|
1989
|
+
...abortField(sessionAbort(result))
|
|
1970
1990
|
};
|
|
1971
1991
|
},
|
|
1972
1992
|
session: {
|
|
@@ -2887,12 +2907,50 @@ var init_heartbeat = __esm({
|
|
|
2887
2907
|
}
|
|
2888
2908
|
});
|
|
2889
2909
|
|
|
2910
|
+
// src/logNoise.ts
|
|
2911
|
+
function timesWord(n) {
|
|
2912
|
+
const tens = n % 100;
|
|
2913
|
+
if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
|
|
2914
|
+
return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
|
|
2915
|
+
}
|
|
2916
|
+
function makeRepeatFilter(opts = {}) {
|
|
2917
|
+
const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
|
|
2918
|
+
const streaks = /* @__PURE__ */ new Map();
|
|
2919
|
+
return {
|
|
2920
|
+
say(key, message, now) {
|
|
2921
|
+
const prev = streaks.get(key);
|
|
2922
|
+
if (!prev || prev.message !== message) {
|
|
2923
|
+
const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
|
|
2924
|
+
streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
|
|
2925
|
+
return `${message}${tail2}`;
|
|
2926
|
+
}
|
|
2927
|
+
prev.hidden += 1;
|
|
2928
|
+
if (now - prev.saidAt < remindAfterMs) return null;
|
|
2929
|
+
const hidden = prev.hidden;
|
|
2930
|
+
const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
|
|
2931
|
+
prev.saidAt = now;
|
|
2932
|
+
prev.hidden = 0;
|
|
2933
|
+
return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
|
|
2934
|
+
},
|
|
2935
|
+
forget(key) {
|
|
2936
|
+
streaks.delete(key);
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
var DEFAULT_REMIND_MS;
|
|
2941
|
+
var init_logNoise = __esm({
|
|
2942
|
+
"src/logNoise.ts"() {
|
|
2943
|
+
"use strict";
|
|
2944
|
+
DEFAULT_REMIND_MS = 10 * 6e4;
|
|
2945
|
+
}
|
|
2946
|
+
});
|
|
2947
|
+
|
|
2890
2948
|
// src/agentToken.ts
|
|
2891
|
-
import { readFileSync as
|
|
2892
|
-
import { join as
|
|
2949
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
2950
|
+
import { join as join8 } from "path";
|
|
2893
2951
|
function loadAgentToken() {
|
|
2894
2952
|
try {
|
|
2895
|
-
const s = JSON.parse(
|
|
2953
|
+
const s = JSON.parse(readFileSync6(STORE_PATH, "utf8"));
|
|
2896
2954
|
return s.token ? s : null;
|
|
2897
2955
|
} catch {
|
|
2898
2956
|
return null;
|
|
@@ -2945,7 +3003,7 @@ var init_agentToken = __esm({
|
|
|
2945
3003
|
"src/agentToken.ts"() {
|
|
2946
3004
|
"use strict";
|
|
2947
3005
|
init_config();
|
|
2948
|
-
STORE_PATH =
|
|
3006
|
+
STORE_PATH = join8(SWARM_HOME, "agent.json");
|
|
2949
3007
|
base = (api) => api.replace(/\/$/, "");
|
|
2950
3008
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2951
3009
|
}
|
|
@@ -3304,11 +3362,100 @@ var init_procCleanup = __esm({
|
|
|
3304
3362
|
}
|
|
3305
3363
|
});
|
|
3306
3364
|
|
|
3365
|
+
// src/netTimeout.ts
|
|
3366
|
+
import { basename as basename2 } from "path";
|
|
3367
|
+
function isTimedOut(outcome) {
|
|
3368
|
+
return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
|
|
3369
|
+
}
|
|
3370
|
+
function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
|
|
3371
|
+
const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
|
|
3372
|
+
if (!isTimedOut(outcome)) {
|
|
3373
|
+
timeoutNoise.forget(noiseKey(scope, call));
|
|
3374
|
+
return false;
|
|
3375
|
+
}
|
|
3376
|
+
const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
|
|
3377
|
+
if (say) warn(say);
|
|
3378
|
+
return true;
|
|
3379
|
+
}
|
|
3380
|
+
function noiseKey(scope, call) {
|
|
3381
|
+
return `timeout:${scope}:${call}`;
|
|
3382
|
+
}
|
|
3383
|
+
function humanMs(ms) {
|
|
3384
|
+
return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
|
|
3385
|
+
}
|
|
3386
|
+
function destroyPipes(child) {
|
|
3387
|
+
for (const pipe of [child.stdout, child.stderr, child.stdin]) {
|
|
3388
|
+
try {
|
|
3389
|
+
pipe?.destroy?.();
|
|
3390
|
+
} catch {
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
|
|
3395
|
+
let timer;
|
|
3396
|
+
const capped = new Promise((_, reject) => {
|
|
3397
|
+
timer = setTimeout(() => {
|
|
3398
|
+
child.kill("SIGKILL");
|
|
3399
|
+
destroyPipes(child);
|
|
3400
|
+
reject(new CallTimedOutError(call, timeoutMs));
|
|
3401
|
+
}, timeoutMs + HARD_CAP_GRACE_MS);
|
|
3402
|
+
timer.unref?.();
|
|
3403
|
+
});
|
|
3404
|
+
try {
|
|
3405
|
+
const res = await Promise.race([child, capped]);
|
|
3406
|
+
noteTimedOut(scope, call, timeoutMs, res, warn);
|
|
3407
|
+
return res;
|
|
3408
|
+
} catch (e) {
|
|
3409
|
+
noteTimedOut(scope, call, timeoutMs, e, warn);
|
|
3410
|
+
throw e;
|
|
3411
|
+
} finally {
|
|
3412
|
+
if (timer) clearTimeout(timer);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function callLabel(cmd, args, head = 4) {
|
|
3416
|
+
const shown = args.slice(0, head).join(" ");
|
|
3417
|
+
return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
|
|
3418
|
+
}
|
|
3419
|
+
function gitCallLabel(repo, args) {
|
|
3420
|
+
return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
|
|
3421
|
+
}
|
|
3422
|
+
var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
|
|
3423
|
+
var init_netTimeout = __esm({
|
|
3424
|
+
"src/netTimeout.ts"() {
|
|
3425
|
+
"use strict";
|
|
3426
|
+
init_logNoise();
|
|
3427
|
+
GH_CALL_TIMEOUT_MS = 15e3;
|
|
3428
|
+
GIT_NET_TIMEOUT_MS = 15e3;
|
|
3429
|
+
GIT_LOCAL_TIMEOUT_MS = 5e3;
|
|
3430
|
+
GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
|
|
3431
|
+
HARD_CAP_GRACE_MS = 2e3;
|
|
3432
|
+
CallTimedOutError = class extends Error {
|
|
3433
|
+
timedOut = true;
|
|
3434
|
+
constructor(call, timeoutMs) {
|
|
3435
|
+
super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
|
|
3436
|
+
this.name = "CallTimedOutError";
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
timeoutNoise = makeRepeatFilter();
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3307
3443
|
// src/git.ts
|
|
3308
3444
|
import { existsSync as existsSync10, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3309
3445
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3310
3446
|
import { isAbsolute as isAbsolute2, join as join17, sep as sep2 } from "path";
|
|
3311
3447
|
import { $ as $2 } from "execa";
|
|
3448
|
+
function gitNet(repo, args, timeoutMs) {
|
|
3449
|
+
return withHardTimeout(
|
|
3450
|
+
$2({ reject: false, timeout: timeoutMs })`git -C ${repo} ${args}`,
|
|
3451
|
+
"git",
|
|
3452
|
+
gitCallLabel(repo, args),
|
|
3453
|
+
timeoutMs
|
|
3454
|
+
).catch(() => null);
|
|
3455
|
+
}
|
|
3456
|
+
function gitNetStrict(repo, args, timeoutMs) {
|
|
3457
|
+
return withHardTimeout($2({ timeout: timeoutMs })`git -C ${repo} ${args}`, "git", gitCallLabel(repo, args), timeoutMs);
|
|
3458
|
+
}
|
|
3312
3459
|
function isUnrecoverableRefError(e) {
|
|
3313
3460
|
if (e instanceof MissingRefError) return true;
|
|
3314
3461
|
const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
@@ -3533,26 +3680,27 @@ async function remoteMainRef(repo) {
|
|
|
3533
3680
|
throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
|
|
3534
3681
|
}
|
|
3535
3682
|
async function remoteBranchExists(repo, branch) {
|
|
3536
|
-
const res = await
|
|
3537
|
-
if (res.exitCode !== 0) return null;
|
|
3683
|
+
const res = await gitNet(repo, ["ls-remote", "--heads", "origin", `refs/heads/${branch}`], 5e3);
|
|
3684
|
+
if (!res || res.exitCode !== 0) return null;
|
|
3538
3685
|
return res.stdout.trim().length > 0;
|
|
3539
3686
|
}
|
|
3540
|
-
async function remoteBaseRef(repo, branch) {
|
|
3541
|
-
await
|
|
3687
|
+
async function remoteBaseRef(repo, branch, timeoutMs = GIT_NET_TIMEOUT_MS) {
|
|
3688
|
+
await gitNet(repo, ["fetch", "--quiet", "origin", branch], timeoutMs);
|
|
3542
3689
|
return `origin/${branch}`;
|
|
3543
3690
|
}
|
|
3544
|
-
async function remoteRepoFullName(repo) {
|
|
3545
|
-
const
|
|
3691
|
+
async function remoteRepoFullName(repo, timeoutMs = GIT_LOCAL_TIMEOUT_MS) {
|
|
3692
|
+
const res = await gitNet(repo, ["remote", "get-url", "origin"], timeoutMs);
|
|
3693
|
+
const url = res?.stdout.trim() ?? "";
|
|
3546
3694
|
const m = url.match(GITHUB_REMOTE_RE);
|
|
3547
3695
|
return m ? m[1] : null;
|
|
3548
3696
|
}
|
|
3549
3697
|
async function journalForBranch(repo, branch, slug) {
|
|
3550
|
-
await
|
|
3698
|
+
await gitNet(repo, ["fetch", "--quiet", "origin", branch], GIT_NET_TIMEOUT_MS);
|
|
3551
3699
|
const out = await $2({ reject: false })`git -C ${repo} show ${`origin/${branch}:journal/${slug}.md`}`;
|
|
3552
3700
|
return out.exitCode === 0 && out.stdout ? out.stdout : null;
|
|
3553
3701
|
}
|
|
3554
3702
|
async function prepareDetachedWorktree(repo, branch, worktree) {
|
|
3555
|
-
await
|
|
3703
|
+
await gitNetStrict(repo, ["fetch", "--quiet", "origin"], GIT_HEAVY_NET_TIMEOUT_MS);
|
|
3556
3704
|
const ref = `origin/${branch}`;
|
|
3557
3705
|
if (!await revParse(repo, `${ref}^{commit}`)) throw new MissingRefError(ref, repo);
|
|
3558
3706
|
await $2`git -C ${repo} worktree add --detach --force ${worktree} ${ref}`;
|
|
@@ -3569,7 +3717,7 @@ async function mergeRef(worktree, ref) {
|
|
|
3569
3717
|
return { clean: false, conflicts: u ? u.split("\n") : [], changed: false };
|
|
3570
3718
|
}
|
|
3571
3719
|
async function pushHeadTo(worktree, branch) {
|
|
3572
|
-
await
|
|
3720
|
+
await gitNetStrict(worktree, ["push", "origin", `HEAD:${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
|
|
3573
3721
|
}
|
|
3574
3722
|
async function snapshotWorktree(worktree, message) {
|
|
3575
3723
|
try {
|
|
@@ -3578,7 +3726,7 @@ async function snapshotWorktree(worktree, message) {
|
|
|
3578
3726
|
await $2({ reject: false })`git -C ${worktree} add -A`;
|
|
3579
3727
|
const commit = await $2({ reject: false })`git -C ${worktree} commit --no-verify -m ${message}`;
|
|
3580
3728
|
if (commit.exitCode !== 0) return false;
|
|
3581
|
-
await
|
|
3729
|
+
await gitNet(worktree, ["push"], GIT_NET_TIMEOUT_MS);
|
|
3582
3730
|
return true;
|
|
3583
3731
|
} catch {
|
|
3584
3732
|
return false;
|
|
@@ -3594,6 +3742,7 @@ var init_git = __esm({
|
|
|
3594
3742
|
"src/git.ts"() {
|
|
3595
3743
|
"use strict";
|
|
3596
3744
|
init_procCleanup();
|
|
3745
|
+
init_netTimeout();
|
|
3597
3746
|
init_swarmFiles();
|
|
3598
3747
|
WorktreeBusyError = class extends Error {
|
|
3599
3748
|
constructor(branch, worktreePath) {
|
|
@@ -4281,7 +4430,7 @@ var init_attachments = __esm({
|
|
|
4281
4430
|
|
|
4282
4431
|
// src/shotUpload.ts
|
|
4283
4432
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4284
|
-
import { basename as
|
|
4433
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join21 } from "path";
|
|
4285
4434
|
function shotMimetype(path) {
|
|
4286
4435
|
const dot = path.lastIndexOf(".");
|
|
4287
4436
|
if (dot < 0) return null;
|
|
@@ -4320,7 +4469,7 @@ async function readShots(paths, opts) {
|
|
|
4320
4469
|
skipped.push({ path, reason: `${(bytes.byteLength / 1024 / 1024).toFixed(1)} \u041C\u0411 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043B\u0438\u043C\u0438\u0442\u0430 ${maxBytes / 1024 / 1024} \u041C\u0411` });
|
|
4321
4470
|
continue;
|
|
4322
4471
|
}
|
|
4323
|
-
files.push({ path, fileName:
|
|
4472
|
+
files.push({ path, fileName: basename4(path), mimetype, bytes });
|
|
4324
4473
|
}
|
|
4325
4474
|
return { files, skipped };
|
|
4326
4475
|
}
|
|
@@ -6217,7 +6366,7 @@ var init_droneStore = __esm({
|
|
|
6217
6366
|
// src/overmind.ts
|
|
6218
6367
|
import { homedir as homedir5 } from "os";
|
|
6219
6368
|
import { existsSync as existsSync17, readFileSync as readFileSync20, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
|
|
6220
|
-
import { basename as
|
|
6369
|
+
import { basename as basename6, dirname as dirname4, join as join30 } from "path";
|
|
6221
6370
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
6222
6371
|
import { spawn as spawn3 } from "child_process";
|
|
6223
6372
|
|
|
@@ -8881,6 +9030,49 @@ function rotateDaemonLog(path, opts, io = realIo) {
|
|
|
8881
9030
|
return "rotated";
|
|
8882
9031
|
}
|
|
8883
9032
|
|
|
9033
|
+
// src/logStamp.ts
|
|
9034
|
+
import { format } from "util";
|
|
9035
|
+
function logStamp(at) {
|
|
9036
|
+
return at.toISOString();
|
|
9037
|
+
}
|
|
9038
|
+
function stampMessage(text, at) {
|
|
9039
|
+
const ts = logStamp(at);
|
|
9040
|
+
return text.split("\n").map((line) => line.length ? `${ts} ${line}` : ts).join("\n");
|
|
9041
|
+
}
|
|
9042
|
+
function utcOffsetText(at) {
|
|
9043
|
+
const minutes = -at.getTimezoneOffset();
|
|
9044
|
+
const sign2 = minutes < 0 ? "-" : "+";
|
|
9045
|
+
const abs = Math.abs(minutes);
|
|
9046
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
9047
|
+
return `UTC${sign2}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
9048
|
+
}
|
|
9049
|
+
function localZoneLine(at, zone) {
|
|
9050
|
+
const tz = zone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
9051
|
+
return `[overmind] \u0432\u0440\u0435\u043C\u044F \u0432 \u043B\u043E\u0433\u0435 \u2014 UTC, ISO-8601 \u0441 \u043C\u0438\u043B\u043B\u0438\u0441\u0435\u043A\u0443\u043D\u0434\u0430\u043C\u0438 (${logStamp(at)}); \u043C\u0435\u0441\u0442\u043D\u043E\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 ${utcOffsetText(at)}${tz ? ` (${tz})` : ""}`;
|
|
9052
|
+
}
|
|
9053
|
+
var LEVELS = ["log", "info", "warn", "error"];
|
|
9054
|
+
var installed = false;
|
|
9055
|
+
function installLogStamps(now = () => /* @__PURE__ */ new Date()) {
|
|
9056
|
+
if (installed) return () => {
|
|
9057
|
+
};
|
|
9058
|
+
installed = true;
|
|
9059
|
+
const original = LEVELS.map((level) => [level, console[level]]);
|
|
9060
|
+
for (const [level, orig] of original) {
|
|
9061
|
+
const write = orig.bind(console);
|
|
9062
|
+
console[level] = (...args) => {
|
|
9063
|
+
write(stampMessage(format(...args), now()));
|
|
9064
|
+
};
|
|
9065
|
+
}
|
|
9066
|
+
return () => {
|
|
9067
|
+
for (const [level, orig] of original) console[level] = orig;
|
|
9068
|
+
installed = false;
|
|
9069
|
+
};
|
|
9070
|
+
}
|
|
9071
|
+
|
|
9072
|
+
// src/overmind.ts
|
|
9073
|
+
init_logNoise();
|
|
9074
|
+
init_linkFailure();
|
|
9075
|
+
|
|
8884
9076
|
// src/repoRegistry.ts
|
|
8885
9077
|
init_config();
|
|
8886
9078
|
import { $ } from "execa";
|
|
@@ -8919,13 +9111,13 @@ async function buildRepoRegistry(paths) {
|
|
|
8919
9111
|
}
|
|
8920
9112
|
|
|
8921
9113
|
// src/repoMemory.ts
|
|
8922
|
-
import { readFileSync as
|
|
8923
|
-
import { join as
|
|
9114
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
9115
|
+
import { join as join9 } from "path";
|
|
8924
9116
|
var FILE = "repos.json";
|
|
8925
9117
|
function loadRememberedRepos(home) {
|
|
8926
9118
|
let raw;
|
|
8927
9119
|
try {
|
|
8928
|
-
raw =
|
|
9120
|
+
raw = readFileSync7(join9(home, FILE), "utf8");
|
|
8929
9121
|
} catch {
|
|
8930
9122
|
return [];
|
|
8931
9123
|
}
|
|
@@ -8940,7 +9132,7 @@ function loadRememberedRepos(home) {
|
|
|
8940
9132
|
function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
|
|
8941
9133
|
const clean = paths.filter((p) => p.trim().length > 0);
|
|
8942
9134
|
if (clean.length === 0) return;
|
|
8943
|
-
|
|
9135
|
+
writeFileSync7(join9(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
|
|
8944
9136
|
`);
|
|
8945
9137
|
}
|
|
8946
9138
|
function pickRepoPaths(args) {
|
|
@@ -9185,9 +9377,13 @@ async function refreshRepoMap(opts) {
|
|
|
9185
9377
|
added++;
|
|
9186
9378
|
}
|
|
9187
9379
|
scannedAt = stamp();
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9380
|
+
const outcome = `[overmind] \u043F\u0440\u043E\u0448\u0451\u043B \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C (${SCAN_REASON_TEXT[decision.reason]}): ${added > 0 ? `\u043D\u0430\u0448\u0451\u043B \u0435\u0449\u0451 ${added}` : "\u043D\u043E\u0432\u043E\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u0451\u043B"}, \u0432\u0441\u0435\u0433\u043E \u043A\u043B\u043E\u043D\u043E\u0432 ${clones.size}`;
|
|
9381
|
+
if (added > 0 || opts.state.announcedScan !== outcome) {
|
|
9382
|
+
opts.state.announcedScan = outcome;
|
|
9383
|
+
log(
|
|
9384
|
+
`${outcome}, \u0437\u0430 ${Date.now() - startedAt}\u043C\u0441. \u0418\u0441\u043A\u0430\u043B \u0432: ${opts.roots.length ? opts.roots.slice(0, 6).join(", ") : "\u043D\u0438\u0433\u0434\u0435 \u2014 \u043F\u043E\u0438\u0441\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)"}`
|
|
9385
|
+
);
|
|
9386
|
+
}
|
|
9191
9387
|
}
|
|
9192
9388
|
const registry = [...clones.values()].map((c) => ({
|
|
9193
9389
|
repoFullName: c.repoFullName,
|
|
@@ -9199,8 +9395,10 @@ async function refreshRepoMap(opts) {
|
|
|
9199
9395
|
scannedAt
|
|
9200
9396
|
);
|
|
9201
9397
|
if (!answer.ok) {
|
|
9202
|
-
|
|
9203
|
-
|
|
9398
|
+
announceMap(opts.state, log, [
|
|
9399
|
+
describeReposFailure(answer.kind, answer.detail),
|
|
9400
|
+
`[overmind] \u0438\u0442\u043E\u0433: \u043A\u043B\u043E\u043D\u043E\u0432 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E ${clones.size}, \u0441\u043F\u0438\u0441\u043E\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0440\u043E\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D (\u0441\u043C. \u0441\u0442\u0440\u043E\u043A\u0443 \u0432\u044B\u0448\u0435)`
|
|
9401
|
+
]);
|
|
9204
9402
|
return registry;
|
|
9205
9403
|
}
|
|
9206
9404
|
opts.state.unknown = false;
|
|
@@ -9209,18 +9407,39 @@ async function refreshRepoMap(opts) {
|
|
|
9209
9407
|
opts.state.roots = [...opts.roots];
|
|
9210
9408
|
opts.state.at = stamp();
|
|
9211
9409
|
if (wantedNames.length === 0) {
|
|
9212
|
-
log
|
|
9410
|
+
announceMap(opts.state, log, ["[overmind] \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435"]);
|
|
9213
9411
|
return registry;
|
|
9214
9412
|
}
|
|
9215
|
-
log(`[overmind] ${describeMapSummary(opts.state, opts.roots)}`);
|
|
9216
|
-
for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
|
|
9217
9413
|
const prefixOf = new Map(answer.repos.map((r) => [r.repoFullName, r.taskPrefix]));
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
log
|
|
9221
|
-
|
|
9414
|
+
announceMap(
|
|
9415
|
+
opts.state,
|
|
9416
|
+
log,
|
|
9417
|
+
[
|
|
9418
|
+
`[overmind] ${describeMapSummary(opts.state, opts.roots)}`,
|
|
9419
|
+
...opts.state.clones.map((c) => `[overmind] ${describeClone(c)}`),
|
|
9420
|
+
...opts.state.missing.map((name) => {
|
|
9421
|
+
const prefix = prefixOf.get(name);
|
|
9422
|
+
return `[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`;
|
|
9423
|
+
})
|
|
9424
|
+
],
|
|
9425
|
+
mapSignature(opts.state, opts.roots)
|
|
9426
|
+
);
|
|
9222
9427
|
return registry;
|
|
9223
9428
|
}
|
|
9429
|
+
function announceMap(state, log, lines, signature = lines.join("\n")) {
|
|
9430
|
+
if (state.announced === signature) return;
|
|
9431
|
+
const first = state.announced === void 0;
|
|
9432
|
+
state.announced = signature;
|
|
9433
|
+
if (!first) log("[overmind] \u043A\u0430\u0440\u0442\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C:");
|
|
9434
|
+
for (const line of lines) log(line);
|
|
9435
|
+
}
|
|
9436
|
+
function mapSignature(state, roots) {
|
|
9437
|
+
return [
|
|
9438
|
+
...state.clones.map((c) => `${c.repoFullName}=${c.path}${c.alternatives.length ? `+${c.alternatives.join("+")}` : ""}`),
|
|
9439
|
+
`missing:${state.missing.join(",")}`,
|
|
9440
|
+
`roots:${roots.join(",")}`
|
|
9441
|
+
].join("\n");
|
|
9442
|
+
}
|
|
9224
9443
|
function describeMapSummary(state, roots) {
|
|
9225
9444
|
const found = state.clones.length;
|
|
9226
9445
|
const where = roots.length ? roots.slice(0, 6).join(", ") : "\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)";
|
|
@@ -9917,17 +10136,20 @@ import { readdirSync as readdirSync10, readFileSync as readFileSync12 } from "fs
|
|
|
9917
10136
|
import { join as join16 } from "path";
|
|
9918
10137
|
init_executors();
|
|
9919
10138
|
function endedItself(outcome) {
|
|
9920
|
-
return outcome === "graceful" || outcome === "budget";
|
|
10139
|
+
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
10140
|
+
}
|
|
10141
|
+
function wasAborted(outcome) {
|
|
10142
|
+
return outcome === "aborted";
|
|
9921
10143
|
}
|
|
9922
10144
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
9923
|
-
function
|
|
10145
|
+
function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
9924
10146
|
const dir = findRunLogDir(runLogsDir, runLogKeys);
|
|
9925
|
-
if (!dir) return
|
|
10147
|
+
if (!dir) return null;
|
|
9926
10148
|
let files;
|
|
9927
10149
|
try {
|
|
9928
10150
|
files = readdirSync10(dir);
|
|
9929
10151
|
} catch {
|
|
9930
|
-
return
|
|
10152
|
+
return null;
|
|
9931
10153
|
}
|
|
9932
10154
|
let latest = null;
|
|
9933
10155
|
for (const f of files) {
|
|
@@ -9936,21 +10158,35 @@ function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
|
|
|
9936
10158
|
const idx = Number(m[1]);
|
|
9937
10159
|
if (!latest || idx > latest.idx) latest = { idx, name: f };
|
|
9938
10160
|
}
|
|
9939
|
-
if (!latest) return
|
|
9940
|
-
let raw;
|
|
10161
|
+
if (!latest) return null;
|
|
9941
10162
|
try {
|
|
9942
|
-
|
|
10163
|
+
return readFileSync12(join16(dir, latest.name), "utf8");
|
|
9943
10164
|
} catch {
|
|
9944
|
-
return
|
|
10165
|
+
return null;
|
|
9945
10166
|
}
|
|
10167
|
+
}
|
|
10168
|
+
function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
|
|
10169
|
+
const raw = readLatestResultRaw(runLogsDir, runLogKeys);
|
|
10170
|
+
if (raw == null) return "unknown";
|
|
9946
10171
|
const result = executor.parseResult(raw, null);
|
|
9947
10172
|
if (!result) return "crash";
|
|
9948
|
-
|
|
10173
|
+
if (result.stoppedByBudget) return "budget";
|
|
10174
|
+
if (result.aborted) return "aborted";
|
|
10175
|
+
return "graceful";
|
|
10176
|
+
}
|
|
10177
|
+
function abortOfExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
|
|
10178
|
+
const raw = readLatestResultRaw(runLogsDir, runLogKeys);
|
|
10179
|
+
if (raw == null) return null;
|
|
10180
|
+
return executor.parseResult(raw, null)?.aborted ?? null;
|
|
9949
10181
|
}
|
|
9950
10182
|
function makeExitClassifier(runLogsDir, runLogKeys, executor) {
|
|
9951
10183
|
const exec = executor ?? resolveExecutor();
|
|
9952
10184
|
return (run) => classifyExit(runLogsDir, runLogKeys(run.id), exec);
|
|
9953
10185
|
}
|
|
10186
|
+
function makeAbortReader(runLogsDir, runLogKeys, executor) {
|
|
10187
|
+
const exec = executor ?? resolveExecutor();
|
|
10188
|
+
return (run) => abortOfExit(runLogsDir, runLogKeys(run.id), exec);
|
|
10189
|
+
}
|
|
9954
10190
|
|
|
9955
10191
|
// src/orchestrator.ts
|
|
9956
10192
|
init_src();
|
|
@@ -10692,9 +10928,22 @@ init_git();
|
|
|
10692
10928
|
|
|
10693
10929
|
// src/gh.ts
|
|
10694
10930
|
init_src();
|
|
10931
|
+
init_netTimeout();
|
|
10695
10932
|
import { $ as $3 } from "execa";
|
|
10696
|
-
|
|
10697
|
-
|
|
10933
|
+
import { basename as basename3 } from "path";
|
|
10934
|
+
async function runGh(args, timeoutMs, cwd) {
|
|
10935
|
+
const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
|
|
10936
|
+
const child = cwd ? $3({ cwd, timeout: timeoutMs })`gh ${args}` : $3({ timeout: timeoutMs })`gh ${args}`;
|
|
10937
|
+
return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
|
|
10938
|
+
}
|
|
10939
|
+
function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
|
|
10940
|
+
return (args) => runGh(args, timeoutMs);
|
|
10941
|
+
}
|
|
10942
|
+
function ghRepoRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
|
|
10943
|
+
return (args, cwd) => runGh(args, timeoutMs, cwd);
|
|
10944
|
+
}
|
|
10945
|
+
var realRunner = ghRunner();
|
|
10946
|
+
var realRepoRunner = ghRepoRunner();
|
|
10698
10947
|
var GH_PR_RE = /^(?:https?:\/\/)?(?:www\.)?github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+(?:[/?#].*)?$/i;
|
|
10699
10948
|
function isGithubPrUrl(url) {
|
|
10700
10949
|
return GH_PR_RE.test(url.trim());
|
|
@@ -10784,7 +11033,7 @@ async function closePR(prUrl, run = realRunner) {
|
|
|
10784
11033
|
async function postPrComment(prUrl, body, run = realRunner) {
|
|
10785
11034
|
await run(["pr", "comment", prUrl, "--body", body]);
|
|
10786
11035
|
}
|
|
10787
|
-
var realCommentsRunner =
|
|
11036
|
+
var realCommentsRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "comments"], GH_CALL_TIMEOUT_MS);
|
|
10788
11037
|
var VERDICT_RE = /^[^A-Za-z]*(APPROVED?|LGTM|(?:REQUEST(?:ED)?\s+)?CHANGES(?:\s+REQUESTED)?)\b/i;
|
|
10789
11038
|
var APPROVE_RE = /^(APPROVE|LGTM)/i;
|
|
10790
11039
|
function verdictLabel(verdict) {
|
|
@@ -10826,7 +11075,7 @@ async function scanPrComments(prUrl, sinceIso, run, pick3) {
|
|
|
10826
11075
|
async function latestVerifyVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
|
|
10827
11076
|
return scanPrComments(prUrl, sinceIso, run, (body) => parseVerifyVerdictComment(body));
|
|
10828
11077
|
}
|
|
10829
|
-
var realPrCommitsRunner =
|
|
11078
|
+
var realPrCommitsRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "commits"], GH_CALL_TIMEOUT_MS);
|
|
10830
11079
|
async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
|
|
10831
11080
|
if (!isGithubPrUrl(prUrl)) return false;
|
|
10832
11081
|
let out;
|
|
@@ -10850,7 +11099,7 @@ async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
|
|
|
10850
11099
|
return Number.isFinite(ms) && (!Number.isFinite(sinceMs) || ms >= sinceMs);
|
|
10851
11100
|
});
|
|
10852
11101
|
}
|
|
10853
|
-
var realPrCommitTitlesRunner =
|
|
11102
|
+
var realPrCommitTitlesRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "commits"], GH_CALL_TIMEOUT_MS);
|
|
10854
11103
|
async function prCommitTitles(prUrl, run = realPrCommitTitlesRunner) {
|
|
10855
11104
|
if (!isGithubPrUrl(prUrl)) return [];
|
|
10856
11105
|
let out;
|
|
@@ -10889,7 +11138,12 @@ function firstLine(s) {
|
|
|
10889
11138
|
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
|
|
10890
11139
|
}
|
|
10891
11140
|
var realChecksRunner = async (pr) => {
|
|
10892
|
-
const r = await
|
|
11141
|
+
const r = await withHardTimeout(
|
|
11142
|
+
$3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
|
|
11143
|
+
"gh",
|
|
11144
|
+
callLabel("gh", ["pr", "checks", pr]),
|
|
11145
|
+
15e3
|
|
11146
|
+
);
|
|
10893
11147
|
const stdout = r.stdout ?? "";
|
|
10894
11148
|
const stderr = r.stderr ?? "";
|
|
10895
11149
|
if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
|
|
@@ -10958,7 +11212,15 @@ async function prCiStatus(pr, run = realChecksRunner) {
|
|
|
10958
11212
|
if (rows.some((r) => r.bucket === "pending")) return { bucket: "pending", failing: [], failingRunIds: [], ...counts };
|
|
10959
11213
|
return { bucket: "pass", failing: [], failingRunIds: [], ...counts };
|
|
10960
11214
|
}
|
|
10961
|
-
var realRunLogRunner = async (runId) =>
|
|
11215
|
+
var realRunLogRunner = async (runId) => {
|
|
11216
|
+
const r = await withHardTimeout(
|
|
11217
|
+
$3({ reject: false, timeout: 3e4, maxBuffer: 10 * 1024 * 1024 })`gh run view ${runId} --log-failed`,
|
|
11218
|
+
"gh",
|
|
11219
|
+
callLabel("gh", ["run", "view", runId]),
|
|
11220
|
+
3e4
|
|
11221
|
+
);
|
|
11222
|
+
return r.stdout;
|
|
11223
|
+
};
|
|
10962
11224
|
var CI_SUMMARY_LOG_CHARS = 4e3;
|
|
10963
11225
|
var CI_SUMMARY_MAX_RUNS = 2;
|
|
10964
11226
|
async function ciFailureSummary(failing, failingRunIds, logRun = realRunLogRunner) {
|
|
@@ -13233,7 +13495,7 @@ function makeEnvGate(opts) {
|
|
|
13233
13495
|
// src/web.ts
|
|
13234
13496
|
init_src();
|
|
13235
13497
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
13236
|
-
import { basename as
|
|
13498
|
+
import { basename as basename5 } from "path";
|
|
13237
13499
|
import { serve as serve2 } from "@hono/node-server";
|
|
13238
13500
|
|
|
13239
13501
|
// src/diff.ts
|
|
@@ -13439,7 +13701,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
13439
13701
|
// package.json
|
|
13440
13702
|
var package_default = {
|
|
13441
13703
|
name: "@taskless-app/swarm",
|
|
13442
|
-
version: "0.6.
|
|
13704
|
+
version: "0.6.23",
|
|
13443
13705
|
type: "module",
|
|
13444
13706
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
13445
13707
|
keywords: [
|
|
@@ -13489,7 +13751,7 @@ var package_default = {
|
|
|
13489
13751
|
execa: "^10.0.0",
|
|
13490
13752
|
zod: "3.25.76"
|
|
13491
13753
|
},
|
|
13492
|
-
gitHead: "
|
|
13754
|
+
gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
|
|
13493
13755
|
};
|
|
13494
13756
|
|
|
13495
13757
|
// src/version.ts
|
|
@@ -13703,7 +13965,7 @@ function timelineState(db, now = Date.now(), overmindLog = []) {
|
|
|
13703
13965
|
id: r.id,
|
|
13704
13966
|
name: r.name,
|
|
13705
13967
|
slug: r.task_slug,
|
|
13706
|
-
repo:
|
|
13968
|
+
repo: basename5(r.repo || "") || "?",
|
|
13707
13969
|
status: r.status,
|
|
13708
13970
|
area: r.area,
|
|
13709
13971
|
start: sqliteIso(r.created_at),
|
|
@@ -15345,9 +15607,9 @@ async function runSelfUpdate(io) {
|
|
|
15345
15607
|
if (!exists(join28(repo, "pnpm-lock.yaml"))) {
|
|
15346
15608
|
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
15347
15609
|
}
|
|
15348
|
-
const
|
|
15349
|
-
if (
|
|
15350
|
-
return { ok: false, step: "install", message: `pnpm install \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(
|
|
15610
|
+
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
15611
|
+
if (installed2.exitCode !== 0) {
|
|
15612
|
+
return { ok: false, step: "install", message: `pnpm install \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(installed2.stderr || installed2.stdout)}` };
|
|
15351
15613
|
}
|
|
15352
15614
|
await io.onStep("build", "\u0441\u043E\u0431\u0438\u0440\u0430\u044E \u043F\u0430\u043A\u0435\u0442\u044B \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430");
|
|
15353
15615
|
const built = await run("pnpm", ["--filter", `${DAEMON_PACKAGE}...`, "build"], repo);
|
|
@@ -15970,9 +16232,24 @@ var executorHasSession = (p) => EXECUTOR.session.exists(p);
|
|
|
15970
16232
|
var OVERMIND_ENTRY = THIS_FILE;
|
|
15971
16233
|
var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
|
|
15972
16234
|
var STALL_FLOOR_MS = Number(process.env.SWARM_STALL_FLOOR_MS) || 30 * 60 * 1e3;
|
|
16235
|
+
var DEAD_HOLDER_SILENCE_MS = Number(process.env.SWARM_DEAD_HOLDER_SILENCE_MS) || 3 * 60 * 60 * 1e3;
|
|
15973
16236
|
var RESPAWN_LIMIT = Number(process.env.SWARM_RESPAWN_LIMIT) || 2;
|
|
15974
16237
|
var RESPAWN_YOUNG_MS = Number(process.env.SWARM_RESPAWN_YOUNG_MS) || 60 * 1e3;
|
|
15975
|
-
var EXITED_NO_DONE_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D
|
|
16238
|
+
var EXITED_NO_DONE_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
|
|
16239
|
+
var ABORTED_STOP_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
|
|
16240
|
+
var EXIT_WORK_PUSHED = "\u0432\u0435\u0442\u043A\u0430 \u0435\u0441\u0442\u044C \u043D\u0430 origin \u2014 \u0435\u0441\u0442\u044C \u0447\u0442\u043E \u0441\u043F\u0430\u0441\u0430\u0442\u044C, PR \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0442";
|
|
16241
|
+
var EXIT_WORK_LOST = "\u0432\u0435\u0442\u043A\u0438 \u043D\u0430 origin \u043D\u0435\u0442 \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u043E";
|
|
16242
|
+
var EXIT_WORK_UNKNOWN = "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C origin \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u2014 \u0446\u0435\u043B\u0430 \u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430, \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E";
|
|
16243
|
+
var ABORT_TEXT = {
|
|
16244
|
+
tools: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u043D\u0430 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 \u2014 \u043E\u0431\u044B\u0447\u043D\u043E \u044D\u0442\u043E \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0441 \u0433\u0438\u0433\u0430\u043D\u0442\u0441\u043A\u0438\u043C \u0432\u044B\u0432\u043E\u0434\u043E\u043C (\u043F\u043E\u043B\u043D\u044B\u0439 \u0441\u044C\u044E\u0442 \u0442\u0435\u0441\u0442\u043E\u0432, \u043B\u043E\u0433 \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430): \u043E\u0442\u0432\u0435\u0442 \u0442\u0443\u043B\u0430 \u043D\u0435 \u0432\u043B\u0435\u0437\u0430\u0435\u0442 \u0438 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u043F\u0443\u0441\u0442\u044B\u043C",
|
|
16245
|
+
link: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u043D\u0430 \u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u043C (\u0441\u0442\u0440\u0438\u043C/\u043E\u0448\u0438\u0431\u043A\u0430 API) \u2014 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C",
|
|
16246
|
+
other: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F"
|
|
16247
|
+
};
|
|
16248
|
+
function abortReason(abort) {
|
|
16249
|
+
if (!abort) return "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
|
|
16250
|
+
return `${ABORT_TEXT[abort.kind]} (${abort.raw})`;
|
|
16251
|
+
}
|
|
16252
|
+
var ABORTED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u041E\u0411\u041E\u0420\u0412\u0410\u041B\u0410\u0421\u042C \u043D\u0430 \u043F\u043E\u043B\u0443\u0441\u043B\u043E\u0432\u0435 (\u043D\u0435 \u0442\u044B \u0435\u0451 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B). \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0412\u0410\u0416\u041D\u041E \u043F\u0440\u043E \u043E\u0431\u0440\u044B\u0432\u044B: \u0440\u0432\u0451\u0442\u0441\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u041E\u0422\u0412\u0415\u0422\u0415 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430, \u0447\u0435\u0439 \u0432\u044B\u0432\u043E\u0434 \u043E\u0433\u0440\u043E\u043C\u0435\u043D (\u043F\u043E\u043B\u043D\u044B\u0439 \u0441\u044C\u044E\u0442 \u0442\u0435\u0441\u0442\u043E\u0432, `git log` \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430, \u0434\u0430\u043C\u043F \u0444\u0430\u0439\u043B\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C). \u0413\u043D\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430\u0434\u043E \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u2014 \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043F\u0438\u0448\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0444\u0430\u0439\u043B (`LOG=$(mktemp -t suite)`) \u0438 \u0431\u0435\u0440\u0438 \u0438\u0437 \u043D\u0435\u0433\u043E \u0445\u0432\u043E\u0441\u0442 \u0438 \u0438\u0442\u043E\u0433\u043E\u0432\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u044B\u043D\u044E. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).";
|
|
15976
16253
|
var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0421\u0410\u041C\u0410, \u043D\u043E \u0442\u044B \u0442\u0430\u043A \u0438 \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043B done(pr_url) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0443\u0432\u0438\u0434\u0435\u043B \u0441\u0434\u0430\u0447\u0438. \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR (\u0435\u0441\u043B\u0438 \u0435\u0449\u0451 \u043D\u0435\u0442) \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url) \u2014 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0441\u0434\u0430\u043D\u043D\u043E\u0439. \u0423\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u0438\u043C\u0438\u0442/\u043D\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u2014 \u043D\u0435 \u043C\u043E\u043B\u043E\u0442\u0438 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0437\u0430\u0434\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask. \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439.";
|
|
15977
16254
|
var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
|
|
15978
16255
|
var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
|
|
@@ -16013,7 +16290,7 @@ var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430
|
|
|
16013
16290
|
var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
|
|
16014
16291
|
var CHANGES_WAKE_FAILED_MARK = "\u043F\u043E\u0431\u0443\u0434\u043A\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
|
|
16015
16292
|
var BUDGET_STOP_REASON = "\u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442: \u0441\u0435\u0441\u0441\u0438\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
|
|
16016
|
-
var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON];
|
|
16293
|
+
var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON, ABORTED_STOP_REASON];
|
|
16017
16294
|
var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
|
|
16018
16295
|
function reviewBudgetMark(cycle) {
|
|
16019
16296
|
return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
|
|
@@ -16806,6 +17083,11 @@ function droneLabel(run) {
|
|
|
16806
17083
|
function signalTitle(run, what) {
|
|
16807
17084
|
return `${run.task_slug || droneLabel(run)}: ${what}`;
|
|
16808
17085
|
}
|
|
17086
|
+
var tickNoise = makeRepeatFilter();
|
|
17087
|
+
function warnRepeating(key, message, now) {
|
|
17088
|
+
const line = tickNoise.say(key, message, now);
|
|
17089
|
+
if (line) console.error(line);
|
|
17090
|
+
}
|
|
16809
17091
|
function narrate(deps, text) {
|
|
16810
17092
|
try {
|
|
16811
17093
|
deps.narrate?.(text);
|
|
@@ -17577,14 +17859,17 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
17577
17859
|
return await failBeforeStart(deps, run, String(e));
|
|
17578
17860
|
}
|
|
17579
17861
|
}
|
|
17862
|
+
function holderStillHolds(deps, holder, now) {
|
|
17863
|
+
if (holder.pid != null) return deps.isAlive(holder.pid);
|
|
17864
|
+
return now - lastActivityMs(deps.db, holder) < DEAD_HOLDER_SILENCE_MS;
|
|
17865
|
+
}
|
|
17580
17866
|
async function healBusyWorktree(deps, run, e) {
|
|
17581
17867
|
const path = e.worktreePath;
|
|
17582
17868
|
if (!deps.releaseWorktree) return false;
|
|
17583
17869
|
if (deps.liveWorktrees?.().has(path)) return false;
|
|
17870
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
17584
17871
|
const holder = deps.db.listRuns().find((r) => r.id !== run.id && r.worktree_path === path && !isTerminalStatus(r.status));
|
|
17585
|
-
if (holder && (holder
|
|
17586
|
-
return false;
|
|
17587
|
-
}
|
|
17872
|
+
if (holder && holderStillHolds(deps, holder, now)) return false;
|
|
17588
17873
|
if (deps.isDirty && await deps.isDirty(path).catch(() => true)) {
|
|
17589
17874
|
deps.db.addEvent(
|
|
17590
17875
|
run.id,
|
|
@@ -17594,10 +17879,12 @@ async function healBusyWorktree(deps, run, e) {
|
|
|
17594
17879
|
narrate(deps, `${run.task_slug}: worktree \u0437\u0430\u043D\u044F\u0442 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439, \u043D\u043E \u0432 \u043D\u0451\u043C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
|
|
17595
17880
|
return false;
|
|
17596
17881
|
}
|
|
17882
|
+
const silence = holder && holder.pid == null ? `\u043C\u043E\u043B\u0447\u0430\u043B ${humanMinutes(now - lastActivityMs(deps.db, holder))}` : null;
|
|
17883
|
+
const why = silence ? `\u0434\u0435\u0440\u0436\u0430\u0442\u0435\u043B\u044C #${holder.id} (${holder.status}) ${silence} \u0438 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442 \u2014 \u043D\u0435 \u0436\u0434\u0451\u0442 \u043C\u0435\u0440\u0436\u0430, \u0430 \u0443\u043C\u0435\u0440` : "\u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0437\u0430 \u043D\u0438\u043C \u043D\u0435\u0442";
|
|
17597
17884
|
await deps.releaseWorktree(run.repo, path).catch(() => {
|
|
17598
17885
|
});
|
|
17599
|
-
deps.db.addEvent(run.id, "step", `worktree ${path} \u0434\u0435\u0440\u0436\u0430\u043B\u0430 \u043C\u0451\u0440\u0442\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u2014 \u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E \u0441\u043F\u0430\u0432\u043D (TASWM-476)`);
|
|
17600
|
-
if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D:
|
|
17886
|
+
deps.db.addEvent(run.id, "step", `worktree ${path} \u0434\u0435\u0440\u0436\u0430\u043B\u0430 \u043C\u0451\u0440\u0442\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F (${why}) \u2014 \u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E \u0441\u043F\u0430\u0432\u043D (TASWM-476)`);
|
|
17887
|
+
if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D: ${why}, \u0441\u043B\u0430\u0433 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id}`);
|
|
17601
17888
|
return true;
|
|
17602
17889
|
}
|
|
17603
17890
|
async function failBeforeStart(deps, run, reason) {
|
|
@@ -17882,7 +18169,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
17882
18169
|
if (deps.exclusiveProject && await deps.exclusiveProject(repo)) continue;
|
|
17883
18170
|
} catch {
|
|
17884
18171
|
}
|
|
17885
|
-
exclusiveRepos.push(
|
|
18172
|
+
exclusiveRepos.push(basename6(repo));
|
|
17886
18173
|
}
|
|
17887
18174
|
const otherHosts = [
|
|
17888
18175
|
...new Set(
|
|
@@ -17908,6 +18195,15 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
17908
18195
|
...hrefOf(worstRun?.task_url)
|
|
17909
18196
|
}));
|
|
17910
18197
|
}
|
|
18198
|
+
async function workFate(deps, run) {
|
|
18199
|
+
if (run.pr_url) return `PR \u043E\u0442\u043A\u0440\u044B\u0442: ${run.pr_url}`;
|
|
18200
|
+
if (!deps.branchOnOrigin) return "";
|
|
18201
|
+
const branch = run.branch || run.task_slug;
|
|
18202
|
+
const exists = await deps.branchOnOrigin(run.repo, branch).catch(() => null);
|
|
18203
|
+
if (exists === true) return `${EXIT_WORK_PUSHED} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
|
|
18204
|
+
if (exists === false) return `${EXIT_WORK_LOST} (\u0432\u0435\u0442\u043A\u0438 ${branch} \u043D\u0430 origin \u043D\u0435\u0442)`;
|
|
18205
|
+
return EXIT_WORK_UNKNOWN;
|
|
18206
|
+
}
|
|
17911
18207
|
async function deliveredPrUrl(deps, run) {
|
|
17912
18208
|
if (run.pr_url) return run.pr_url;
|
|
17913
18209
|
if (!deps.openPrsForBranch) return null;
|
|
@@ -18299,7 +18595,11 @@ async function tickOnce(deps) {
|
|
|
18299
18595
|
deps.db.addEvent(run.id, "step", `${SELF_REFERENCE_MARK}: PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 ${DAEMON_CODE_PATHS.join(", ")} \u2014 \u044D\u0441\u043A\u0430\u043B\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443, \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E`);
|
|
18300
18596
|
narrate(deps, `PR ${droneLabel(run)} \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0441\u0430\u043C \u0441\u0435\u0431\u044F \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442, \u044D\u0441\u043A\u0430\u043B\u0438\u0440\u0443\u044E \u0438 \u043D\u0435 \u0433\u043E\u043D\u044F\u044E \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E.`);
|
|
18301
18597
|
} catch (e) {
|
|
18302
|
-
|
|
18598
|
+
warnRepeating(
|
|
18599
|
+
`self-reference:${signal.key}`,
|
|
18600
|
+
`[overmind] \u0441\u0442\u043E\u0440\u043E\u0436: self-reference \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
|
|
18601
|
+
now
|
|
18602
|
+
);
|
|
18303
18603
|
}
|
|
18304
18604
|
}
|
|
18305
18605
|
continue;
|
|
@@ -18505,7 +18805,10 @@ async function tickOnce(deps) {
|
|
|
18505
18805
|
continue;
|
|
18506
18806
|
}
|
|
18507
18807
|
}
|
|
18508
|
-
if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
|
|
18808
|
+
if (!alive && (outcome === "graceful" || outcome === "aborted") && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
|
|
18809
|
+
const aborted = wasAborted(outcome);
|
|
18810
|
+
const abort = aborted ? deps.exitAbort?.(run) ?? null : null;
|
|
18811
|
+
const what = aborted ? abortReason(abort) : EXITED_NO_DONE_REASON;
|
|
18509
18812
|
const wt = run.worktree_path;
|
|
18510
18813
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
18511
18814
|
if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -18513,21 +18816,31 @@ async function tickOnce(deps) {
|
|
|
18513
18816
|
deps.db.addEvent(
|
|
18514
18817
|
run.id,
|
|
18515
18818
|
"respawned",
|
|
18516
|
-
`\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430 \u0431\u0435\u0437 done() \u2014 --continue, \u0434\u043E\u0432\u043E\u0436\u0443 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 (\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u044E \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435)`
|
|
18819
|
+
aborted ? `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: ${what} \u2014 --continue \u0441 \u043D\u0430\u043A\u0430\u0437\u043E\u043C \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0442\u044C \u0442\u044F\u0436\u0451\u043B\u0443\u044E \u043A\u043E\u043C\u0430\u043D\u0434\u0443` : `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430 \u0431\u0435\u0437 done() \u2014 --continue, \u0434\u043E\u0432\u043E\u0436\u0443 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 (\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u044E \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435)`
|
|
18820
|
+
);
|
|
18821
|
+
narrate(
|
|
18822
|
+
deps,
|
|
18823
|
+
aborted ? `${droneLabel(run)}: ${what}. \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue.` : `${droneLabel(run)} \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0432\u0451\u043B \u0434\u043E PR/done.`
|
|
18517
18824
|
);
|
|
18518
|
-
narrate(deps, `${droneLabel(run)} \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0432\u0451\u043B \u0434\u043E PR/done.`);
|
|
18519
18825
|
try {
|
|
18520
|
-
await deps.respawn(run, "continue", EXITED_CONTINUE_NUDGE);
|
|
18826
|
+
await deps.respawn(run, "continue", aborted ? ABORTED_CONTINUE_NUDGE : EXITED_CONTINUE_NUDGE);
|
|
18521
18827
|
} catch (e) {
|
|
18522
|
-
|
|
18523
|
-
deps.db.
|
|
18828
|
+
const failed = `${aborted ? ABORTED_STOP_REASON : EXITED_NO_DONE_REASON}; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C: ${String(e)}`;
|
|
18829
|
+
deps.db.updateRun(run.id, { status: "paused", stop_reason: failed });
|
|
18830
|
+
deps.db.addEvent(run.id, "paused", failed);
|
|
18524
18831
|
}
|
|
18525
18832
|
continue;
|
|
18526
18833
|
}
|
|
18527
|
-
const
|
|
18834
|
+
const marker = aborted ? ABORTED_STOP_REASON : EXITED_NO_DONE_REASON;
|
|
18835
|
+
const head = run.respawns >= RESPAWN_LIMIT ? `${marker} \u2014 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT}), \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440` : marker;
|
|
18836
|
+
const fate = await workFate(deps, run);
|
|
18837
|
+
const reason2 = [head, aborted ? what : null, fate].filter(Boolean).join("; ");
|
|
18528
18838
|
deps.db.updateRun(run.id, { status: "paused", stop_reason: reason2 });
|
|
18529
18839
|
deps.db.addEvent(run.id, "paused", reason2);
|
|
18530
|
-
narrate(
|
|
18840
|
+
narrate(
|
|
18841
|
+
deps,
|
|
18842
|
+
aborted ? `${droneLabel(run)}: ${what}. \u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, ${fate || "\u0441\u0443\u0434\u044C\u0431\u0430 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430"}.` : `${droneLabel(run)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u043B\u043E\u0433\u0435 (TASWM-229).`
|
|
18843
|
+
);
|
|
18531
18844
|
continue;
|
|
18532
18845
|
}
|
|
18533
18846
|
if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -18703,8 +19016,13 @@ async function tickOnce(deps) {
|
|
|
18703
19016
|
const signal = finalCall ? { ...item.signal, body: `${item.signal.body} ${LAST_REMINDER_SUFFIX}` } : item.signal;
|
|
18704
19017
|
try {
|
|
18705
19018
|
await az.notify(signal);
|
|
19019
|
+
tickNoise.forget(`notify:${signal.key}`);
|
|
18706
19020
|
} catch (e) {
|
|
18707
|
-
|
|
19021
|
+
warnRepeating(
|
|
19022
|
+
`notify:${signal.key}`,
|
|
19023
|
+
`[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
|
|
19024
|
+
now
|
|
19025
|
+
);
|
|
18708
19026
|
continue;
|
|
18709
19027
|
}
|
|
18710
19028
|
for (const key of item.keys) az.tracker.commit(key, now);
|
|
@@ -18726,8 +19044,13 @@ async function tickOnce(deps) {
|
|
|
18726
19044
|
try {
|
|
18727
19045
|
await az.resolve(batch);
|
|
18728
19046
|
az.tracker.forgetResolved(batch);
|
|
19047
|
+
tickNoise.forget("anomaly-resolve");
|
|
18729
19048
|
} catch (e) {
|
|
18730
|
-
|
|
19049
|
+
warnRepeating(
|
|
19050
|
+
"anomaly-resolve",
|
|
19051
|
+
`[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u0433\u0430\u0448\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0432\u0448\u0438\u0445\u0441\u044F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u0439 \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u043E, \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
|
|
19052
|
+
now
|
|
19053
|
+
);
|
|
18731
19054
|
}
|
|
18732
19055
|
}
|
|
18733
19056
|
}
|
|
@@ -18817,7 +19140,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
18817
19140
|
model: reviewRun.model,
|
|
18818
19141
|
review_model: reviewRun.review_model
|
|
18819
19142
|
});
|
|
18820
|
-
const wt = join30(deps.worktreesDir,
|
|
19143
|
+
const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
18821
19144
|
try {
|
|
18822
19145
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
18823
19146
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -18874,7 +19197,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
18874
19197
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
18875
19198
|
workspace_id: reviewRun.workspace_id
|
|
18876
19199
|
});
|
|
18877
|
-
const wt = join30(deps.worktreesDir,
|
|
19200
|
+
const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
18878
19201
|
try {
|
|
18879
19202
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
18880
19203
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -18950,7 +19273,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
18950
19273
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
18951
19274
|
workspace_id: verifiedRun.workspace_id
|
|
18952
19275
|
});
|
|
18953
|
-
const wt = join30(deps.worktreesDir,
|
|
19276
|
+
const wt = join30(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
18954
19277
|
try {
|
|
18955
19278
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
18956
19279
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -19200,6 +19523,8 @@ async function reportWorkspaceAccess(tl, db) {
|
|
|
19200
19523
|
}
|
|
19201
19524
|
async function runDaemon(opts) {
|
|
19202
19525
|
installCrashGuards();
|
|
19526
|
+
installLogStamps();
|
|
19527
|
+
console.log(localZoneLine(/* @__PURE__ */ new Date()));
|
|
19203
19528
|
const daemonStart = captureDaemonStart();
|
|
19204
19529
|
const latestRelease = fetchLatestVersion();
|
|
19205
19530
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
@@ -19780,6 +20105,11 @@ async function runDaemon(opts) {
|
|
|
19780
20105
|
// Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
|
|
19781
20106
|
// (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
|
|
19782
20107
|
exitOutcome: makeExitClassifier(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
|
|
20108
|
+
// TASWM-487: вид обрыва — из того же result-файла и теми же ключами каталога.
|
|
20109
|
+
exitAbort: makeAbortReader(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
|
|
20110
|
+
// TASWM-487: жива ли работа на origin. Тот же фолбэк repo, что у removeWorktree выше:
|
|
20111
|
+
// у прогона в основном репозитории демона run.repo пуст.
|
|
20112
|
+
branchOnOrigin: (repo, branch) => remoteBranchExists(repo || opts.repo, branch),
|
|
19783
20113
|
// Живой child-процесс (TASWM-89): долгая Bash-команда (тесты/сборка) — не «завис».
|
|
19784
20114
|
livePpids,
|
|
19785
20115
|
// Погасить дрона (SIGTERM) — reap внешне-остановленных прогонов (kill из дашборда:
|
|
@@ -19811,7 +20141,7 @@ async function runDaemon(opts) {
|
|
|
19811
20141
|
worktreesDir,
|
|
19812
20142
|
activeWorktreePaths: new Set(runsWithWorktree.filter((r) => !isTerminalStatus(r.status)).map((r) => r.worktree_path)),
|
|
19813
20143
|
containerRunPrefixes: runsWithWorktree.map((r) => ({
|
|
19814
|
-
prefix: composeProjectPrefix(
|
|
20144
|
+
prefix: composeProjectPrefix(basename6(r.worktree_path)),
|
|
19815
20145
|
active: !isTerminalStatus(r.status)
|
|
19816
20146
|
}))
|
|
19817
20147
|
}).then(() => {
|
|
@@ -20381,6 +20711,9 @@ if (isEntrypoint) {
|
|
|
20381
20711
|
}
|
|
20382
20712
|
}
|
|
20383
20713
|
export {
|
|
20714
|
+
ABORTED_CONTINUE_NUDGE,
|
|
20715
|
+
ABORTED_STOP_REASON,
|
|
20716
|
+
ABORT_TEXT,
|
|
20384
20717
|
AWAITING_CI_REASON,
|
|
20385
20718
|
BUDGET_STOP_REASON,
|
|
20386
20719
|
CHANGES_FRESH_WAKE_AFTER_MS,
|
|
@@ -20405,8 +20738,12 @@ export {
|
|
|
20405
20738
|
CI_WAKE_LIMIT,
|
|
20406
20739
|
CONFLICT_RESOLVE_COOLDOWN_MS,
|
|
20407
20740
|
CONTINUE_NUDGE,
|
|
20741
|
+
DEAD_HOLDER_SILENCE_MS,
|
|
20408
20742
|
EXITED_CONTINUE_NUDGE,
|
|
20409
20743
|
EXITED_NO_DONE_REASON,
|
|
20744
|
+
EXIT_WORK_LOST,
|
|
20745
|
+
EXIT_WORK_PUSHED,
|
|
20746
|
+
EXIT_WORK_UNKNOWN,
|
|
20410
20747
|
GATE_LOG_HEARTBEAT_TICKS,
|
|
20411
20748
|
HEADLESS_BLOCK,
|
|
20412
20749
|
HERO_CONTINUE_NUDGE,
|
|
@@ -20429,6 +20766,7 @@ export {
|
|
|
20429
20766
|
STORM_DEAD_LIMIT,
|
|
20430
20767
|
VERIFY_SPAWN_LIMIT,
|
|
20431
20768
|
WATCHDOG_PARK_REASON,
|
|
20769
|
+
abortReason,
|
|
20432
20770
|
buildChangesWakeNudge,
|
|
20433
20771
|
buildCiReadyNudge,
|
|
20434
20772
|
buildCiWakeNudge,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.23",
|
|
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": "af6c4af0f523891b5db805d9b4583021688ed1f5",
|
|
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",
|
|
@@ -46,6 +46,39 @@ CONTRIBUTING): КАКИМИ командами это зелёное получ
|
|
|
46
46
|
разрешение молча сдать: скажи прямо, чем проверял вместо них. Инфраструктуру
|
|
47
47
|
проекта под гейт роя НЕ достраивай.
|
|
48
48
|
|
|
49
|
+
## Тяжёлый прогон — в файл, а не в контекст
|
|
50
|
+
|
|
51
|
+
Вывод на тысячи строк не влезает в один результат инструмента, приходит пустым — и
|
|
52
|
+
сессия рвётся на полуслове, посреди твоей работы. В run-логах роя за июль–август это
|
|
53
|
+
**215 обрывов из 1104 выходов**: каждый пятый дрон умирает не от бага, а от
|
|
54
|
+
собственного `pnpm test`.
|
|
55
|
+
|
|
56
|
+
Порядок такой:
|
|
57
|
+
|
|
58
|
+
1. **Сначала спроси, надо ли гнать сьют целиком здесь.** Если рой работает в режиме
|
|
59
|
+
`verifyMode=ci`, общий прогон уже делает CI на этом же коммите — дублировать его на
|
|
60
|
+
машине владельца не надо (тебе про это скажут в задании). Твоё дело тогда — прицельно
|
|
61
|
+
те файлы, которых касается правка.
|
|
62
|
+
2. **Гонишь тяжёлое — пиши в файл, в контекст бери хвост.**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
LOG=$(mktemp -t suite) # свой файл: на машине не ты один
|
|
66
|
+
pnpm test > "$LOG" 2>&1; echo "exit=$?" # код — от ТЕСТОВ, не от tail
|
|
67
|
+
tail -40 "$LOG" # итог: сколько файлов, сколько тестов
|
|
68
|
+
grep -nE "FAIL|✗|✘|●" "$LOG" | head -40 # упавшие — точечно
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- Код возврата бери у самой команды (`echo "exit=$?"` сразу после неё). `cmd | tail`
|
|
72
|
+
всегда вернёт 0 — это код `tail`, и красный сьют так маскируется.
|
|
73
|
+
- Свой файл на прогон (`mktemp`), не общий `/tmp/suite.log`: дронов на машине несколько,
|
|
74
|
+
и они затрут лог друг другу — а ты прочитаешь чужой результат как свой.
|
|
75
|
+
- Доказательство в сдачу — итоговые строки из файла (`Test Files … Tests …`, `exit=0`),
|
|
76
|
+
а не простыня целиком. Полнота доказательства — в цифрах итога, не в объёме.
|
|
77
|
+
- То же касается `git log`, `git diff` без пути, дампа большого файла, `docker build`:
|
|
78
|
+
всё, что печатает бесконечно, идёт через файл.
|
|
79
|
+
- Это не «частичный прогон» из п. 2: прогон полный, обрезан только вывод. Экстраполяция
|
|
80
|
+
запрещена по-прежнему — итог читай, а не первые строки.
|
|
81
|
+
|
|
49
82
|
## Red flags — stop and go get evidence
|
|
50
83
|
|
|
51
84
|
- «should», «probably», «seems to», «кажется, работает»
|