@taskless-app/swarm 0.6.4 → 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 +302 -42
- package/dist/overmind.js +501 -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
|
}
|
|
@@ -4105,6 +4250,16 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4105
4250
|
const text = res?.content?.find((c) => c.type === "text")?.text;
|
|
4106
4251
|
return typeof text === "string" && text.trim() ? text : null;
|
|
4107
4252
|
}
|
|
4253
|
+
async function reportAcceptanceCheck(input) {
|
|
4254
|
+
const res = await safeCall("report_acceptance_check", {
|
|
4255
|
+
id: input.id,
|
|
4256
|
+
state: input.state,
|
|
4257
|
+
...input.evidence ? { evidence: input.evidence } : {}
|
|
4258
|
+
});
|
|
4259
|
+
if (!res) return null;
|
|
4260
|
+
const text = res.content?.find((c) => c.type === "text")?.text;
|
|
4261
|
+
return typeof text === "string" && text.trim() ? text : null;
|
|
4262
|
+
}
|
|
4108
4263
|
async function resolveId(idOrSlug) {
|
|
4109
4264
|
const cached = idCache.get(idOrSlug);
|
|
4110
4265
|
if (cached) return cached;
|
|
@@ -4173,6 +4328,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4173
4328
|
backlogStatusId,
|
|
4174
4329
|
getDocument,
|
|
4175
4330
|
getTaskContext,
|
|
4331
|
+
reportAcceptanceCheck,
|
|
4176
4332
|
createTask: async (input) => {
|
|
4177
4333
|
if (!input.projectId) {
|
|
4178
4334
|
throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
|
|
@@ -4421,7 +4577,7 @@ var init_taskless = __esm({
|
|
|
4421
4577
|
init_agentToken();
|
|
4422
4578
|
init_workspace();
|
|
4423
4579
|
SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
|
|
4424
|
-
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload"]);
|
|
4580
|
+
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
|
|
4425
4581
|
TaskBodyUnavailableError = class extends Error {
|
|
4426
4582
|
constructor(slug, workspaceId) {
|
|
4427
4583
|
super(
|
|
@@ -4492,6 +4648,10 @@ function rowToRun(row) {
|
|
|
4492
4648
|
area: row.area ?? null,
|
|
4493
4649
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
4494
4650
|
model: row.model ?? null,
|
|
4651
|
+
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
4652
|
+
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
4653
|
+
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
4654
|
+
effort: toEffortLevel(row.effort),
|
|
4495
4655
|
cost_usd: row.cost_usd ?? 0,
|
|
4496
4656
|
// Токены/время работы (TASWM-353) — старые прогоны без колонок → 0.
|
|
4497
4657
|
tokens_in: row.tokens_in ?? 0,
|
|
@@ -4561,6 +4721,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4561
4721
|
area TEXT,
|
|
4562
4722
|
depends_on TEXT,
|
|
4563
4723
|
model TEXT,
|
|
4724
|
+
effort TEXT,
|
|
4564
4725
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
4565
4726
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
4566
4727
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
@@ -4662,7 +4823,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4662
4823
|
// Чем обошёлся прогон, кроме денег (TASWM-353): токены вход/выход и время работы.
|
|
4663
4824
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
4664
4825
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
4665
|
-
"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"
|
|
4666
4830
|
]) {
|
|
4667
4831
|
try {
|
|
4668
4832
|
this.db.exec(alter);
|
|
@@ -4682,7 +4846,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4682
4846
|
createRun(input) {
|
|
4683
4847
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
4684
4848
|
const res = this.db.prepare(
|
|
4685
|
-
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4686
4850
|
).run(
|
|
4687
4851
|
input.task_slug,
|
|
4688
4852
|
name,
|
|
@@ -4695,6 +4859,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4695
4859
|
input.area ?? null,
|
|
4696
4860
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
4697
4861
|
input.model ?? null,
|
|
4862
|
+
input.effort ?? null,
|
|
4863
|
+
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
4698
4864
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
4699
4865
|
input.resolves_run_id ?? null,
|
|
4700
4866
|
input.reviews_run_id ?? null,
|
|
@@ -5619,6 +5785,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5619
5785
|
// предикат «репо есть локально» для решения, брать ли свободный прогон.
|
|
5620
5786
|
host;
|
|
5621
5787
|
repoExists;
|
|
5788
|
+
// TASWM-398: ЧЕМ эта машина исполняет прогоны — id адаптера (claude-code|codex).
|
|
5789
|
+
executorId;
|
|
5622
5790
|
// Зеркало.
|
|
5623
5791
|
runs = /* @__PURE__ */ new Map();
|
|
5624
5792
|
runtime = /* @__PURE__ */ new Map();
|
|
@@ -5696,6 +5864,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5696
5864
|
this.host = opts.host ?? null;
|
|
5697
5865
|
this.repoExists = opts.repoExists ?? existsSync5;
|
|
5698
5866
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
5867
|
+
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
5699
5868
|
this.outboxPath = opts.outboxPath;
|
|
5700
5869
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
5701
5870
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
@@ -5809,8 +5978,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5809
5978
|
runFields(patch) {
|
|
5810
5979
|
const out = {};
|
|
5811
5980
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
5981
|
+
if (patch.status === "running") out.executor = this.executorId;
|
|
5812
5982
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
5813
5983
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
5984
|
+
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
5814
5985
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
5815
5986
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
5816
5987
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
@@ -5881,6 +6052,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
5881
6052
|
area: input.area ?? null,
|
|
5882
6053
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
5883
6054
|
model: input.model ?? null,
|
|
6055
|
+
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
6056
|
+
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
6057
|
+
effort: input.effort ?? null,
|
|
5884
6058
|
cost_usd: 0,
|
|
5885
6059
|
tokens_in: 0,
|
|
5886
6060
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
@@ -5933,6 +6107,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5933
6107
|
status: "queued",
|
|
5934
6108
|
area: d.area,
|
|
5935
6109
|
model: d.model,
|
|
6110
|
+
effort: d.effort,
|
|
6111
|
+
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
5936
6112
|
name: d.name,
|
|
5937
6113
|
repo: d.repo,
|
|
5938
6114
|
depends_on: d.depends_on,
|
|
@@ -6686,6 +6862,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
6686
6862
|
* → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
|
|
6687
6863
|
*/
|
|
6688
6864
|
async ownerMaxDrones() {
|
|
6865
|
+
return (await this.ownerSwarmSettings())?.maxDrones ?? null;
|
|
6866
|
+
}
|
|
6867
|
+
/**
|
|
6868
|
+
* TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
|
|
6869
|
+
* которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
|
|
6870
|
+
* двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
|
|
6871
|
+
* соседним полем того же ответа был бы платой ни за что.
|
|
6872
|
+
*
|
|
6873
|
+
* Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
|
|
6874
|
+
* (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
|
|
6875
|
+
* не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
|
|
6876
|
+
*/
|
|
6877
|
+
async ownerSwarmSettings() {
|
|
6689
6878
|
let res;
|
|
6690
6879
|
try {
|
|
6691
6880
|
res = await this.call("who_am_i", {});
|
|
@@ -6695,7 +6884,27 @@ var SwarmClient = class _SwarmClient {
|
|
|
6695
6884
|
if (res?.isError) return null;
|
|
6696
6885
|
const obj = jsonFromTextContent(res);
|
|
6697
6886
|
const n = obj?.maxDrones;
|
|
6698
|
-
|
|
6887
|
+
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
6888
|
+
const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
6889
|
+
const cost = obj?.swarmSettings?.maxCostUsd;
|
|
6890
|
+
return {
|
|
6891
|
+
maxDrones: Math.trunc(n),
|
|
6892
|
+
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
6893
|
+
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
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
|
|
6907
|
+
};
|
|
6699
6908
|
}
|
|
6700
6909
|
/**
|
|
6701
6910
|
* Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
|
|
@@ -6833,19 +7042,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
6833
7042
|
if (res?.isError) return [];
|
|
6834
7043
|
const raw = (res.structuredContent ?? {}).hosts;
|
|
6835
7044
|
if (!Array.isArray(raw)) return [];
|
|
6836
|
-
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) =>
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
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
|
+
});
|
|
6849
7064
|
}
|
|
6850
7065
|
/**
|
|
6851
7066
|
* TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
|
|
@@ -6911,13 +7126,22 @@ var SwarmClient = class _SwarmClient {
|
|
|
6911
7126
|
* разное. Не знаем своего потолка (ещё не читали / sqlite) → ключ не шлём, прод не
|
|
6912
7127
|
* трогает прошлое значение — та же логика, что у repoFullNames.
|
|
6913
7128
|
*
|
|
7129
|
+
* TASWM-394: тем же пульсом уезжают РЕСУРСЫ машины (`resources` — процессор, память,
|
|
7130
|
+
* диск и время замера). Это не новое измерение, а ровно то, что демон УЖЕ посчитал для
|
|
7131
|
+
* своего гейта спавна: числа в интерфейсе обязаны совпадать с теми, по которым он
|
|
7132
|
+
* отказывается поднимать дрона. Без них «машина на связи, свободна» и «машина физически
|
|
7133
|
+
* не может поднять дрона» выглядят в проде одинаково — 12.08 это стоило полудня
|
|
7134
|
+
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
7135
|
+
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
7136
|
+
*
|
|
6914
7137
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6915
7138
|
*/
|
|
6916
|
-
async heartbeat(daemonVersion, pause, capacity) {
|
|
7139
|
+
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
6917
7140
|
try {
|
|
6918
7141
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6919
7142
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
6920
7143
|
if (capacity != null && Number.isFinite(capacity) && capacity > 0) args.capacity = Math.trunc(capacity);
|
|
7144
|
+
if (resources) args.resources = resources;
|
|
6921
7145
|
if (pause !== void 0) {
|
|
6922
7146
|
args.paused = !!pause?.paused;
|
|
6923
7147
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
@@ -7124,6 +7348,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7124
7348
|
area: null,
|
|
7125
7349
|
depends_on: [],
|
|
7126
7350
|
model: null,
|
|
7351
|
+
effort: null,
|
|
7352
|
+
// усилие рассуждения (TASWM-395) — прод-поле, заполняется ниже из ответа
|
|
7127
7353
|
cost_usd: 0,
|
|
7128
7354
|
tokens_in: 0,
|
|
7129
7355
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
@@ -7192,6 +7418,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7192
7418
|
local.area = pr.area ?? null;
|
|
7193
7419
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
7194
7420
|
local.model = pr.model ?? null;
|
|
7421
|
+
local.effort = toEffortLevel(pr.effort);
|
|
7195
7422
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
7196
7423
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
7197
7424
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
@@ -8186,8 +8413,26 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8186
8413
|
};
|
|
8187
8414
|
}
|
|
8188
8415
|
|
|
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
|
+
}
|
|
8426
|
+
function resolveRunModel(run, swarm, env = process.env) {
|
|
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;
|
|
8431
|
+
}
|
|
8432
|
+
|
|
8189
8433
|
// src/launcher.ts
|
|
8190
8434
|
init_executors();
|
|
8435
|
+
init_src();
|
|
8191
8436
|
var requireFromHere = createRequire(import.meta.url);
|
|
8192
8437
|
function resolveTsxCli() {
|
|
8193
8438
|
try {
|
|
@@ -8199,6 +8444,15 @@ function resolveTsxCli() {
|
|
|
8199
8444
|
}
|
|
8200
8445
|
}
|
|
8201
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
|
+
}
|
|
8202
8456
|
function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
8203
8457
|
const env = { ...base3 };
|
|
8204
8458
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
@@ -8323,7 +8577,13 @@ function makeLauncher(cfg) {
|
|
|
8323
8577
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
8324
8578
|
}
|
|
8325
8579
|
};
|
|
8326
|
-
const model = run
|
|
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
|
+
}
|
|
8327
8587
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8328
8588
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8329
8589
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8342,7 +8602,14 @@ function makeLauncher(cfg) {
|
|
|
8342
8602
|
// Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
|
|
8343
8603
|
// сидит в HOME демона, и запирать половину дверей — самообман.
|
|
8344
8604
|
denyPaths: cfg.droneIsolation ? dronePrivatePaths(realHome, cfg.droneIsolation.swarmHome) : [],
|
|
8345
|
-
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
|
|
8346
8613
|
});
|
|
8347
8614
|
let stdio = "ignore";
|
|
8348
8615
|
let outFd;
|
|
@@ -8391,7 +8658,7 @@ function accountRunCost(deps, run) {
|
|
|
8391
8658
|
const dir = join13(deps.runLogsDir, String(run.id));
|
|
8392
8659
|
const files = unprocessedLogFiles(dir);
|
|
8393
8660
|
if (files.length === 0) return;
|
|
8394
|
-
const model = run
|
|
8661
|
+
const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
|
|
8395
8662
|
const executor = deps.executor ?? resolveExecutor();
|
|
8396
8663
|
let added = 0;
|
|
8397
8664
|
let tokensIn = 0;
|
|
@@ -8441,6 +8708,9 @@ function makeCostAccountant(deps) {
|
|
|
8441
8708
|
init_executors();
|
|
8442
8709
|
import { readdirSync as readdirSync9, readFileSync as readFileSync11 } from "fs";
|
|
8443
8710
|
import { join as join14 } from "path";
|
|
8711
|
+
function endedItself(outcome) {
|
|
8712
|
+
return outcome === "graceful" || outcome === "budget";
|
|
8713
|
+
}
|
|
8444
8714
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
8445
8715
|
function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
8446
8716
|
const dir = join14(runLogsDir, String(runId));
|
|
@@ -8464,7 +8734,9 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
8464
8734
|
} catch {
|
|
8465
8735
|
return "unknown";
|
|
8466
8736
|
}
|
|
8467
|
-
|
|
8737
|
+
const result = executor.parseResult(raw, null);
|
|
8738
|
+
if (!result) return "crash";
|
|
8739
|
+
return result.stoppedByBudget ? "budget" : "graceful";
|
|
8468
8740
|
}
|
|
8469
8741
|
function makeExitClassifier(runLogsDir, executor) {
|
|
8470
8742
|
const exec = executor ?? resolveExecutor();
|
|
@@ -9166,6 +9438,9 @@ async function mergedPrForBranch(branch, repoDir, run = realRepoRunner) {
|
|
|
9166
9438
|
async function closePR(prUrl, run = realRunner) {
|
|
9167
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)"]);
|
|
9168
9440
|
}
|
|
9441
|
+
async function postPrComment(prUrl, body, run = realRunner) {
|
|
9442
|
+
await run(["pr", "comment", prUrl, "--body", body]);
|
|
9443
|
+
}
|
|
9169
9444
|
var realCommentsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json comments`).stdout;
|
|
9170
9445
|
var VERDICT_RE = /^[^A-Za-z]*(APPROVED?|LGTM|(?:REQUEST(?:ED)?\s+)?CHANGES(?:\s+REQUESTED)?)\b/i;
|
|
9171
9446
|
var APPROVE_RE = /^(APPROVE|LGTM)/i;
|
|
@@ -9495,6 +9770,41 @@ ${notFoundText}` : notFoundText }],
|
|
|
9495
9770
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9496
9771
|
}
|
|
9497
9772
|
);
|
|
9773
|
+
server.tool(
|
|
9774
|
+
"report_acceptance_check",
|
|
9775
|
+
"\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043E\u0434\u0438\u043D \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0442\u0432\u043E\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438: passed \u2014 \u043F\u0440\u043E\u0448\u043B\u0430, failed \u2014 \u0443\u043F\u0430\u043B\u0430, pending \u2014 \u0441\u043D\u043E\u0432\u0430 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C. id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0431\u0435\u0440\u0438 \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 (\u0441\u0442\u0440\u043E\u043A\u0430 \xAB(id: \u2026)\xBB) \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435 \u0438\u043B\u0438 \u0432 read_task. evidence \u2014 \u0414\u041E\u041A\u0410\u0417\u0410\u0422\u0415\u041B\u042C\u0421\u0422\u0412\u041E: \u0441\u0442\u0440\u043E\u043A\u0430 \u0432\u044B\u0432\u043E\u0434\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B. \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u0442\u0430\u0432\u044C \u043F\u043E \u0424\u0410\u041A\u0422\u0423 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0430 \u043D\u0435 \u043F\u043E \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044E: \xAB\u0443\u043F\u0430\u043B\u0430\xBB \u0438 \xAB\u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C\xBB \u2014 \u0440\u0430\u0437\u043D\u043E\u0435. \u041F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u0432\u0441\u0435\u043C \u043F\u0443\u043D\u043A\u0442\u0430\u043C \u041F\u0415\u0420\u0415\u0414 done(): \u043D\u0435\u043F\u0440\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043F\u043E \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0430 \u0440\u0430\u0431\u043E\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0441\u043D\u0438\u043C\u0430\u044E\u0442. \u0421\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0430\u043C\u0438 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043D\u0435\u0447\u0435\u043C: \u0438\u0445 \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0449\u0438\u043A.",
|
|
9776
|
+
{
|
|
9777
|
+
id: z.string().describe("id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \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"),
|
|
9778
|
+
state: z.enum(["passed", "failed", "pending"]).describe("passed | failed | pending \u2014 \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430"),
|
|
9779
|
+
evidence: z.string().max(4e3).optional().describe("\u0447\u0435\u043C \u0434\u043E\u043A\u0430\u0437\u0430\u043D\u043E: \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435")
|
|
9780
|
+
},
|
|
9781
|
+
async ({ id, state, evidence }) => {
|
|
9782
|
+
if (!opts.taskless) {
|
|
9783
|
+
return {
|
|
9784
|
+
content: [
|
|
9785
|
+
{
|
|
9786
|
+
type: "text",
|
|
9787
|
+
text: "Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0433\u0430\u043B\u043E\u0447\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C. \u041E\u0442\u0447\u0438\u0442\u0430\u0439\u0441\u044F \u043F\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044E \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done()."
|
|
9788
|
+
}
|
|
9789
|
+
],
|
|
9790
|
+
isError: true
|
|
9791
|
+
};
|
|
9792
|
+
}
|
|
9793
|
+
const text = await opts.taskless.reportAcceptanceCheck({ id, state, evidence });
|
|
9794
|
+
if (!text) {
|
|
9795
|
+
return {
|
|
9796
|
+
content: [
|
|
9797
|
+
{
|
|
9798
|
+
type: "text",
|
|
9799
|
+
text: `\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 ${id} \u043D\u0435 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u0430 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430): id \u043C\u043E\u0433 \u0431\u044B\u0442\u044C \u0447\u0443\u0436\u0438\u043C \u0438\u043B\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044C \u2014 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0439 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 read_task. \u041D\u0435 \u043C\u043E\u043B\u0447\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done().`
|
|
9800
|
+
}
|
|
9801
|
+
],
|
|
9802
|
+
isError: true
|
|
9803
|
+
};
|
|
9804
|
+
}
|
|
9805
|
+
return { content: [{ type: "text", text }] };
|
|
9806
|
+
}
|
|
9807
|
+
);
|
|
9498
9808
|
server.tool(
|
|
9499
9809
|
"ask",
|
|
9500
9810
|
"\u0417\u0430\u0434\u0430\u0442\u044C \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u043E\u0442\u0432\u0435\u0442\u0430. \u0415\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F \u2014 \u0434\u0430\u0439 options (2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430, \u0441\u0442\u0430\u043D\u0443\u0442 \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438); \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0432\u044B\u0431\u0435\u0440\u0435\u0442 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0438\u043B\u0438 \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u0441\u0432\u043E\u0451. \u041E\u043F\u0443\u0441\u0442\u0438 options \u0434\u043B\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043E\u0442\u0432\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u043E\u0439. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439.",
|
|
@@ -11037,7 +11347,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
11037
11347
|
// package.json
|
|
11038
11348
|
var package_default = {
|
|
11039
11349
|
name: "@taskless-app/swarm",
|
|
11040
|
-
version: "0.6.
|
|
11350
|
+
version: "0.6.6",
|
|
11041
11351
|
type: "module",
|
|
11042
11352
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11043
11353
|
keywords: [
|
|
@@ -11087,7 +11397,7 @@ var package_default = {
|
|
|
11087
11397
|
execa: "^10.0.0",
|
|
11088
11398
|
zod: "3.25.76"
|
|
11089
11399
|
},
|
|
11090
|
-
gitHead: "
|
|
11400
|
+
gitHead: "89e3fe0078755d130512c75ac85f195e6b950392"
|
|
11091
11401
|
};
|
|
11092
11402
|
|
|
11093
11403
|
// src/version.ts
|
|
@@ -11310,7 +11620,8 @@ function timelineState(db, now = Date.now(), overmindLog = []) {
|
|
|
11310
11620
|
heroRunId: r.hero_run_id,
|
|
11311
11621
|
reviewVerdict: r.review_verdict,
|
|
11312
11622
|
reviewPhase: r.review_phase,
|
|
11313
|
-
model: r.model
|
|
11623
|
+
model: r.model,
|
|
11624
|
+
effort: r.effort
|
|
11314
11625
|
});
|
|
11315
11626
|
}
|
|
11316
11627
|
const liveLanes = lanes.filter((l) => l.live);
|
|
@@ -13084,9 +13395,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
13084
13395
|
)
|
|
13085
13396
|
);
|
|
13086
13397
|
return {
|
|
13087
|
-
since:
|
|
13088
|
-
lastFiredAt:
|
|
13089
|
-
fires:
|
|
13398
|
+
since: pick2(since, fresh),
|
|
13399
|
+
lastFiredAt: pick2(lastFiredAt, fresh),
|
|
13400
|
+
fires: pick2(fires, fresh),
|
|
13090
13401
|
// TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
|
|
13091
13402
|
// уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
|
|
13092
13403
|
// недоставленным, гашение не становится лишним — оно становится нужнее.
|
|
@@ -13170,7 +13481,7 @@ function strings(raw) {
|
|
|
13170
13481
|
if (!Array.isArray(raw)) return [];
|
|
13171
13482
|
return raw.filter((v) => typeof v === "string" && v.length > 0);
|
|
13172
13483
|
}
|
|
13173
|
-
function
|
|
13484
|
+
function pick2(src, keys) {
|
|
13174
13485
|
const out = {};
|
|
13175
13486
|
for (const k of keys) if (src[k] != null) out[k] = src[k];
|
|
13176
13487
|
return out;
|
|
@@ -13568,13 +13879,20 @@ var CHANGES_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CHANGES_WAKE_COOLDOWN_MS
|
|
|
13568
13879
|
var CHANGES_WAKE_LIMIT = Number(process.env.SWARM_CHANGES_WAKE_LIMIT) || 3;
|
|
13569
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";
|
|
13570
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
|
+
}
|
|
13571
13888
|
var NEEDS_HUMAN_MARKS = [
|
|
13572
13889
|
RESOLVE_ESCALATION_MARK,
|
|
13573
13890
|
REVIEW_ESCALATION_MARK,
|
|
13574
13891
|
REVIEW_UNRECOVERABLE_MARK,
|
|
13575
13892
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
13576
13893
|
CI_WAKE_ESCALATION_MARK,
|
|
13577
|
-
CHANGES_WAKE_ESCALATION_MARK
|
|
13894
|
+
CHANGES_WAKE_ESCALATION_MARK,
|
|
13895
|
+
REVIEW_BUDGET_MARK
|
|
13578
13896
|
];
|
|
13579
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";
|
|
13580
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)";
|
|
@@ -13954,8 +14272,14 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
13954
14272
|
// вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
|
|
13955
14273
|
// ветку заводит spawnDrone из слага) — прежняя формулировка.
|
|
13956
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"}.`,
|
|
13957
|
-
"\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); 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).",
|
|
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).",
|
|
13958
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.",
|
|
13959
14283
|
// TASWM-293: живой случай SCE-5 — ревьюер заблокировал PR за отсутствие CI (у проекта
|
|
13960
14284
|
// его нет и не предполагается), автора разбудили «закрывать замечания», замечаний по коду
|
|
13961
14285
|
// не было, и дрон завёл в ЧУЖОМ репозитории .github/workflows/check.yml, чтобы пройти
|
|
@@ -14099,6 +14423,13 @@ function lastChangesSummary(db, reviewRunId) {
|
|
|
14099
14423
|
}
|
|
14100
14424
|
return null;
|
|
14101
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");
|
|
14102
14433
|
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
14103
14434
|
const name = reviewer.name || `#${reviewer.id}`;
|
|
14104
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)";
|
|
@@ -14116,6 +14447,8 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
14116
14447
|
] : [],
|
|
14117
14448
|
"",
|
|
14118
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,
|
|
14119
14452
|
// TASWM-283: ревьюер читает ТУ ЖЕ задачу и так же слеп к её картинкам — без макета
|
|
14120
14453
|
// «сделано ли как просили» он проверить не может, а UI-пункт чек-листа держится
|
|
14121
14454
|
// ровно на этом. Кладём ему тот же каталог вложений, что и исполнителю.
|
|
@@ -15109,6 +15442,67 @@ async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
|
15109
15442
|
}
|
|
15110
15443
|
return true;
|
|
15111
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
|
+
}
|
|
15112
15506
|
function buildEscalationCandidates(db) {
|
|
15113
15507
|
const candidates = [];
|
|
15114
15508
|
for (const run of db.listRuns("review")) {
|
|
@@ -15132,6 +15526,7 @@ async function tickOnce(deps) {
|
|
|
15132
15526
|
const now = deps.now ? deps.now() : Date.now();
|
|
15133
15527
|
const worktreeExists = deps.worktreeExists ?? existsSync16;
|
|
15134
15528
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
15529
|
+
deps.sampleHost?.();
|
|
15135
15530
|
if (deps.rehydrate) {
|
|
15136
15531
|
const rh = deps.rehydrate;
|
|
15137
15532
|
const sinceLast = rh.lastAt == null ? Infinity : now - rh.lastAt;
|
|
@@ -15243,7 +15638,7 @@ async function tickOnce(deps) {
|
|
|
15243
15638
|
continue;
|
|
15244
15639
|
}
|
|
15245
15640
|
}
|
|
15246
|
-
if (deps.exitOutcome && deps.exitOutcome(run)
|
|
15641
|
+
if (deps.exitOutcome && endedItself(deps.exitOutcome(run))) continue;
|
|
15247
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)`);
|
|
15248
15643
|
}
|
|
15249
15644
|
stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
|
|
@@ -15278,12 +15673,11 @@ async function tickOnce(deps) {
|
|
|
15278
15673
|
stormActive
|
|
15279
15674
|
});
|
|
15280
15675
|
if (salvaged) continue;
|
|
15281
|
-
|
|
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.`
|
|
15282
15680
|
});
|
|
15283
|
-
deps.db.updateRun(run.id, { status: "failed", stop_reason: REVIEW_NO_VERDICT_REASON });
|
|
15284
|
-
deps.db.addEvent(run.id, "failed", REVIEW_NO_VERDICT_REASON);
|
|
15285
|
-
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`);
|
|
15286
|
-
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.`);
|
|
15287
15681
|
continue;
|
|
15288
15682
|
}
|
|
15289
15683
|
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
@@ -15372,6 +15766,8 @@ async function tickOnce(deps) {
|
|
|
15372
15766
|
const attempts = deps.db.reviewerAttemptCountFor(run.id);
|
|
15373
15767
|
const runEvents = deps.db.events(run.id);
|
|
15374
15768
|
const unrecoverableMark = reviewUnrecoverableMark(run.branch || run.task_slug);
|
|
15769
|
+
const budgetMark = reviewBudgetMark(run.review_cycle ?? 0);
|
|
15770
|
+
const LATCHES = [unrecoverableMark, budgetMark];
|
|
15375
15771
|
let verdictFromComment = null;
|
|
15376
15772
|
let lastAttempt = null;
|
|
15377
15773
|
if (attempts > 0 && deps.verdictComment) {
|
|
@@ -15391,7 +15787,7 @@ async function tickOnce(deps) {
|
|
|
15391
15787
|
await wakeAuthorOnChanges(deps, run, now, droneCap);
|
|
15392
15788
|
}
|
|
15393
15789
|
}
|
|
15394
|
-
} else if (runEvents.some((e) => (e.text ?? "").includes(
|
|
15790
|
+
} else if (runEvents.some((e) => LATCHES.some((mark) => (e.text ?? "").includes(mark)))) {
|
|
15395
15791
|
} else if (attempts >= REVIEW_SPAWN_LIMIT) {
|
|
15396
15792
|
if (!runEvents.some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_ESCALATION_MARK))) {
|
|
15397
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`);
|
|
@@ -15474,6 +15870,10 @@ async function tickOnce(deps) {
|
|
|
15474
15870
|
}
|
|
15475
15871
|
continue;
|
|
15476
15872
|
}
|
|
15873
|
+
if (!alive && outcome === "budget") {
|
|
15874
|
+
await handleBudgetExhausted(deps, run, { now, droneCap, stormActive });
|
|
15875
|
+
continue;
|
|
15876
|
+
}
|
|
15477
15877
|
if (!alive && run.kind === "hero") {
|
|
15478
15878
|
const young = now - lastActivityMs(deps.db, run) < RESPAWN_YOUNG_MS;
|
|
15479
15879
|
if (young && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -15631,7 +16031,7 @@ async function tickOnce(deps) {
|
|
|
15631
16031
|
if (run.kind === "hero") continue;
|
|
15632
16032
|
if (run.reviews_run_id != null) continue;
|
|
15633
16033
|
if (run.stop_reason === RELIEF_PAUSE_REASON) continue;
|
|
15634
|
-
if (
|
|
16034
|
+
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
15635
16035
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
15636
16036
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
15637
16037
|
const wt = run.worktree_path;
|
|
@@ -16117,6 +16517,9 @@ async function runDaemon(opts) {
|
|
|
16117
16517
|
}
|
|
16118
16518
|
} : void 0;
|
|
16119
16519
|
let lastDroneCap = null;
|
|
16520
|
+
let lastSwarmModels = null;
|
|
16521
|
+
let lastSwarmEfforts = null;
|
|
16522
|
+
let lastMaxCostUsd = null;
|
|
16120
16523
|
let lastOwnCapacity = null;
|
|
16121
16524
|
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
16122
16525
|
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
@@ -16129,7 +16532,16 @@ async function runDaemon(opts) {
|
|
|
16129
16532
|
executor: EXECUTOR,
|
|
16130
16533
|
droneIsolation: DRONE_ISOLATION,
|
|
16131
16534
|
channel: droneChannelLaunch,
|
|
16132
|
-
droneCap: () => lastDroneCap
|
|
16535
|
+
droneCap: () => lastDroneCap,
|
|
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
|
|
16133
16545
|
});
|
|
16134
16546
|
let prepareReader;
|
|
16135
16547
|
const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
|
|
@@ -16175,7 +16587,7 @@ async function runDaemon(opts) {
|
|
|
16175
16587
|
const client = db;
|
|
16176
16588
|
heartbeat = {
|
|
16177
16589
|
push: async () => {
|
|
16178
|
-
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());
|
|
16179
16591
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
16180
16592
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
16181
16593
|
if (reply.logRequests.length) {
|
|
@@ -16265,12 +16677,24 @@ async function runDaemon(opts) {
|
|
|
16265
16677
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
16266
16678
|
const client = db;
|
|
16267
16679
|
const readMaxDrones = makeMaxDronesReader({
|
|
16268
|
-
|
|
16680
|
+
// TASWM-364: одним чтением берём и кап, и модели роя — это соседние поля ОДНОГО
|
|
16681
|
+
// ответа who_am_i. Модели складываем в снимок для спавна тем же побочным ходом,
|
|
16682
|
+
// что и кап ниже: второго сетевого канала за ними заводить незачем.
|
|
16683
|
+
fetch: async () => {
|
|
16684
|
+
const settings = await client.ownerSwarmSettings();
|
|
16685
|
+
if (settings) {
|
|
16686
|
+
lastSwarmModels = settings.models;
|
|
16687
|
+
lastSwarmEfforts = settings.efforts;
|
|
16688
|
+
lastMaxCostUsd = settings.maxCostUsd;
|
|
16689
|
+
}
|
|
16690
|
+
return settings?.maxDrones ?? null;
|
|
16691
|
+
},
|
|
16269
16692
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
16270
16693
|
ttlMs: 1e4
|
|
16271
16694
|
});
|
|
16272
16695
|
maxDrones = async () => {
|
|
16273
|
-
const
|
|
16696
|
+
const live = await readMaxDrones();
|
|
16697
|
+
const own = lastOwnCapacity = hostMaxDrones ?? live;
|
|
16274
16698
|
return lastDroneCap = operatorMaxDrones != null ? Math.min(own, operatorMaxDrones) : own;
|
|
16275
16699
|
};
|
|
16276
16700
|
}
|
|
@@ -16467,7 +16891,9 @@ async function runDaemon(opts) {
|
|
|
16467
16891
|
repoHasCi: (run) => repoHasCiWorkflows(run.worktree_path, run.repo || opts.repo),
|
|
16468
16892
|
isAlive: pidAlive,
|
|
16469
16893
|
// Учёт cost/usage (TASWM-188): реальный аккаунтант поверх RUN_LOGS_DIR + db.
|
|
16470
|
-
|
|
16894
|
+
// TASWM-364: swarmModels — ТОТ ЖЕ снимок, что уехал в launcher: цена токенов считается
|
|
16895
|
+
// по модели, на которой прогон реально работал.
|
|
16896
|
+
accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels }),
|
|
16471
16897
|
// Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
|
|
16472
16898
|
// (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
|
|
16473
16899
|
exitOutcome: makeExitClassifier(RUN_LOGS_DIR, EXECUTOR),
|
|
@@ -16519,6 +16945,9 @@ async function runDaemon(opts) {
|
|
|
16519
16945
|
transcriptMtimeMs: (p) => EXECUTOR.session.mtimeMs(p),
|
|
16520
16946
|
// Ресурс-гейт спавна (TASWM-130): реальные показания хоста (load1 / free RAM).
|
|
16521
16947
|
resourcesOk: hostResourcesOk,
|
|
16948
|
+
// Замер занятости CPU раз в тик (TASWM-397): окно фиксированной длины набирает пульс
|
|
16949
|
+
// тика, гейт ниже только читает готовое показание.
|
|
16950
|
+
sampleHost: () => void sampleCpuBusy(),
|
|
16522
16951
|
// Ресурс-гейт v2 (TASWM-151): жёсткий порог активной разгрузки — те же сырые показания
|
|
16523
16952
|
// хоста (общий кэш в resources.ts), но заведомо строже настроенные пороги.
|
|
16524
16953
|
reliefNeeded: hostReliefNeeded,
|
|
@@ -16649,6 +17078,11 @@ async function runDaemon(opts) {
|
|
|
16649
17078
|
// Сторож прогресса роя (TASWM-270): в отличие от anomalies выше — заведён БЕЗУСЛОВНО
|
|
16650
17079
|
// (см. докблок конструирования watchdog выше — рунги а/б/в работают и без client-режима).
|
|
16651
17080
|
watchdog,
|
|
17081
|
+
// TASWM-377: отказ в PR за мёртвого (ревьюер, срезанный потолком трат). Заведён
|
|
17082
|
+
// БЕЗУСЛОВНО, в отличие от ключей авто-ревью ниже: разбирать уже поднятого ревьюера
|
|
17083
|
+
// приходится и после того, как авто-ревью выключили, — а молчать за него тем более
|
|
17084
|
+
// некому.
|
|
17085
|
+
prComment: postPrComment,
|
|
16652
17086
|
// Авто-ревью (TASWM-148): ключи есть только когда SWARM_AUTO_REVIEW включён (дефолт да) —
|
|
16653
17087
|
// spawnReviewerFn/verdictCommentFn/reviewCutoffAtValue выставляются В ОДНОМ if(SWARM_AUTO_REVIEW)
|
|
16654
17088
|
// блоке выше, поэтому один и тот же гейт на все три (без отдельных тернарных условий).
|
|
@@ -16974,6 +17408,7 @@ if (isEntrypoint) {
|
|
|
16974
17408
|
}
|
|
16975
17409
|
export {
|
|
16976
17410
|
AWAITING_CI_REASON,
|
|
17411
|
+
BUDGET_STOP_REASON,
|
|
16977
17412
|
CHANGES_WAKE_COOLDOWN_MS,
|
|
16978
17413
|
CHANGES_WAKE_ESCALATION_MARK,
|
|
16979
17414
|
CHANGES_WAKE_LIMIT,
|
|
@@ -17001,6 +17436,7 @@ export {
|
|
|
17001
17436
|
RESOLVE_LIMIT,
|
|
17002
17437
|
RESPAWN_LIMIT,
|
|
17003
17438
|
RESPAWN_YOUNG_MS,
|
|
17439
|
+
REVIEW_BUDGET_MARK,
|
|
17004
17440
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
17005
17441
|
REVIEW_CYCLE_LIMIT,
|
|
17006
17442
|
REVIEW_ESCALATION_MARK,
|
|
@@ -17030,6 +17466,7 @@ export {
|
|
|
17030
17466
|
repoHasCiWorkflows,
|
|
17031
17467
|
resolveConflict,
|
|
17032
17468
|
respawnRun,
|
|
17469
|
+
reviewBudgetMark,
|
|
17033
17470
|
reviewUnrecoverableMark,
|
|
17034
17471
|
reviveStuck,
|
|
17035
17472
|
runDaemon,
|