@taskless-app/swarm 0.6.5 → 0.6.6
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 +277 -47
- package/dist/overmind.js +413 -64
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -765,6 +765,32 @@ var init_hosts = __esm({
|
|
|
765
765
|
}
|
|
766
766
|
});
|
|
767
767
|
|
|
768
|
+
// ../swarm-core/src/hostResources.ts
|
|
769
|
+
function parseHostResources(raw) {
|
|
770
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
771
|
+
const v = raw;
|
|
772
|
+
const num = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
|
|
773
|
+
const memFreeGb = num(v.memFreeGb);
|
|
774
|
+
const memTotalGb = num(v.memTotalGb);
|
|
775
|
+
if (memFreeGb == null || memTotalGb == null) return null;
|
|
776
|
+
if (typeof v.measuredAt !== "string" || Number.isNaN(Date.parse(v.measuredAt))) return null;
|
|
777
|
+
const cpu = num(v.cpuBusyPct);
|
|
778
|
+
return {
|
|
779
|
+
cpuBusyPct: cpu == null ? null : Math.min(100, Math.max(0, cpu)),
|
|
780
|
+
memFreeGb,
|
|
781
|
+
memTotalGb,
|
|
782
|
+
diskFreeGb: num(v.diskFreeGb),
|
|
783
|
+
diskTotalGb: num(v.diskTotalGb),
|
|
784
|
+
measuredAt: v.measuredAt
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
var init_hostResources = __esm({
|
|
788
|
+
"../swarm-core/src/hostResources.ts"() {
|
|
789
|
+
"use strict";
|
|
790
|
+
init_hosts();
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
|
|
768
794
|
// ../swarm-core/src/hostPool.ts
|
|
769
795
|
var init_hostPool = __esm({
|
|
770
796
|
"../swarm-core/src/hostPool.ts"() {
|
|
@@ -854,7 +880,12 @@ var init_cost = __esm({
|
|
|
854
880
|
});
|
|
855
881
|
|
|
856
882
|
// ../swarm-core/src/runGuards.ts
|
|
857
|
-
|
|
883
|
+
function remainingRunBudgetUsd(maxCostUsd, spentUsd) {
|
|
884
|
+
if (!(maxCostUsd > 0)) return null;
|
|
885
|
+
const spent = Number.isFinite(spentUsd) && spentUsd > 0 ? spentUsd : 0;
|
|
886
|
+
return Math.max(MIN_RUN_BUDGET_USD, Math.round((maxCostUsd - spent) * 100) / 100);
|
|
887
|
+
}
|
|
888
|
+
var DEFAULT_RUN_LIMITS, MIN_RUN_BUDGET_USD;
|
|
858
889
|
var init_runGuards = __esm({
|
|
859
890
|
"../swarm-core/src/runGuards.ts"() {
|
|
860
891
|
"use strict";
|
|
@@ -864,6 +895,7 @@ var init_runGuards = __esm({
|
|
|
864
895
|
stepTimeoutMs: 90 * 6e4,
|
|
865
896
|
lostMs: 30 * 6e4
|
|
866
897
|
};
|
|
898
|
+
MIN_RUN_BUDGET_USD = 0.05;
|
|
867
899
|
}
|
|
868
900
|
});
|
|
869
901
|
|
|
@@ -1267,6 +1299,20 @@ var init_codeZones = __esm({
|
|
|
1267
1299
|
}
|
|
1268
1300
|
});
|
|
1269
1301
|
|
|
1302
|
+
// ../swarm-core/src/effort.ts
|
|
1303
|
+
function toEffortLevel(raw) {
|
|
1304
|
+
const s = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
1305
|
+
return EFFORT_SET.has(s) ? s : null;
|
|
1306
|
+
}
|
|
1307
|
+
var EFFORT_LEVELS, EFFORT_SET;
|
|
1308
|
+
var init_effort = __esm({
|
|
1309
|
+
"../swarm-core/src/effort.ts"() {
|
|
1310
|
+
"use strict";
|
|
1311
|
+
EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
1312
|
+
EFFORT_SET = new Set(EFFORT_LEVELS);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1270
1316
|
// ../swarm-core/src/index.ts
|
|
1271
1317
|
var init_src = __esm({
|
|
1272
1318
|
"../swarm-core/src/index.ts"() {
|
|
@@ -1284,12 +1330,14 @@ var init_src = __esm({
|
|
|
1284
1330
|
init_names();
|
|
1285
1331
|
init_baseBranch();
|
|
1286
1332
|
init_hosts();
|
|
1333
|
+
init_hostResources();
|
|
1287
1334
|
init_hostPool();
|
|
1288
1335
|
init_hostCommands();
|
|
1289
1336
|
init_relief();
|
|
1290
1337
|
init_cost();
|
|
1291
1338
|
init_runGuards();
|
|
1292
1339
|
init_codeZones();
|
|
1340
|
+
init_effort();
|
|
1293
1341
|
}
|
|
1294
1342
|
});
|
|
1295
1343
|
|
|
@@ -1470,14 +1518,32 @@ function launchCostUsd(result, model) {
|
|
|
1470
1518
|
return costFromUsage(result.usage, model);
|
|
1471
1519
|
}
|
|
1472
1520
|
function launchSpend(result) {
|
|
1473
|
-
const u = result.usage ?? {};
|
|
1474
1521
|
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1475
|
-
|
|
1522
|
+
const sum = (u) => ({
|
|
1476
1523
|
tokensIn: num(u.input_tokens) + num(u.cache_creation_input_tokens) + num(u.cache_read_input_tokens),
|
|
1477
|
-
tokensOut: num(u.output_tokens)
|
|
1478
|
-
|
|
1524
|
+
tokensOut: num(u.output_tokens)
|
|
1525
|
+
});
|
|
1526
|
+
const spend = sum(result.usage ?? {});
|
|
1527
|
+
if (spend.tokensIn === 0 && spend.tokensOut === 0 && result.modelUsage) {
|
|
1528
|
+
for (const m of Object.values(result.modelUsage)) {
|
|
1529
|
+
const perModel = sum(asTokenUsage(m));
|
|
1530
|
+
spend.tokensIn += perModel.tokensIn;
|
|
1531
|
+
spend.tokensOut += perModel.tokensOut;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return { ...spend, durationMs: num(result.duration_ms) };
|
|
1535
|
+
}
|
|
1536
|
+
function asTokenUsage(m) {
|
|
1537
|
+
return {
|
|
1538
|
+
...m?.inputTokens !== void 0 ? { input_tokens: m.inputTokens } : {},
|
|
1539
|
+
...m?.outputTokens !== void 0 ? { output_tokens: m.outputTokens } : {},
|
|
1540
|
+
...m?.cacheCreationInputTokens !== void 0 ? { cache_creation_input_tokens: m.cacheCreationInputTokens } : {},
|
|
1541
|
+
...m?.cacheReadInputTokens !== void 0 ? { cache_read_input_tokens: m.cacheReadInputTokens } : {}
|
|
1479
1542
|
};
|
|
1480
1543
|
}
|
|
1544
|
+
function budgetExhausted(result) {
|
|
1545
|
+
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1546
|
+
}
|
|
1481
1547
|
var init_claudeResult = __esm({
|
|
1482
1548
|
"src/claudeResult.ts"() {
|
|
1483
1549
|
"use strict";
|
|
@@ -1489,8 +1555,10 @@ var init_claudeResult = __esm({
|
|
|
1489
1555
|
import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1490
1556
|
import { homedir } from "os";
|
|
1491
1557
|
import { join as join2 } from "path";
|
|
1492
|
-
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings) {
|
|
1558
|
+
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
1493
1559
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
1560
|
+
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
1561
|
+
const effortFlags = effort ? ["--effort", effort] : [];
|
|
1494
1562
|
const base3 = [
|
|
1495
1563
|
"--dangerously-skip-permissions",
|
|
1496
1564
|
...ISOLATION_FLAGS,
|
|
@@ -1498,6 +1566,8 @@ function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs
|
|
|
1498
1566
|
cfgPath,
|
|
1499
1567
|
...settings ? ["--settings", settings] : [],
|
|
1500
1568
|
...pluginFlags,
|
|
1569
|
+
...budgetFlags,
|
|
1570
|
+
...effortFlags,
|
|
1501
1571
|
"--output-format",
|
|
1502
1572
|
"json",
|
|
1503
1573
|
"-p",
|
|
@@ -1574,7 +1644,7 @@ var init_claudeCode = __esm({
|
|
|
1574
1644
|
const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
|
|
1575
1645
|
return {
|
|
1576
1646
|
bin: resolveClaudeBin(),
|
|
1577
|
-
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings)
|
|
1647
|
+
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
|
|
1578
1648
|
};
|
|
1579
1649
|
},
|
|
1580
1650
|
parseResult(raw, model) {
|
|
@@ -1587,7 +1657,10 @@ var init_claudeCode = __esm({
|
|
|
1587
1657
|
...est.warning ? { warning: est.warning } : {},
|
|
1588
1658
|
tokensIn: spend.tokensIn,
|
|
1589
1659
|
tokensOut: spend.tokensOut,
|
|
1590
|
-
durationMs: spend.durationMs
|
|
1660
|
+
durationMs: spend.durationMs,
|
|
1661
|
+
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1662
|
+
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1663
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1591
1664
|
};
|
|
1592
1665
|
},
|
|
1593
1666
|
session: {
|
|
@@ -1747,6 +1820,13 @@ ${notice}` : spec.prompt;
|
|
|
1747
1820
|
SKIP_GIT_CHECK_FLAG,
|
|
1748
1821
|
BYPASS_FLAG,
|
|
1749
1822
|
...spec.model ? ["-m", spec.model] : [],
|
|
1823
|
+
// TASWM-395: УСИЛИЕ РАССУЖДЕНИЯ (spec.effort) codex пока НЕ получает — и это сказано
|
|
1824
|
+
// вслух, как требует контракт адаптера (см. ExecutorLaunchSpec.effort), а не забыто.
|
|
1825
|
+
// Кандидат есть — `-c model_reasoning_effort=<low|medium|high>` — но словари не совпадают
|
|
1826
|
+
// (у нас пять уровней, xhigh/max пришлось бы схлопывать в high), а главное: непонятый
|
|
1827
|
+
// ключ в `-c` роняет НЕ один флаг, а весь спавн. Проверять это надо на живом codex, и
|
|
1828
|
+
// до такой проверки правильный размен — работающий codex на дефолтном усилии, а не
|
|
1829
|
+
// сломанный на настроенном. Провода до сюда доведены: останется дописать одну строку.
|
|
1750
1830
|
...mcpConfigArgs(spec),
|
|
1751
1831
|
prompt
|
|
1752
1832
|
];
|
|
@@ -1849,6 +1929,10 @@ __export(config_exports, {
|
|
|
1849
1929
|
SWARM_AUTO_REVIEW: () => SWARM_AUTO_REVIEW,
|
|
1850
1930
|
SWARM_BACKEND: () => SWARM_BACKEND,
|
|
1851
1931
|
SWARM_CLAIM_INTERVAL_MS: () => SWARM_CLAIM_INTERVAL_MS,
|
|
1932
|
+
SWARM_CPU_SANITY_BUSY: () => SWARM_CPU_SANITY_BUSY,
|
|
1933
|
+
SWARM_CPU_SANITY_LAG_MS: () => SWARM_CPU_SANITY_LAG_MS,
|
|
1934
|
+
SWARM_CPU_SANITY_LOAD_FACTOR: () => SWARM_CPU_SANITY_LOAD_FACTOR,
|
|
1935
|
+
SWARM_CPU_WINDOW_MS: () => SWARM_CPU_WINDOW_MS,
|
|
1852
1936
|
SWARM_DRONE_BRIDGE_MS: () => SWARM_DRONE_BRIDGE_MS,
|
|
1853
1937
|
SWARM_HOME: () => SWARM_HOME,
|
|
1854
1938
|
SWARM_LOW_DISK_GB: () => SWARM_LOW_DISK_GB,
|
|
@@ -1973,7 +2057,7 @@ function reviewCutoffAt() {
|
|
|
1973
2057
|
}
|
|
1974
2058
|
return at;
|
|
1975
2059
|
}
|
|
1976
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2060
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
|
|
1977
2061
|
var init_config = __esm({
|
|
1978
2062
|
"src/config.ts"() {
|
|
1979
2063
|
"use strict";
|
|
@@ -2003,6 +2087,10 @@ var init_config = __esm({
|
|
|
2003
2087
|
SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
|
|
2004
2088
|
SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
|
|
2005
2089
|
SWARM_MAX_CPU_BUSY = envNum("SWARM_MAX_CPU_BUSY", 0.85);
|
|
2090
|
+
SWARM_CPU_WINDOW_MS = Math.max(1e3, envNum("SWARM_CPU_WINDOW_MS", 3e3));
|
|
2091
|
+
SWARM_CPU_SANITY_BUSY = envNum("SWARM_CPU_SANITY_BUSY", 0.9);
|
|
2092
|
+
SWARM_CPU_SANITY_LOAD_FACTOR = envNum("SWARM_CPU_SANITY_LOAD_FACTOR", 0.5);
|
|
2093
|
+
SWARM_CPU_SANITY_LAG_MS = envNum("SWARM_CPU_SANITY_LAG_MS", 6e4);
|
|
2006
2094
|
SWARM_RELIEF_LOAD_FACTOR = envNum("SWARM_RELIEF_LOAD_FACTOR", 1.3);
|
|
2007
2095
|
SWARM_RELIEF_MIN_FREE_GB = envNum("SWARM_RELIEF_MIN_FREE_GB", 0.8);
|
|
2008
2096
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR = envNum("SWARM_RELIEF_MAX_FD_LOAD_FACTOR", 0.95);
|
|
@@ -2173,22 +2261,59 @@ function cpuBusyFromTicks(prev, cur) {
|
|
|
2173
2261
|
if (dTotal <= 0) return null;
|
|
2174
2262
|
return Math.min(1, Math.max(0, 1 - dIdle / dTotal));
|
|
2175
2263
|
}
|
|
2176
|
-
function
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2264
|
+
function cpuBusyFromSamples(prev, cur, minWindowMs = SWARM_CPU_WINDOW_MS) {
|
|
2265
|
+
if (!prev) return null;
|
|
2266
|
+
if (cur.at - prev.at < minWindowMs) return null;
|
|
2267
|
+
return cpuBusyFromTicks(prev, cur);
|
|
2268
|
+
}
|
|
2269
|
+
function cpuBusySane(busy, load1, cpus, highForMs, sanityBusy = SWARM_CPU_SANITY_BUSY, sanityLoadFactor = SWARM_CPU_SANITY_LOAD_FACTOR, lagMs = SWARM_CPU_SANITY_LAG_MS) {
|
|
2270
|
+
if (busy < sanityBusy) return true;
|
|
2271
|
+
if (highForMs < lagMs) return true;
|
|
2272
|
+
const cores = cpus > 0 ? cpus : 1;
|
|
2273
|
+
if (!Number.isFinite(load1) || load1 < 0) return true;
|
|
2274
|
+
return load1 >= cores * sanityLoadFactor;
|
|
2275
|
+
}
|
|
2276
|
+
function sampleCpuBusy(now = Date.now(), ticks = readCpuTicks(os.cpus())) {
|
|
2277
|
+
const cur = { at: now, ...ticks };
|
|
2278
|
+
if (!cpuWindowBase) {
|
|
2279
|
+
cpuWindowBase = cur;
|
|
2280
|
+
return cpuBusyValue;
|
|
2281
|
+
}
|
|
2282
|
+
if (cur.at - cpuWindowBase.at < SWARM_CPU_WINDOW_MS) return cpuBusyValue;
|
|
2283
|
+
cpuBusyValue = cpuBusyFromSamples(cpuWindowBase, cur);
|
|
2284
|
+
cpuWindowBase = cur;
|
|
2285
|
+
return cpuBusyValue;
|
|
2286
|
+
}
|
|
2287
|
+
function cpuBusyReading() {
|
|
2288
|
+
return cpuBusyValue;
|
|
2289
|
+
}
|
|
2290
|
+
function trustedCpuBusy(busy, load1, cpus, now) {
|
|
2291
|
+
if (busy == null || busy < SWARM_CPU_SANITY_BUSY) {
|
|
2292
|
+
cpuHighSince = null;
|
|
2293
|
+
return busy;
|
|
2294
|
+
}
|
|
2295
|
+
cpuHighSince ??= now;
|
|
2296
|
+
if (cpuBusySane(busy, load1, cpus, now - cpuHighSince)) return busy;
|
|
2297
|
+
if (now - cpuSanityWarnAt >= CPU_SANITY_WARN_EVERY_MS) {
|
|
2298
|
+
cpuSanityWarnAt = now;
|
|
2299
|
+
console.warn(
|
|
2300
|
+
`[overmind] \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u0438\u044E \xABCPU \u0437\u0430\u043D\u044F\u0442 ${Math.round(busy * 100)}%\xBB \u043D\u0435 \u0432\u0435\u0440\u044E: \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044F ${Math.round((now - cpuHighSince) / 1e3)}\u0441, \u0430 load1 \u0432\u0441\u0451 \u0435\u0449\u0451 ${load1.toFixed(2)} \u043F\u0440\u0438 ${cpus} \u044F\u0434\u0440\u0430\u0445 \u2014 \u0442\u0430\u043A \u043D\u0435 \u0431\u044B\u0432\u0430\u0435\u0442. \u0413\u0435\u0439\u0442 \u0441\u043F\u0430\u0432\u043D\u0430 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043F\u043E load average, \u043F\u043E\u043A\u0430 \u0437\u0430\u043C\u0435\u0440 \u043D\u0435 \u0441\u043E\u0439\u0434\u0451\u0442\u0441\u044F.`
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
return null;
|
|
2181
2304
|
}
|
|
2182
2305
|
function hostReading() {
|
|
2183
2306
|
const now = Date.now();
|
|
2184
2307
|
if (readingCache && now - readingCache.at < RESOURCES_CACHE_MS) return readingCache.reading;
|
|
2308
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
2309
|
+
const cpus = os.cpus().length;
|
|
2310
|
+
sampleCpuBusy(now);
|
|
2185
2311
|
const reading = {
|
|
2186
|
-
load1
|
|
2187
|
-
cpus
|
|
2312
|
+
load1,
|
|
2313
|
+
cpus,
|
|
2188
2314
|
freeGb: availableMemBytes() / 1e9,
|
|
2189
2315
|
fdLoadRatio: fileDescriptorLoadRatio(),
|
|
2190
|
-
cpuBusy:
|
|
2191
|
-
// TASWM-234: раз в тик (кэш выше) — иначе окно дельты схлопнется
|
|
2316
|
+
cpuBusy: trustedCpuBusy(cpuBusyReading(), load1, cpus, now),
|
|
2192
2317
|
totalGb: os.totalmem() / 1e9,
|
|
2193
2318
|
disk: diskGb()
|
|
2194
2319
|
};
|
|
@@ -2247,6 +2372,22 @@ function pressureReport(r, minFreeGb = SWARM_RELIEF_MIN_FREE_GB, maxCpuBusy = SW
|
|
|
2247
2372
|
function hostPressure() {
|
|
2248
2373
|
return pressureReport(hostReading());
|
|
2249
2374
|
}
|
|
2375
|
+
function resourcesForProd(r, measuredAt) {
|
|
2376
|
+
const gb = (v) => Math.round(v * 10) / 10;
|
|
2377
|
+
return {
|
|
2378
|
+
// null — busy% ещё не измерена (первый тик демона: она дельта тиков CPU). Ноль вместо
|
|
2379
|
+
// неё сказал бы «процессор простаивает» ровно тогда, когда мы про него не знаем.
|
|
2380
|
+
cpuBusyPct: r.cpuBusy == null ? null : Math.round(r.cpuBusy * 100),
|
|
2381
|
+
memFreeGb: gb(r.freeGb),
|
|
2382
|
+
memTotalGb: gb(r.totalGb),
|
|
2383
|
+
diskFreeGb: r.disk ? gb(r.disk.freeGb) : null,
|
|
2384
|
+
diskTotalGb: r.disk ? gb(r.disk.totalGb) : null,
|
|
2385
|
+
measuredAt
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
function hostResourcesForProd() {
|
|
2389
|
+
return resourcesForProd(hostReading(), (/* @__PURE__ */ new Date()).toISOString());
|
|
2390
|
+
}
|
|
2250
2391
|
function hostResourceSnapshot() {
|
|
2251
2392
|
const r = hostReading();
|
|
2252
2393
|
const reason = gateReason(r.load1, r.cpus, r.freeGb, r.fdLoadRatio, void 0, void 0, void 0, r.cpuBusy);
|
|
@@ -2261,13 +2402,17 @@ function hostResourceSnapshot() {
|
|
|
2261
2402
|
thresholds: { maxLoadFactor: SWARM_MAX_LOAD_FACTOR, minFreeGb: SWARM_MIN_FREE_GB, maxFdLoadFactor: SWARM_MAX_FD_LOAD_FACTOR, maxCpuBusy: SWARM_MAX_CPU_BUSY }
|
|
2262
2403
|
};
|
|
2263
2404
|
}
|
|
2264
|
-
var
|
|
2405
|
+
var cpuWindowBase, cpuBusyValue, cpuHighSince, cpuSanityWarnAt, CPU_SANITY_WARN_EVERY_MS, RESOURCES_CACHE_MS, readingCache;
|
|
2265
2406
|
var init_resources = __esm({
|
|
2266
2407
|
"src/resources.ts"() {
|
|
2267
2408
|
"use strict";
|
|
2268
2409
|
init_quietExec();
|
|
2269
2410
|
init_config();
|
|
2270
|
-
|
|
2411
|
+
cpuWindowBase = null;
|
|
2412
|
+
cpuBusyValue = null;
|
|
2413
|
+
cpuHighSince = null;
|
|
2414
|
+
cpuSanityWarnAt = 0;
|
|
2415
|
+
CPU_SANITY_WARN_EVERY_MS = 5 * 6e4;
|
|
2271
2416
|
RESOURCES_CACHE_MS = 250;
|
|
2272
2417
|
readingCache = null;
|
|
2273
2418
|
}
|
|
@@ -4503,6 +4648,10 @@ function rowToRun(row) {
|
|
|
4503
4648
|
area: row.area ?? null,
|
|
4504
4649
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
4505
4650
|
model: row.model ?? null,
|
|
4651
|
+
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
4652
|
+
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
4653
|
+
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
4654
|
+
effort: toEffortLevel(row.effort),
|
|
4506
4655
|
cost_usd: row.cost_usd ?? 0,
|
|
4507
4656
|
// Токены/время работы (TASWM-353) — старые прогоны без колонок → 0.
|
|
4508
4657
|
tokens_in: row.tokens_in ?? 0,
|
|
@@ -4572,6 +4721,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4572
4721
|
area TEXT,
|
|
4573
4722
|
depends_on TEXT,
|
|
4574
4723
|
model TEXT,
|
|
4724
|
+
effort TEXT,
|
|
4575
4725
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
4576
4726
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
4577
4727
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
@@ -4673,7 +4823,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4673
4823
|
// Чем обошёлся прогон, кроме денег (TASWM-353): токены вход/выход и время работы.
|
|
4674
4824
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
4675
4825
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
4676
|
-
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0"
|
|
4826
|
+
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
4827
|
+
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
4828
|
+
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
4829
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
4677
4830
|
]) {
|
|
4678
4831
|
try {
|
|
4679
4832
|
this.db.exec(alter);
|
|
@@ -4693,7 +4846,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4693
4846
|
createRun(input) {
|
|
4694
4847
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
4695
4848
|
const res = this.db.prepare(
|
|
4696
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4849
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4697
4850
|
).run(
|
|
4698
4851
|
input.task_slug,
|
|
4699
4852
|
name,
|
|
@@ -4706,6 +4859,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4706
4859
|
input.area ?? null,
|
|
4707
4860
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
4708
4861
|
input.model ?? null,
|
|
4862
|
+
input.effort ?? null,
|
|
4863
|
+
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
4709
4864
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
4710
4865
|
input.resolves_run_id ?? null,
|
|
4711
4866
|
input.reviews_run_id ?? null,
|
|
@@ -5630,6 +5785,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5630
5785
|
// предикат «репо есть локально» для решения, брать ли свободный прогон.
|
|
5631
5786
|
host;
|
|
5632
5787
|
repoExists;
|
|
5788
|
+
// TASWM-398: ЧЕМ эта машина исполняет прогоны — id адаптера (claude-code|codex).
|
|
5789
|
+
executorId;
|
|
5633
5790
|
// Зеркало.
|
|
5634
5791
|
runs = /* @__PURE__ */ new Map();
|
|
5635
5792
|
runtime = /* @__PURE__ */ new Map();
|
|
@@ -5707,6 +5864,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5707
5864
|
this.host = opts.host ?? null;
|
|
5708
5865
|
this.repoExists = opts.repoExists ?? existsSync5;
|
|
5709
5866
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
5867
|
+
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
5710
5868
|
this.outboxPath = opts.outboxPath;
|
|
5711
5869
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
5712
5870
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
@@ -5820,8 +5978,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5820
5978
|
runFields(patch) {
|
|
5821
5979
|
const out = {};
|
|
5822
5980
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
5981
|
+
if (patch.status === "running") out.executor = this.executorId;
|
|
5823
5982
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
5824
5983
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
5984
|
+
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
5825
5985
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
5826
5986
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
5827
5987
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
@@ -5892,6 +6052,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
5892
6052
|
area: input.area ?? null,
|
|
5893
6053
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
5894
6054
|
model: input.model ?? null,
|
|
6055
|
+
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
6056
|
+
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
6057
|
+
effort: input.effort ?? null,
|
|
5895
6058
|
cost_usd: 0,
|
|
5896
6059
|
tokens_in: 0,
|
|
5897
6060
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
@@ -5944,6 +6107,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5944
6107
|
status: "queued",
|
|
5945
6108
|
area: d.area,
|
|
5946
6109
|
model: d.model,
|
|
6110
|
+
effort: d.effort,
|
|
6111
|
+
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
5947
6112
|
name: d.name,
|
|
5948
6113
|
repo: d.repo,
|
|
5949
6114
|
depends_on: d.depends_on,
|
|
@@ -6721,11 +6886,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
6721
6886
|
const n = obj?.maxDrones;
|
|
6722
6887
|
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
6723
6888
|
const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
6889
|
+
const cost = obj?.swarmSettings?.maxCostUsd;
|
|
6724
6890
|
return {
|
|
6725
6891
|
maxDrones: Math.trunc(n),
|
|
6726
6892
|
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
6727
6893
|
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
6728
|
-
models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) }
|
|
6894
|
+
models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) },
|
|
6895
|
+
// TASWM-395: уровни усилия — из того же ответа и с той же семантикой, что модели:
|
|
6896
|
+
// старый прод поля не отдаёт → оба null, то есть env машины и дефолт харнесса.
|
|
6897
|
+
// toEffortLevel заодно отсекает мусор, записанный в uiPrefs мимо схемы.
|
|
6898
|
+
efforts: {
|
|
6899
|
+
drone: toEffortLevel(obj?.swarmSettings?.droneEffort),
|
|
6900
|
+
reviewer: toEffortLevel(obj?.swarmSettings?.reviewerEffort)
|
|
6901
|
+
},
|
|
6902
|
+
// TASWM-377: потолок трат — из того же ответа, тем же одним чтением. НОЛЬ здесь
|
|
6903
|
+
// значим («гарантия выключена осознанно»), поэтому пропускаем и его, а не только
|
|
6904
|
+
// положительные: null остаётся ровно за «ручка не задана / старый прод», и только
|
|
6905
|
+
// он уводит спавн на env-фолбэк и дефолт ядра.
|
|
6906
|
+
maxCostUsd: typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null
|
|
6729
6907
|
};
|
|
6730
6908
|
}
|
|
6731
6909
|
/**
|
|
@@ -6864,19 +7042,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
6864
7042
|
if (res?.isError) return [];
|
|
6865
7043
|
const raw = (res.structuredContent ?? {}).hosts;
|
|
6866
7044
|
if (!Array.isArray(raw)) return [];
|
|
6867
|
-
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) =>
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
7045
|
+
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) => {
|
|
7046
|
+
const resources = parseHostResources(h.resources);
|
|
7047
|
+
return {
|
|
7048
|
+
hostId: h.hostId,
|
|
7049
|
+
hostName: typeof h.hostName === "string" && h.hostName ? h.hostName : h.hostId,
|
|
7050
|
+
lastSeenAt: typeof h.lastSeenAt === "string" ? h.lastSeenAt : null,
|
|
7051
|
+
// TASWM-298: репозитории машины — «на связи» ещё не значит «возьмёт эту задачу».
|
|
7052
|
+
// Прод старой версии поля не пришлёт → пустой список, строка просто без хвоста.
|
|
7053
|
+
reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : [],
|
|
7054
|
+
// TASWM-326: машина на паузе — «на связи, но работу не берёт». Прод старой
|
|
7055
|
+
// версии поля не пришлёт → undefined, строка выглядит как раньше.
|
|
7056
|
+
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
7057
|
+
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
7058
|
+
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
7059
|
+
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
7060
|
+
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
7061
|
+
...resources ? { resources } : {}
|
|
7062
|
+
};
|
|
7063
|
+
});
|
|
6880
7064
|
}
|
|
6881
7065
|
/**
|
|
6882
7066
|
* TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
|
|
@@ -6942,13 +7126,22 @@ var SwarmClient = class _SwarmClient {
|
|
|
6942
7126
|
* разное. Не знаем своего потолка (ещё не читали / sqlite) → ключ не шлём, прод не
|
|
6943
7127
|
* трогает прошлое значение — та же логика, что у repoFullNames.
|
|
6944
7128
|
*
|
|
7129
|
+
* TASWM-394: тем же пульсом уезжают РЕСУРСЫ машины (`resources` — процессор, память,
|
|
7130
|
+
* диск и время замера). Это не новое измерение, а ровно то, что демон УЖЕ посчитал для
|
|
7131
|
+
* своего гейта спавна: числа в интерфейсе обязаны совпадать с теми, по которым он
|
|
7132
|
+
* отказывается поднимать дрона. Без них «машина на связи, свободна» и «машина физически
|
|
7133
|
+
* не может поднять дрона» выглядят в проде одинаково — 12.08 это стоило полудня
|
|
7134
|
+
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
7135
|
+
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
7136
|
+
*
|
|
6945
7137
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6946
7138
|
*/
|
|
6947
|
-
async heartbeat(daemonVersion, pause, capacity) {
|
|
7139
|
+
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
6948
7140
|
try {
|
|
6949
7141
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6950
7142
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
6951
7143
|
if (capacity != null && Number.isFinite(capacity) && capacity > 0) args.capacity = Math.trunc(capacity);
|
|
7144
|
+
if (resources) args.resources = resources;
|
|
6952
7145
|
if (pause !== void 0) {
|
|
6953
7146
|
args.paused = !!pause?.paused;
|
|
6954
7147
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
@@ -7155,6 +7348,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7155
7348
|
area: null,
|
|
7156
7349
|
depends_on: [],
|
|
7157
7350
|
model: null,
|
|
7351
|
+
effort: null,
|
|
7352
|
+
// усилие рассуждения (TASWM-395) — прод-поле, заполняется ниже из ответа
|
|
7158
7353
|
cost_usd: 0,
|
|
7159
7354
|
tokens_in: 0,
|
|
7160
7355
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
@@ -7223,6 +7418,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7223
7418
|
local.area = pr.area ?? null;
|
|
7224
7419
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
7225
7420
|
local.model = pr.model ?? null;
|
|
7421
|
+
local.effort = toEffortLevel(pr.effort);
|
|
7226
7422
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
7227
7423
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
7228
7424
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
@@ -8218,17 +8414,25 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8218
8414
|
}
|
|
8219
8415
|
|
|
8220
8416
|
// src/swarmModels.ts
|
|
8417
|
+
init_src();
|
|
8418
|
+
function pick(v) {
|
|
8419
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8420
|
+
return s.length > 0 ? s : null;
|
|
8421
|
+
}
|
|
8422
|
+
function forKind(kind, swarm, norm) {
|
|
8423
|
+
if (!swarm) return null;
|
|
8424
|
+
return kind === "reviewer" ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
8425
|
+
}
|
|
8221
8426
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
const fromSwarm = run.kind === "reviewer" ? pick2(swarm?.reviewer) ?? pick2(swarm?.drone) : pick2(swarm?.drone);
|
|
8227
|
-
return pick2(run.model) ?? fromSwarm ?? pick2(env.SWARM_DRONE_MODEL) ?? null;
|
|
8427
|
+
return pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8428
|
+
}
|
|
8429
|
+
function resolveRunEffort(run, swarm, env = process.env) {
|
|
8430
|
+
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
8228
8431
|
}
|
|
8229
8432
|
|
|
8230
8433
|
// src/launcher.ts
|
|
8231
8434
|
init_executors();
|
|
8435
|
+
init_src();
|
|
8232
8436
|
var requireFromHere = createRequire(import.meta.url);
|
|
8233
8437
|
function resolveTsxCli() {
|
|
8234
8438
|
try {
|
|
@@ -8240,6 +8444,15 @@ function resolveTsxCli() {
|
|
|
8240
8444
|
}
|
|
8241
8445
|
}
|
|
8242
8446
|
var MCP_SERVER_NAME = "overmind";
|
|
8447
|
+
function launchBudgetUsd(limit, spentUsd, env = process.env) {
|
|
8448
|
+
return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
|
|
8449
|
+
}
|
|
8450
|
+
function envCapUsd(env) {
|
|
8451
|
+
const raw = env.SWARM_MAX_COST_USD;
|
|
8452
|
+
if (raw === void 0 || raw === "") return DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8453
|
+
const n = Number(raw);
|
|
8454
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8455
|
+
}
|
|
8243
8456
|
function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
8244
8457
|
const env = { ...base3 };
|
|
8245
8458
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
@@ -8365,6 +8578,12 @@ function makeLauncher(cfg) {
|
|
|
8365
8578
|
}
|
|
8366
8579
|
};
|
|
8367
8580
|
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
8581
|
+
const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
|
|
8582
|
+
try {
|
|
8583
|
+
cfg.recordEffort?.(run.id, effort);
|
|
8584
|
+
} catch (e) {
|
|
8585
|
+
console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
|
|
8586
|
+
}
|
|
8368
8587
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8369
8588
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8370
8589
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8383,7 +8602,14 @@ function makeLauncher(cfg) {
|
|
|
8383
8602
|
// Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
|
|
8384
8603
|
// сидит в HOME демона, и запирать половину дверей — самообман.
|
|
8385
8604
|
denyPaths: cfg.droneIsolation ? dronePrivatePaths(realHome, cfg.droneIsolation.swarmHome) : [],
|
|
8386
|
-
realHome
|
|
8605
|
+
realHome,
|
|
8606
|
+
// TASWM-377: остаток бюджета ЭТОГО прогона — харнессу, чтобы он резал трату у себя, в
|
|
8607
|
+
// момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
|
|
8608
|
+
// стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
|
|
8609
|
+
// ни жёг, — отсюда и $74 при лимите $20.
|
|
8610
|
+
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
|
|
8611
|
+
// TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
|
|
8612
|
+
effort
|
|
8387
8613
|
});
|
|
8388
8614
|
let stdio = "ignore";
|
|
8389
8615
|
let outFd;
|
|
@@ -8482,6 +8708,9 @@ function makeCostAccountant(deps) {
|
|
|
8482
8708
|
init_executors();
|
|
8483
8709
|
import { readdirSync as readdirSync9, readFileSync as readFileSync11 } from "fs";
|
|
8484
8710
|
import { join as join14 } from "path";
|
|
8711
|
+
function endedItself(outcome) {
|
|
8712
|
+
return outcome === "graceful" || outcome === "budget";
|
|
8713
|
+
}
|
|
8485
8714
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
8486
8715
|
function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
8487
8716
|
const dir = join14(runLogsDir, String(runId));
|
|
@@ -8505,7 +8734,9 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
8505
8734
|
} catch {
|
|
8506
8735
|
return "unknown";
|
|
8507
8736
|
}
|
|
8508
|
-
|
|
8737
|
+
const result = executor.parseResult(raw, null);
|
|
8738
|
+
if (!result) return "crash";
|
|
8739
|
+
return result.stoppedByBudget ? "budget" : "graceful";
|
|
8509
8740
|
}
|
|
8510
8741
|
function makeExitClassifier(runLogsDir, executor) {
|
|
8511
8742
|
const exec = executor ?? resolveExecutor();
|
|
@@ -9207,6 +9438,9 @@ async function mergedPrForBranch(branch, repoDir, run = realRepoRunner) {
|
|
|
9207
9438
|
async function closePR(prUrl, run = realRunner) {
|
|
9208
9439
|
await run(["pr", "close", prUrl, "--comment", "\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D \u043D\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 (swarm)"]);
|
|
9209
9440
|
}
|
|
9441
|
+
async function postPrComment(prUrl, body, run = realRunner) {
|
|
9442
|
+
await run(["pr", "comment", prUrl, "--body", body]);
|
|
9443
|
+
}
|
|
9210
9444
|
var realCommentsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json comments`).stdout;
|
|
9211
9445
|
var VERDICT_RE = /^[^A-Za-z]*(APPROVED?|LGTM|(?:REQUEST(?:ED)?\s+)?CHANGES(?:\s+REQUESTED)?)\b/i;
|
|
9212
9446
|
var APPROVE_RE = /^(APPROVE|LGTM)/i;
|
|
@@ -11113,7 +11347,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
11113
11347
|
// package.json
|
|
11114
11348
|
var package_default = {
|
|
11115
11349
|
name: "@taskless-app/swarm",
|
|
11116
|
-
version: "0.6.
|
|
11350
|
+
version: "0.6.6",
|
|
11117
11351
|
type: "module",
|
|
11118
11352
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11119
11353
|
keywords: [
|
|
@@ -11163,7 +11397,7 @@ var package_default = {
|
|
|
11163
11397
|
execa: "^10.0.0",
|
|
11164
11398
|
zod: "3.25.76"
|
|
11165
11399
|
},
|
|
11166
|
-
gitHead: "
|
|
11400
|
+
gitHead: "89e3fe0078755d130512c75ac85f195e6b950392"
|
|
11167
11401
|
};
|
|
11168
11402
|
|
|
11169
11403
|
// src/version.ts
|
|
@@ -11386,7 +11620,8 @@ function timelineState(db, now = Date.now(), overmindLog = []) {
|
|
|
11386
11620
|
heroRunId: r.hero_run_id,
|
|
11387
11621
|
reviewVerdict: r.review_verdict,
|
|
11388
11622
|
reviewPhase: r.review_phase,
|
|
11389
|
-
model: r.model
|
|
11623
|
+
model: r.model,
|
|
11624
|
+
effort: r.effort
|
|
11390
11625
|
});
|
|
11391
11626
|
}
|
|
11392
11627
|
const liveLanes = lanes.filter((l) => l.live);
|
|
@@ -13160,9 +13395,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
13160
13395
|
)
|
|
13161
13396
|
);
|
|
13162
13397
|
return {
|
|
13163
|
-
since:
|
|
13164
|
-
lastFiredAt:
|
|
13165
|
-
fires:
|
|
13398
|
+
since: pick2(since, fresh),
|
|
13399
|
+
lastFiredAt: pick2(lastFiredAt, fresh),
|
|
13400
|
+
fires: pick2(fires, fresh),
|
|
13166
13401
|
// TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
|
|
13167
13402
|
// уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
|
|
13168
13403
|
// недоставленным, гашение не становится лишним — оно становится нужнее.
|
|
@@ -13246,7 +13481,7 @@ function strings(raw) {
|
|
|
13246
13481
|
if (!Array.isArray(raw)) return [];
|
|
13247
13482
|
return raw.filter((v) => typeof v === "string" && v.length > 0);
|
|
13248
13483
|
}
|
|
13249
|
-
function
|
|
13484
|
+
function pick2(src, keys) {
|
|
13250
13485
|
const out = {};
|
|
13251
13486
|
for (const k of keys) if (src[k] != null) out[k] = src[k];
|
|
13252
13487
|
return out;
|
|
@@ -13644,13 +13879,20 @@ var CHANGES_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CHANGES_WAKE_COOLDOWN_MS
|
|
|
13644
13879
|
var CHANGES_WAKE_LIMIT = Number(process.env.SWARM_CHANGES_WAKE_LIMIT) || 3;
|
|
13645
13880
|
var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB";
|
|
13646
13881
|
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";
|
|
13882
|
+
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";
|
|
13883
|
+
var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON];
|
|
13884
|
+
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";
|
|
13885
|
+
function reviewBudgetMark(cycle) {
|
|
13886
|
+
return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
|
|
13887
|
+
}
|
|
13647
13888
|
var NEEDS_HUMAN_MARKS = [
|
|
13648
13889
|
RESOLVE_ESCALATION_MARK,
|
|
13649
13890
|
REVIEW_ESCALATION_MARK,
|
|
13650
13891
|
REVIEW_UNRECOVERABLE_MARK,
|
|
13651
13892
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
13652
13893
|
CI_WAKE_ESCALATION_MARK,
|
|
13653
|
-
CHANGES_WAKE_ESCALATION_MARK
|
|
13894
|
+
CHANGES_WAKE_ESCALATION_MARK,
|
|
13895
|
+
REVIEW_BUDGET_MARK
|
|
13654
13896
|
];
|
|
13655
13897
|
var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
|
|
13656
13898
|
var REVIEW_NO_VERDICT_REASON = "\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430: \u043D\u0438 approve, \u043D\u0438 changes \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434 (TASWM-375)";
|
|
@@ -14032,6 +14274,12 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
14032
14274
|
`\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
|
|
14033
14275
|
"\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
|
|
14034
14276
|
"\u041F\u0440\u0430\u0432\u0438\u043B\u0430: \u0442\u0440\u043E\u0433\u0430\u0435\u0448\u044C UI \u2014 \u0421\u041D\u0410\u0427\u0410\u041B\u0410 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0441\u043A\u0438\u043B\u043B \u0441 UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u0433\u043E \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (\u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u0435 \u0445\u0430\u0440\u0434\u043A\u043E\u0434\u044C \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u043E\u0435); \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u0441\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438; \u043F\u043E\u0441\u043B\u0435 UI-\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 screenshots \u0443 done); \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0447\u0435\u0440\u0435\u0437 gh \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).",
|
|
14277
|
+
// TASWM-377: у сессии есть ПОТОЛОК ТРАТ, и он режет её без предупреждения, прямо посреди
|
|
14278
|
+
// работы. Дрон об этом знать обязан: цена сессии растёт от ЧИСЛА ХОДОВ (каждый ход заново
|
|
14279
|
+
// оплачивает весь накопленный контекст), поэтому «просканирую репозиторий, чтобы вникнуть»
|
|
14280
|
+
// — это не тщательность, а способ не доехать до PR. Замер по прогонам роя: дорогие сессии
|
|
14281
|
+
// отличаются от дешёвых числом заходов, а не размером задачи.
|
|
14282
|
+
"\u{1F4B8} \u0421\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u041F\u041E\u0422\u041E\u041B\u041A\u041E\u041C \u0422\u0420\u0410\u0422: \u0443\u043F\u0440\u0451\u0448\u044C\u0441\u044F \u2014 \u0442\u0435\u0431\u044F \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u044F\u0442 \u043F\u043E\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043E\u0442\u044B, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u043F\u0440\u043E\u043F\u0430\u0434\u0451\u0442, \u0430 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0443\u0434\u0435\u0442 \u0434\u043E\u0434\u0435\u043B\u044B\u0432\u0430\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A. \u0427\u0438\u0442\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043D\u0443\u0436\u043D\u043E \u043F\u0440\u0430\u0432\u043A\u0435 (\u043A\u0430\u0440\u0442\u0430 \u043A\u043E\u0434\u0430\u0431\u0430\u0437\u044B + \u0437\u0430\u0442\u0440\u043E\u043D\u0443\u0442\u044B\u0435 \u0444\u0430\u0439\u043B\u044B), \u0430 \u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0446\u0435\u043B\u0438\u043A\u043E\u043C; \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u044B\u0432\u0430\u0439; \u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043F\u0443\u0448\u044C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0438\u043C\u043E\u0433\u043E \u0448\u0430\u0433\u0430 \u2014 \u0442\u043E\u0433\u0434\u0430 \u0434\u0430\u0436\u0435 \u043E\u0431\u0440\u044B\u0432 \u043F\u043E \u0431\u044E\u0434\u0436\u0435\u0442\u0443 \u043E\u0441\u0442\u0430\u0432\u0438\u0442 \u0440\u0430\u0431\u043E\u0442\u0443 \u0432 origin.",
|
|
14035
14283
|
// TASWM-293: живой случай SCE-5 — ревьюер заблокировал PR за отсутствие CI (у проекта
|
|
14036
14284
|
// его нет и не предполагается), автора разбудили «закрывать замечания», замечаний по коду
|
|
14037
14285
|
// не было, и дрон завёл в ЧУЖОМ репозитории .github/workflows/check.yml, чтобы пройти
|
|
@@ -14175,6 +14423,13 @@ function lastChangesSummary(db, reviewRunId) {
|
|
|
14175
14423
|
}
|
|
14176
14424
|
return null;
|
|
14177
14425
|
}
|
|
14426
|
+
var REVIEW_SCOPE_BLOCK = [
|
|
14427
|
+
"\u{1F4B8} \u041A\u041E\u041D\u0422\u0423\u0420 \u0420\u0415\u0412\u042C\u042E (\u0436\u0451\u0441\u0442\u043A\u043E). \u0422\u0432\u043E\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u0431\u044E\u0434\u0436\u0435\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0438 \u043E\u043D \u0441\u0440\u0435\u0436\u0435\u0442 \u0435\u0451 \u0411\u0415\u0417 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F: \u0443\u043F\u0440\u0451\u0448\u044C\u0441\u044F \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043E\u0432\u0441\u0435, \u0430 PR \u0443\u0439\u0434\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443. \u041F\u043E\u044D\u0442\u043E\u043C\u0443:",
|
|
14428
|
+
"\u2022 \u0427\u0438\u0442\u0430\u0439 \u0414\u0418\u0424\u0424 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0435\u0433\u043E \u043E\u043D \u043A\u0430\u0441\u0430\u0435\u0442\u0441\u044F: \u0441\u0430\u043C\u0438 \u0438\u0437\u043C\u0435\u043D\u0451\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B + \u043C\u0435\u0441\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0438\u043C\u0438 \u043B\u043E\u043C\u0430\u044E\u0442\u0441\u044F. \u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \xAB\u0434\u043B\u044F \u043E\u0431\u0449\u0435\u0433\u043E \u043F\u043E\u043D\u0438\u043C\u0430\u043D\u0438\u044F\xBB \u043D\u0435 \u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0439 \u2014 \u043D\u0430 \u044D\u0442\u043E \u0443\u0445\u043E\u0434\u0438\u0442 \u0432\u0435\u0441\u044C \u0431\u044E\u0434\u0436\u0435\u0442 \u0438 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0438\u0447\u0435\u0433\u043E.",
|
|
14429
|
+
"\u2022 \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u044B\u0432\u0430\u0439. \u041F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u043E\u0441\u044C \u0432\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u043A \u0444\u0430\u0439\u043B\u0443 \u2014 \u0432\u0435\u0440\u043D\u0438\u0441\u044C \u043A \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432\u0438\u0434\u0435\u043B \u0432 \u044D\u0442\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438, \u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E \u0437\u0430\u043D\u043E\u0432\u043E; `git diff`/`gh pr diff` \u0442\u043E\u0436\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043E\u0434\u043D\u043E\u0433\u043E \u0440\u0430\u0437\u0430.",
|
|
14430
|
+
"\u2022 \u0417\u0430\u0445\u043E\u0434\u043E\u0432 \u043D\u0430 \u043E\u0434\u0438\u043D \u0432\u043E\u043F\u0440\u043E\u0441 \u2014 \u043E\u0434\u0438\u043D. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u043E\u0442\u0432\u0435\u0442\u0430 \u0441 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0440\u0430\u0437\u0430 (\u0438\u043B\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u043C\u043E\u043B\u0447\u0438\u0442) \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0430: \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435, \u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 \u043A\u0440\u0443\u0433 \u043F\u043E\u0438\u0441\u043A\u0430.",
|
|
14431
|
+
"\u2022 \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0441\u043F\u0435\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0412\u0421\u0415\u0413\u0414\u0410 \u043B\u0443\u0447\u0448\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442: \u043E\u0434\u043D\u043E\u0433\u043E \u043E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0447\u0442\u043E\u0431\u044B PR \u043F\u043E\u0435\u0445\u0430\u043B \u043D\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0443. \u041A\u043E\u043F\u0430\u0435\u0448\u044C \u0434\u043E\u043B\u0433\u043E \u2014 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0439\u0441\u044F \u0438 \u0432\u044B\u043D\u043E\u0441\u0438 \u0442\u043E, \u0447\u0442\u043E \u0435\u0441\u0442\u044C, \u0447\u0435\u0441\u0442\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u0432, \u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0435\u043B."
|
|
14432
|
+
].join("\n");
|
|
14178
14433
|
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
14179
14434
|
const name = reviewer.name || `#${reviewer.id}`;
|
|
14180
14435
|
const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
|
|
@@ -14192,6 +14447,8 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
14192
14447
|
] : [],
|
|
14193
14448
|
"",
|
|
14194
14449
|
"\u26A0\uFE0F \u0422\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C \u0424\u0410\u0419\u041B\u042B \u0418 \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C \u041A\u041E\u041C\u041C\u0418\u0422\u042B. \u0422\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0438 \u0432\u044B\u043D\u0435\u0441\u0442\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442.",
|
|
14450
|
+
"",
|
|
14451
|
+
REVIEW_SCOPE_BLOCK,
|
|
14195
14452
|
// TASWM-283: ревьюер читает ТУ ЖЕ задачу и так же слеп к её картинкам — без макета
|
|
14196
14453
|
// «сделано ли как просили» он проверить не может, а UI-пункт чек-листа держится
|
|
14197
14454
|
// ровно на этом. Кладём ему тот же каталог вложений, что и исполнителю.
|
|
@@ -15185,6 +15442,67 @@ async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
|
15185
15442
|
}
|
|
15186
15443
|
return true;
|
|
15187
15444
|
}
|
|
15445
|
+
async function failReviewerRun(deps, reviewer, reviewsRunId, opts) {
|
|
15446
|
+
if (!opts.keepWorktree && reviewer.worktree_path) {
|
|
15447
|
+
await deps.removeWorktree(reviewer.repo, reviewer.worktree_path).catch(() => {
|
|
15448
|
+
});
|
|
15449
|
+
}
|
|
15450
|
+
deps.db.updateRun(reviewer.id, { status: "failed", stop_reason: opts.reason });
|
|
15451
|
+
deps.db.addEvent(reviewer.id, "failed", opts.reason);
|
|
15452
|
+
deps.db.addEvent(reviewsRunId, "step", opts.noteForReviewed);
|
|
15453
|
+
narrate(deps, opts.narration);
|
|
15454
|
+
}
|
|
15455
|
+
async function handleBudgetExhausted(deps, run, ctx) {
|
|
15456
|
+
const spentUsd = deps.db.getRun(run.id)?.cost_usd ?? run.cost_usd;
|
|
15457
|
+
const spent = `$${spentUsd.toFixed(2)}`;
|
|
15458
|
+
const reason = `${BUDGET_STOP_REASON} (\u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E ${spent})`;
|
|
15459
|
+
if (run.reviews_run_id != null) {
|
|
15460
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15461
|
+
how: `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 (${spent})`,
|
|
15462
|
+
now: ctx.now,
|
|
15463
|
+
droneCap: ctx.droneCap,
|
|
15464
|
+
stormActive: ctx.stormActive
|
|
15465
|
+
});
|
|
15466
|
+
if (salvaged) return;
|
|
15467
|
+
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15468
|
+
const cycle = reviewed?.review_cycle ?? 0;
|
|
15469
|
+
const refusal = [
|
|
15470
|
+
`\u26D4 \u0410\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u043E\u0441\u044C: \u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${run.name} \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 (\u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E ${spent}).`,
|
|
15471
|
+
"",
|
|
15472
|
+
"\u0412\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043F\u043E \u043A\u043E\u0434\u0443 \u043D\u0435\u0442 \u2014 \u043D\u0438 \u043E\u0434\u043E\u0431\u0440\u0435\u043D\u0438\u044F, \u043D\u0438 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439: \u0441\u0435\u0441\u0441\u0438\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u0431\u044E\u0434\u0436\u0435\u0442 \u0440\u0430\u043D\u044C\u0448\u0435, \u0447\u0435\u043C \u0440\u0435\u0432\u044C\u044E \u0434\u043E\u0448\u043B\u043E \u0434\u043E \u043A\u043E\u043D\u0446\u0430.",
|
|
15473
|
+
"\u042D\u0442\u043E\u0442 PR \u043D\u0443\u0436\u043D\u043E \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0440\u0443\u043A\u0430\u043C\u0438. \u041D\u043E\u0432\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0442 \u043A\u0440\u0443\u0433 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u2014 \u043E\u043D \u0443\u043F\u0451\u0440\u0441\u044F \u0431\u044B \u0432 \u0442\u043E\u0442 \u0436\u0435 \u043F\u043E\u0442\u043E\u043B\u043E\u043A.",
|
|
15474
|
+
"",
|
|
15475
|
+
"\u041F\u043E\u0442\u043E\u043B\u043E\u043A \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044F \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u0440\u043E\u044F (\xAB\u0420\u043E\u0439\xBB \u2192 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D) \u2014 \u0435\u0441\u043B\u0438 \u0440\u0435\u0432\u044C\u044E \u0442\u0430\u043A\u043E\u0433\u043E PR \u0447\u0435\u0441\u0442\u043D\u043E \u0441\u0442\u043E\u0438\u0442 \u0434\u043E\u0440\u043E\u0436\u0435, \u043F\u043E\u0434\u043D\u0438\u043C\u0438\u0442\u0435 \u0447\u0438\u0441\u043B\u043E \u0438 \u043F\u043E\u0437\u043E\u0432\u0438\u0442\u0435 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E."
|
|
15476
|
+
].join("\n");
|
|
15477
|
+
await Promise.all([
|
|
15478
|
+
run.pr_url && deps.prComment ? deps.prComment(run.pr_url, refusal).catch((e) => {
|
|
15479
|
+
deps.db.addEvent(run.id, "step", `\u043E\u0442\u043A\u0430\u0437 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0437\u0430\u043F\u043E\u0441\u0442\u0438\u043B\u0441\u044F \u0432 PR: ${e instanceof Error ? e.message : String(e)}`);
|
|
15480
|
+
}) : Promise.resolve(),
|
|
15481
|
+
run.worktree_path ? deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15482
|
+
}) : Promise.resolve()
|
|
15483
|
+
]);
|
|
15484
|
+
await failReviewerRun(deps, run, run.reviews_run_id, {
|
|
15485
|
+
reason,
|
|
15486
|
+
noteForReviewed: `${reviewBudgetMark(cycle)}: \u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${run.name} (#${run.id}) \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0431\u044E\u0434\u0436\u0435\u0442\u043E\u043C, \u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E ${spent} \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`,
|
|
15487
|
+
narration: `${droneLabel(run)} \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E\u0442\u043E\u043B\u043A\u043E\u043C \u0442\u0440\u0430\u0442 (${spent}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0432 PR \u043E\u0442\u043A\u0430\u0437 \u0438 \u0437\u043E\u0432\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430.`,
|
|
15488
|
+
// worktree уже снесён выше — параллельно с отказом в PR.
|
|
15489
|
+
keepWorktree: true
|
|
15490
|
+
});
|
|
15491
|
+
return;
|
|
15492
|
+
}
|
|
15493
|
+
if (run.resolves_run_id != null) {
|
|
15494
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15495
|
+
});
|
|
15496
|
+
deps.db.updateRun(run.id, { status: "failed", stop_reason: reason });
|
|
15497
|
+
deps.db.addEvent(run.id, "failed", reason);
|
|
15498
|
+
deps.db.addEvent(run.resolves_run_id, "conflict", `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 ${run.name} (#${run.id}) \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E\u0442\u043E\u043B\u043A\u043E\u043C \u0442\u0440\u0430\u0442 (${spent}) \u2014 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D`);
|
|
15499
|
+
narrate(deps, `${droneLabel(run)} (\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440) \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 (${spent}) \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u043E\u043F\u044B\u0442\u043A\u0443, \u0434\u0430\u043B\u044C\u0448\u0435 \u0440\u0435\u0448\u0430\u0435\u0442 \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 PR.`);
|
|
15500
|
+
return;
|
|
15501
|
+
}
|
|
15502
|
+
deps.db.updateRun(run.id, { status: "paused", stop_reason: reason });
|
|
15503
|
+
deps.db.addEvent(run.id, "paused", `${reason} \u2014 worktree \u0446\u0435\u043B, \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442: \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0431\u044E\u0434\u0436\u0435\u0442\u0430 \u0443\u0436\u0435 \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u0442`);
|
|
15504
|
+
narrate(deps, `${droneLabel(run)} \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 (${spent}) \u2014 \u0437\u0430\u043C\u0435\u0440, \u0440\u0430\u0431\u043E\u0442\u0430 \u0432 worktree \u0446\u0435\u043B\u0430, \u0434\u0430\u043B\u044C\u0448\u0435 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0437\u0430 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C.`);
|
|
15505
|
+
}
|
|
15188
15506
|
function buildEscalationCandidates(db) {
|
|
15189
15507
|
const candidates = [];
|
|
15190
15508
|
for (const run of db.listRuns("review")) {
|
|
@@ -15208,6 +15526,7 @@ async function tickOnce(deps) {
|
|
|
15208
15526
|
const now = deps.now ? deps.now() : Date.now();
|
|
15209
15527
|
const worktreeExists = deps.worktreeExists ?? existsSync16;
|
|
15210
15528
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
15529
|
+
deps.sampleHost?.();
|
|
15211
15530
|
if (deps.rehydrate) {
|
|
15212
15531
|
const rh = deps.rehydrate;
|
|
15213
15532
|
const sinceLast = rh.lastAt == null ? Infinity : now - rh.lastAt;
|
|
@@ -15319,7 +15638,7 @@ async function tickOnce(deps) {
|
|
|
15319
15638
|
continue;
|
|
15320
15639
|
}
|
|
15321
15640
|
}
|
|
15322
|
-
if (deps.exitOutcome && deps.exitOutcome(run)
|
|
15641
|
+
if (deps.exitOutcome && endedItself(deps.exitOutcome(run))) continue;
|
|
15323
15642
|
deps.db.recordDeath(run.id, `\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440 (pid ${run.pid} \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442)`);
|
|
15324
15643
|
}
|
|
15325
15644
|
stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
|
|
@@ -15354,12 +15673,11 @@ async function tickOnce(deps) {
|
|
|
15354
15673
|
stormActive
|
|
15355
15674
|
});
|
|
15356
15675
|
if (salvaged) continue;
|
|
15357
|
-
|
|
15676
|
+
await failReviewerRun(deps, run, run.reviews_run_id, {
|
|
15677
|
+
reason: REVIEW_NO_VERDICT_REASON,
|
|
15678
|
+
noteForReviewed: `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${run.name} (#${run.id}) \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u043E\u0441\u044C, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`,
|
|
15679
|
+
narration: `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0439: \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`
|
|
15358
15680
|
});
|
|
15359
|
-
deps.db.updateRun(run.id, { status: "failed", stop_reason: REVIEW_NO_VERDICT_REASON });
|
|
15360
|
-
deps.db.addEvent(run.id, "failed", REVIEW_NO_VERDICT_REASON);
|
|
15361
|
-
deps.db.addEvent(run.reviews_run_id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${run.name} (#${run.id}) \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u043E\u0441\u044C, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`);
|
|
15362
|
-
narrate(deps, `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0439: \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
|
|
15363
15681
|
continue;
|
|
15364
15682
|
}
|
|
15365
15683
|
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
@@ -15448,6 +15766,8 @@ async function tickOnce(deps) {
|
|
|
15448
15766
|
const attempts = deps.db.reviewerAttemptCountFor(run.id);
|
|
15449
15767
|
const runEvents = deps.db.events(run.id);
|
|
15450
15768
|
const unrecoverableMark = reviewUnrecoverableMark(run.branch || run.task_slug);
|
|
15769
|
+
const budgetMark = reviewBudgetMark(run.review_cycle ?? 0);
|
|
15770
|
+
const LATCHES = [unrecoverableMark, budgetMark];
|
|
15451
15771
|
let verdictFromComment = null;
|
|
15452
15772
|
let lastAttempt = null;
|
|
15453
15773
|
if (attempts > 0 && deps.verdictComment) {
|
|
@@ -15467,7 +15787,7 @@ async function tickOnce(deps) {
|
|
|
15467
15787
|
await wakeAuthorOnChanges(deps, run, now, droneCap);
|
|
15468
15788
|
}
|
|
15469
15789
|
}
|
|
15470
|
-
} else if (runEvents.some((e) => (e.text ?? "").includes(
|
|
15790
|
+
} else if (runEvents.some((e) => LATCHES.some((mark) => (e.text ?? "").includes(mark)))) {
|
|
15471
15791
|
} else if (attempts >= REVIEW_SPAWN_LIMIT) {
|
|
15472
15792
|
if (!runEvents.some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_ESCALATION_MARK))) {
|
|
15473
15793
|
deps.db.addEvent(run.id, "step", `${REVIEW_ESCALATION_MARK}: ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u0440\u0435\u0432\u044C\u044E \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
|
|
@@ -15550,6 +15870,10 @@ async function tickOnce(deps) {
|
|
|
15550
15870
|
}
|
|
15551
15871
|
continue;
|
|
15552
15872
|
}
|
|
15873
|
+
if (!alive && outcome === "budget") {
|
|
15874
|
+
await handleBudgetExhausted(deps, run, { now, droneCap, stormActive });
|
|
15875
|
+
continue;
|
|
15876
|
+
}
|
|
15553
15877
|
if (!alive && run.kind === "hero") {
|
|
15554
15878
|
const young = now - lastActivityMs(deps.db, run) < RESPAWN_YOUNG_MS;
|
|
15555
15879
|
if (young && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -15707,7 +16031,7 @@ async function tickOnce(deps) {
|
|
|
15707
16031
|
if (run.kind === "hero") continue;
|
|
15708
16032
|
if (run.reviews_run_id != null) continue;
|
|
15709
16033
|
if (run.stop_reason === RELIEF_PAUSE_REASON) continue;
|
|
15710
|
-
if (
|
|
16034
|
+
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
15711
16035
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
15712
16036
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
15713
16037
|
const wt = run.worktree_path;
|
|
@@ -16194,6 +16518,8 @@ async function runDaemon(opts) {
|
|
|
16194
16518
|
} : void 0;
|
|
16195
16519
|
let lastDroneCap = null;
|
|
16196
16520
|
let lastSwarmModels = null;
|
|
16521
|
+
let lastSwarmEfforts = null;
|
|
16522
|
+
let lastMaxCostUsd = null;
|
|
16197
16523
|
let lastOwnCapacity = null;
|
|
16198
16524
|
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
16199
16525
|
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
@@ -16207,7 +16533,15 @@ async function runDaemon(opts) {
|
|
|
16207
16533
|
droneIsolation: DRONE_ISOLATION,
|
|
16208
16534
|
channel: droneChannelLaunch,
|
|
16209
16535
|
droneCap: () => lastDroneCap,
|
|
16210
|
-
swarmModels: () => lastSwarmModels
|
|
16536
|
+
swarmModels: () => lastSwarmModels,
|
|
16537
|
+
swarmEfforts: () => lastSwarmEfforts,
|
|
16538
|
+
// TASWM-395: разрешённый уровень усилия впечатывается в прогон на спавне — иначе в учёте
|
|
16539
|
+
// виден только пин (то есть null у всех, кто шёл на настройке роя). Оттуда же его берут
|
|
16540
|
+
// респавн и `--continue`: прогон, начатый трудным, не должен подешеветь на середине.
|
|
16541
|
+
recordEffort: (runId, effort) => {
|
|
16542
|
+
db.updateRun(runId, { effort });
|
|
16543
|
+
},
|
|
16544
|
+
maxCostUsd: () => lastMaxCostUsd
|
|
16211
16545
|
});
|
|
16212
16546
|
let prepareReader;
|
|
16213
16547
|
const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
|
|
@@ -16253,7 +16587,7 @@ async function runDaemon(opts) {
|
|
|
16253
16587
|
const client = db;
|
|
16254
16588
|
heartbeat = {
|
|
16255
16589
|
push: async () => {
|
|
16256
|
-
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity);
|
|
16590
|
+
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity, hostResourcesForProd());
|
|
16257
16591
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
16258
16592
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
16259
16593
|
if (reply.logRequests.length) {
|
|
@@ -16348,7 +16682,11 @@ async function runDaemon(opts) {
|
|
|
16348
16682
|
// что и кап ниже: второго сетевого канала за ними заводить незачем.
|
|
16349
16683
|
fetch: async () => {
|
|
16350
16684
|
const settings = await client.ownerSwarmSettings();
|
|
16351
|
-
if (settings)
|
|
16685
|
+
if (settings) {
|
|
16686
|
+
lastSwarmModels = settings.models;
|
|
16687
|
+
lastSwarmEfforts = settings.efforts;
|
|
16688
|
+
lastMaxCostUsd = settings.maxCostUsd;
|
|
16689
|
+
}
|
|
16352
16690
|
return settings?.maxDrones ?? null;
|
|
16353
16691
|
},
|
|
16354
16692
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
@@ -16607,6 +16945,9 @@ async function runDaemon(opts) {
|
|
|
16607
16945
|
transcriptMtimeMs: (p) => EXECUTOR.session.mtimeMs(p),
|
|
16608
16946
|
// Ресурс-гейт спавна (TASWM-130): реальные показания хоста (load1 / free RAM).
|
|
16609
16947
|
resourcesOk: hostResourcesOk,
|
|
16948
|
+
// Замер занятости CPU раз в тик (TASWM-397): окно фиксированной длины набирает пульс
|
|
16949
|
+
// тика, гейт ниже только читает готовое показание.
|
|
16950
|
+
sampleHost: () => void sampleCpuBusy(),
|
|
16610
16951
|
// Ресурс-гейт v2 (TASWM-151): жёсткий порог активной разгрузки — те же сырые показания
|
|
16611
16952
|
// хоста (общий кэш в resources.ts), но заведомо строже настроенные пороги.
|
|
16612
16953
|
reliefNeeded: hostReliefNeeded,
|
|
@@ -16737,6 +17078,11 @@ async function runDaemon(opts) {
|
|
|
16737
17078
|
// Сторож прогресса роя (TASWM-270): в отличие от anomalies выше — заведён БЕЗУСЛОВНО
|
|
16738
17079
|
// (см. докблок конструирования watchdog выше — рунги а/б/в работают и без client-режима).
|
|
16739
17080
|
watchdog,
|
|
17081
|
+
// TASWM-377: отказ в PR за мёртвого (ревьюер, срезанный потолком трат). Заведён
|
|
17082
|
+
// БЕЗУСЛОВНО, в отличие от ключей авто-ревью ниже: разбирать уже поднятого ревьюера
|
|
17083
|
+
// приходится и после того, как авто-ревью выключили, — а молчать за него тем более
|
|
17084
|
+
// некому.
|
|
17085
|
+
prComment: postPrComment,
|
|
16740
17086
|
// Авто-ревью (TASWM-148): ключи есть только когда SWARM_AUTO_REVIEW включён (дефолт да) —
|
|
16741
17087
|
// spawnReviewerFn/verdictCommentFn/reviewCutoffAtValue выставляются В ОДНОМ if(SWARM_AUTO_REVIEW)
|
|
16742
17088
|
// блоке выше, поэтому один и тот же гейт на все три (без отдельных тернарных условий).
|
|
@@ -17062,6 +17408,7 @@ if (isEntrypoint) {
|
|
|
17062
17408
|
}
|
|
17063
17409
|
export {
|
|
17064
17410
|
AWAITING_CI_REASON,
|
|
17411
|
+
BUDGET_STOP_REASON,
|
|
17065
17412
|
CHANGES_WAKE_COOLDOWN_MS,
|
|
17066
17413
|
CHANGES_WAKE_ESCALATION_MARK,
|
|
17067
17414
|
CHANGES_WAKE_LIMIT,
|
|
@@ -17089,6 +17436,7 @@ export {
|
|
|
17089
17436
|
RESOLVE_LIMIT,
|
|
17090
17437
|
RESPAWN_LIMIT,
|
|
17091
17438
|
RESPAWN_YOUNG_MS,
|
|
17439
|
+
REVIEW_BUDGET_MARK,
|
|
17092
17440
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
17093
17441
|
REVIEW_CYCLE_LIMIT,
|
|
17094
17442
|
REVIEW_ESCALATION_MARK,
|
|
@@ -17118,6 +17466,7 @@ export {
|
|
|
17118
17466
|
repoHasCiWorkflows,
|
|
17119
17467
|
resolveConflict,
|
|
17120
17468
|
respawnRun,
|
|
17469
|
+
reviewBudgetMark,
|
|
17121
17470
|
reviewUnrecoverableMark,
|
|
17122
17471
|
reviveStuck,
|
|
17123
17472
|
runDaemon,
|