@taskless-app/swarm 0.6.5 → 0.6.7
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 +294 -47
- package/dist/overmind.js +430 -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,36 @@ 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
|
+
|
|
1316
|
+
// ../swarm-core/src/queuePlan.ts
|
|
1317
|
+
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS, QUEUE_REVIEW_TAIL_MS;
|
|
1318
|
+
var init_queuePlan = __esm({
|
|
1319
|
+
"../swarm-core/src/queuePlan.ts"() {
|
|
1320
|
+
"use strict";
|
|
1321
|
+
init_status();
|
|
1322
|
+
init_hostPool();
|
|
1323
|
+
init_hosts();
|
|
1324
|
+
init_time();
|
|
1325
|
+
MIN_MS = 6e4;
|
|
1326
|
+
QUEUE_NOMINAL_RUN_MS = 25 * MIN_MS;
|
|
1327
|
+
QUEUE_MIN_TAIL_MS = 3 * MIN_MS;
|
|
1328
|
+
QUEUE_REVIEW_TAIL_MS = 10 * MIN_MS;
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1270
1332
|
// ../swarm-core/src/index.ts
|
|
1271
1333
|
var init_src = __esm({
|
|
1272
1334
|
"../swarm-core/src/index.ts"() {
|
|
@@ -1284,12 +1346,15 @@ var init_src = __esm({
|
|
|
1284
1346
|
init_names();
|
|
1285
1347
|
init_baseBranch();
|
|
1286
1348
|
init_hosts();
|
|
1349
|
+
init_hostResources();
|
|
1287
1350
|
init_hostPool();
|
|
1288
1351
|
init_hostCommands();
|
|
1289
1352
|
init_relief();
|
|
1290
1353
|
init_cost();
|
|
1291
1354
|
init_runGuards();
|
|
1292
1355
|
init_codeZones();
|
|
1356
|
+
init_effort();
|
|
1357
|
+
init_queuePlan();
|
|
1293
1358
|
}
|
|
1294
1359
|
});
|
|
1295
1360
|
|
|
@@ -1470,14 +1535,32 @@ function launchCostUsd(result, model) {
|
|
|
1470
1535
|
return costFromUsage(result.usage, model);
|
|
1471
1536
|
}
|
|
1472
1537
|
function launchSpend(result) {
|
|
1473
|
-
const u = result.usage ?? {};
|
|
1474
1538
|
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1475
|
-
|
|
1539
|
+
const sum = (u) => ({
|
|
1476
1540
|
tokensIn: num(u.input_tokens) + num(u.cache_creation_input_tokens) + num(u.cache_read_input_tokens),
|
|
1477
|
-
tokensOut: num(u.output_tokens)
|
|
1478
|
-
|
|
1541
|
+
tokensOut: num(u.output_tokens)
|
|
1542
|
+
});
|
|
1543
|
+
const spend = sum(result.usage ?? {});
|
|
1544
|
+
if (spend.tokensIn === 0 && spend.tokensOut === 0 && result.modelUsage) {
|
|
1545
|
+
for (const m of Object.values(result.modelUsage)) {
|
|
1546
|
+
const perModel = sum(asTokenUsage(m));
|
|
1547
|
+
spend.tokensIn += perModel.tokensIn;
|
|
1548
|
+
spend.tokensOut += perModel.tokensOut;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return { ...spend, durationMs: num(result.duration_ms) };
|
|
1552
|
+
}
|
|
1553
|
+
function asTokenUsage(m) {
|
|
1554
|
+
return {
|
|
1555
|
+
...m?.inputTokens !== void 0 ? { input_tokens: m.inputTokens } : {},
|
|
1556
|
+
...m?.outputTokens !== void 0 ? { output_tokens: m.outputTokens } : {},
|
|
1557
|
+
...m?.cacheCreationInputTokens !== void 0 ? { cache_creation_input_tokens: m.cacheCreationInputTokens } : {},
|
|
1558
|
+
...m?.cacheReadInputTokens !== void 0 ? { cache_read_input_tokens: m.cacheReadInputTokens } : {}
|
|
1479
1559
|
};
|
|
1480
1560
|
}
|
|
1561
|
+
function budgetExhausted(result) {
|
|
1562
|
+
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1563
|
+
}
|
|
1481
1564
|
var init_claudeResult = __esm({
|
|
1482
1565
|
"src/claudeResult.ts"() {
|
|
1483
1566
|
"use strict";
|
|
@@ -1489,8 +1572,10 @@ var init_claudeResult = __esm({
|
|
|
1489
1572
|
import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1490
1573
|
import { homedir } from "os";
|
|
1491
1574
|
import { join as join2 } from "path";
|
|
1492
|
-
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings) {
|
|
1575
|
+
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
1493
1576
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
1577
|
+
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
1578
|
+
const effortFlags = effort ? ["--effort", effort] : [];
|
|
1494
1579
|
const base3 = [
|
|
1495
1580
|
"--dangerously-skip-permissions",
|
|
1496
1581
|
...ISOLATION_FLAGS,
|
|
@@ -1498,6 +1583,8 @@ function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs
|
|
|
1498
1583
|
cfgPath,
|
|
1499
1584
|
...settings ? ["--settings", settings] : [],
|
|
1500
1585
|
...pluginFlags,
|
|
1586
|
+
...budgetFlags,
|
|
1587
|
+
...effortFlags,
|
|
1501
1588
|
"--output-format",
|
|
1502
1589
|
"json",
|
|
1503
1590
|
"-p",
|
|
@@ -1574,7 +1661,7 @@ var init_claudeCode = __esm({
|
|
|
1574
1661
|
const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
|
|
1575
1662
|
return {
|
|
1576
1663
|
bin: resolveClaudeBin(),
|
|
1577
|
-
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings)
|
|
1664
|
+
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
|
|
1578
1665
|
};
|
|
1579
1666
|
},
|
|
1580
1667
|
parseResult(raw, model) {
|
|
@@ -1587,7 +1674,10 @@ var init_claudeCode = __esm({
|
|
|
1587
1674
|
...est.warning ? { warning: est.warning } : {},
|
|
1588
1675
|
tokensIn: spend.tokensIn,
|
|
1589
1676
|
tokensOut: spend.tokensOut,
|
|
1590
|
-
durationMs: spend.durationMs
|
|
1677
|
+
durationMs: spend.durationMs,
|
|
1678
|
+
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1679
|
+
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1680
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1591
1681
|
};
|
|
1592
1682
|
},
|
|
1593
1683
|
session: {
|
|
@@ -1747,6 +1837,13 @@ ${notice}` : spec.prompt;
|
|
|
1747
1837
|
SKIP_GIT_CHECK_FLAG,
|
|
1748
1838
|
BYPASS_FLAG,
|
|
1749
1839
|
...spec.model ? ["-m", spec.model] : [],
|
|
1840
|
+
// TASWM-395: УСИЛИЕ РАССУЖДЕНИЯ (spec.effort) codex пока НЕ получает — и это сказано
|
|
1841
|
+
// вслух, как требует контракт адаптера (см. ExecutorLaunchSpec.effort), а не забыто.
|
|
1842
|
+
// Кандидат есть — `-c model_reasoning_effort=<low|medium|high>` — но словари не совпадают
|
|
1843
|
+
// (у нас пять уровней, xhigh/max пришлось бы схлопывать в high), а главное: непонятый
|
|
1844
|
+
// ключ в `-c` роняет НЕ один флаг, а весь спавн. Проверять это надо на живом codex, и
|
|
1845
|
+
// до такой проверки правильный размен — работающий codex на дефолтном усилии, а не
|
|
1846
|
+
// сломанный на настроенном. Провода до сюда доведены: останется дописать одну строку.
|
|
1750
1847
|
...mcpConfigArgs(spec),
|
|
1751
1848
|
prompt
|
|
1752
1849
|
];
|
|
@@ -1849,6 +1946,10 @@ __export(config_exports, {
|
|
|
1849
1946
|
SWARM_AUTO_REVIEW: () => SWARM_AUTO_REVIEW,
|
|
1850
1947
|
SWARM_BACKEND: () => SWARM_BACKEND,
|
|
1851
1948
|
SWARM_CLAIM_INTERVAL_MS: () => SWARM_CLAIM_INTERVAL_MS,
|
|
1949
|
+
SWARM_CPU_SANITY_BUSY: () => SWARM_CPU_SANITY_BUSY,
|
|
1950
|
+
SWARM_CPU_SANITY_LAG_MS: () => SWARM_CPU_SANITY_LAG_MS,
|
|
1951
|
+
SWARM_CPU_SANITY_LOAD_FACTOR: () => SWARM_CPU_SANITY_LOAD_FACTOR,
|
|
1952
|
+
SWARM_CPU_WINDOW_MS: () => SWARM_CPU_WINDOW_MS,
|
|
1852
1953
|
SWARM_DRONE_BRIDGE_MS: () => SWARM_DRONE_BRIDGE_MS,
|
|
1853
1954
|
SWARM_HOME: () => SWARM_HOME,
|
|
1854
1955
|
SWARM_LOW_DISK_GB: () => SWARM_LOW_DISK_GB,
|
|
@@ -1973,7 +2074,7 @@ function reviewCutoffAt() {
|
|
|
1973
2074
|
}
|
|
1974
2075
|
return at;
|
|
1975
2076
|
}
|
|
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;
|
|
2077
|
+
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
2078
|
var init_config = __esm({
|
|
1978
2079
|
"src/config.ts"() {
|
|
1979
2080
|
"use strict";
|
|
@@ -2003,6 +2104,10 @@ var init_config = __esm({
|
|
|
2003
2104
|
SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
|
|
2004
2105
|
SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
|
|
2005
2106
|
SWARM_MAX_CPU_BUSY = envNum("SWARM_MAX_CPU_BUSY", 0.85);
|
|
2107
|
+
SWARM_CPU_WINDOW_MS = Math.max(1e3, envNum("SWARM_CPU_WINDOW_MS", 3e3));
|
|
2108
|
+
SWARM_CPU_SANITY_BUSY = envNum("SWARM_CPU_SANITY_BUSY", 0.9);
|
|
2109
|
+
SWARM_CPU_SANITY_LOAD_FACTOR = envNum("SWARM_CPU_SANITY_LOAD_FACTOR", 0.5);
|
|
2110
|
+
SWARM_CPU_SANITY_LAG_MS = envNum("SWARM_CPU_SANITY_LAG_MS", 6e4);
|
|
2006
2111
|
SWARM_RELIEF_LOAD_FACTOR = envNum("SWARM_RELIEF_LOAD_FACTOR", 1.3);
|
|
2007
2112
|
SWARM_RELIEF_MIN_FREE_GB = envNum("SWARM_RELIEF_MIN_FREE_GB", 0.8);
|
|
2008
2113
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR = envNum("SWARM_RELIEF_MAX_FD_LOAD_FACTOR", 0.95);
|
|
@@ -2173,22 +2278,59 @@ function cpuBusyFromTicks(prev, cur) {
|
|
|
2173
2278
|
if (dTotal <= 0) return null;
|
|
2174
2279
|
return Math.min(1, Math.max(0, 1 - dIdle / dTotal));
|
|
2175
2280
|
}
|
|
2176
|
-
function
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2281
|
+
function cpuBusyFromSamples(prev, cur, minWindowMs = SWARM_CPU_WINDOW_MS) {
|
|
2282
|
+
if (!prev) return null;
|
|
2283
|
+
if (cur.at - prev.at < minWindowMs) return null;
|
|
2284
|
+
return cpuBusyFromTicks(prev, cur);
|
|
2285
|
+
}
|
|
2286
|
+
function cpuBusySane(busy, load1, cpus, highForMs, sanityBusy = SWARM_CPU_SANITY_BUSY, sanityLoadFactor = SWARM_CPU_SANITY_LOAD_FACTOR, lagMs = SWARM_CPU_SANITY_LAG_MS) {
|
|
2287
|
+
if (busy < sanityBusy) return true;
|
|
2288
|
+
if (highForMs < lagMs) return true;
|
|
2289
|
+
const cores = cpus > 0 ? cpus : 1;
|
|
2290
|
+
if (!Number.isFinite(load1) || load1 < 0) return true;
|
|
2291
|
+
return load1 >= cores * sanityLoadFactor;
|
|
2292
|
+
}
|
|
2293
|
+
function sampleCpuBusy(now = Date.now(), ticks = readCpuTicks(os.cpus())) {
|
|
2294
|
+
const cur = { at: now, ...ticks };
|
|
2295
|
+
if (!cpuWindowBase) {
|
|
2296
|
+
cpuWindowBase = cur;
|
|
2297
|
+
return cpuBusyValue;
|
|
2298
|
+
}
|
|
2299
|
+
if (cur.at - cpuWindowBase.at < SWARM_CPU_WINDOW_MS) return cpuBusyValue;
|
|
2300
|
+
cpuBusyValue = cpuBusyFromSamples(cpuWindowBase, cur);
|
|
2301
|
+
cpuWindowBase = cur;
|
|
2302
|
+
return cpuBusyValue;
|
|
2303
|
+
}
|
|
2304
|
+
function cpuBusyReading() {
|
|
2305
|
+
return cpuBusyValue;
|
|
2306
|
+
}
|
|
2307
|
+
function trustedCpuBusy(busy, load1, cpus, now) {
|
|
2308
|
+
if (busy == null || busy < SWARM_CPU_SANITY_BUSY) {
|
|
2309
|
+
cpuHighSince = null;
|
|
2310
|
+
return busy;
|
|
2311
|
+
}
|
|
2312
|
+
cpuHighSince ??= now;
|
|
2313
|
+
if (cpuBusySane(busy, load1, cpus, now - cpuHighSince)) return busy;
|
|
2314
|
+
if (now - cpuSanityWarnAt >= CPU_SANITY_WARN_EVERY_MS) {
|
|
2315
|
+
cpuSanityWarnAt = now;
|
|
2316
|
+
console.warn(
|
|
2317
|
+
`[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.`
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
return null;
|
|
2181
2321
|
}
|
|
2182
2322
|
function hostReading() {
|
|
2183
2323
|
const now = Date.now();
|
|
2184
2324
|
if (readingCache && now - readingCache.at < RESOURCES_CACHE_MS) return readingCache.reading;
|
|
2325
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
2326
|
+
const cpus = os.cpus().length;
|
|
2327
|
+
sampleCpuBusy(now);
|
|
2185
2328
|
const reading = {
|
|
2186
|
-
load1
|
|
2187
|
-
cpus
|
|
2329
|
+
load1,
|
|
2330
|
+
cpus,
|
|
2188
2331
|
freeGb: availableMemBytes() / 1e9,
|
|
2189
2332
|
fdLoadRatio: fileDescriptorLoadRatio(),
|
|
2190
|
-
cpuBusy:
|
|
2191
|
-
// TASWM-234: раз в тик (кэш выше) — иначе окно дельты схлопнется
|
|
2333
|
+
cpuBusy: trustedCpuBusy(cpuBusyReading(), load1, cpus, now),
|
|
2192
2334
|
totalGb: os.totalmem() / 1e9,
|
|
2193
2335
|
disk: diskGb()
|
|
2194
2336
|
};
|
|
@@ -2247,6 +2389,22 @@ function pressureReport(r, minFreeGb = SWARM_RELIEF_MIN_FREE_GB, maxCpuBusy = SW
|
|
|
2247
2389
|
function hostPressure() {
|
|
2248
2390
|
return pressureReport(hostReading());
|
|
2249
2391
|
}
|
|
2392
|
+
function resourcesForProd(r, measuredAt) {
|
|
2393
|
+
const gb = (v) => Math.round(v * 10) / 10;
|
|
2394
|
+
return {
|
|
2395
|
+
// null — busy% ещё не измерена (первый тик демона: она дельта тиков CPU). Ноль вместо
|
|
2396
|
+
// неё сказал бы «процессор простаивает» ровно тогда, когда мы про него не знаем.
|
|
2397
|
+
cpuBusyPct: r.cpuBusy == null ? null : Math.round(r.cpuBusy * 100),
|
|
2398
|
+
memFreeGb: gb(r.freeGb),
|
|
2399
|
+
memTotalGb: gb(r.totalGb),
|
|
2400
|
+
diskFreeGb: r.disk ? gb(r.disk.freeGb) : null,
|
|
2401
|
+
diskTotalGb: r.disk ? gb(r.disk.totalGb) : null,
|
|
2402
|
+
measuredAt
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
function hostResourcesForProd() {
|
|
2406
|
+
return resourcesForProd(hostReading(), (/* @__PURE__ */ new Date()).toISOString());
|
|
2407
|
+
}
|
|
2250
2408
|
function hostResourceSnapshot() {
|
|
2251
2409
|
const r = hostReading();
|
|
2252
2410
|
const reason = gateReason(r.load1, r.cpus, r.freeGb, r.fdLoadRatio, void 0, void 0, void 0, r.cpuBusy);
|
|
@@ -2261,13 +2419,17 @@ function hostResourceSnapshot() {
|
|
|
2261
2419
|
thresholds: { maxLoadFactor: SWARM_MAX_LOAD_FACTOR, minFreeGb: SWARM_MIN_FREE_GB, maxFdLoadFactor: SWARM_MAX_FD_LOAD_FACTOR, maxCpuBusy: SWARM_MAX_CPU_BUSY }
|
|
2262
2420
|
};
|
|
2263
2421
|
}
|
|
2264
|
-
var
|
|
2422
|
+
var cpuWindowBase, cpuBusyValue, cpuHighSince, cpuSanityWarnAt, CPU_SANITY_WARN_EVERY_MS, RESOURCES_CACHE_MS, readingCache;
|
|
2265
2423
|
var init_resources = __esm({
|
|
2266
2424
|
"src/resources.ts"() {
|
|
2267
2425
|
"use strict";
|
|
2268
2426
|
init_quietExec();
|
|
2269
2427
|
init_config();
|
|
2270
|
-
|
|
2428
|
+
cpuWindowBase = null;
|
|
2429
|
+
cpuBusyValue = null;
|
|
2430
|
+
cpuHighSince = null;
|
|
2431
|
+
cpuSanityWarnAt = 0;
|
|
2432
|
+
CPU_SANITY_WARN_EVERY_MS = 5 * 6e4;
|
|
2271
2433
|
RESOURCES_CACHE_MS = 250;
|
|
2272
2434
|
readingCache = null;
|
|
2273
2435
|
}
|
|
@@ -4503,6 +4665,10 @@ function rowToRun(row) {
|
|
|
4503
4665
|
area: row.area ?? null,
|
|
4504
4666
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
4505
4667
|
model: row.model ?? null,
|
|
4668
|
+
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
4669
|
+
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
4670
|
+
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
4671
|
+
effort: toEffortLevel(row.effort),
|
|
4506
4672
|
cost_usd: row.cost_usd ?? 0,
|
|
4507
4673
|
// Токены/время работы (TASWM-353) — старые прогоны без колонок → 0.
|
|
4508
4674
|
tokens_in: row.tokens_in ?? 0,
|
|
@@ -4572,6 +4738,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4572
4738
|
area TEXT,
|
|
4573
4739
|
depends_on TEXT,
|
|
4574
4740
|
model TEXT,
|
|
4741
|
+
effort TEXT,
|
|
4575
4742
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
4576
4743
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
4577
4744
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
@@ -4673,7 +4840,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4673
4840
|
// Чем обошёлся прогон, кроме денег (TASWM-353): токены вход/выход и время работы.
|
|
4674
4841
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
4675
4842
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
4676
|
-
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0"
|
|
4843
|
+
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
4844
|
+
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
4845
|
+
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
4846
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
4677
4847
|
]) {
|
|
4678
4848
|
try {
|
|
4679
4849
|
this.db.exec(alter);
|
|
@@ -4693,7 +4863,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4693
4863
|
createRun(input) {
|
|
4694
4864
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
4695
4865
|
const res = this.db.prepare(
|
|
4696
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4866
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4697
4867
|
).run(
|
|
4698
4868
|
input.task_slug,
|
|
4699
4869
|
name,
|
|
@@ -4706,6 +4876,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4706
4876
|
input.area ?? null,
|
|
4707
4877
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
4708
4878
|
input.model ?? null,
|
|
4879
|
+
input.effort ?? null,
|
|
4880
|
+
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
4709
4881
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
4710
4882
|
input.resolves_run_id ?? null,
|
|
4711
4883
|
input.reviews_run_id ?? null,
|
|
@@ -5630,6 +5802,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5630
5802
|
// предикат «репо есть локально» для решения, брать ли свободный прогон.
|
|
5631
5803
|
host;
|
|
5632
5804
|
repoExists;
|
|
5805
|
+
// TASWM-398: ЧЕМ эта машина исполняет прогоны — id адаптера (claude-code|codex).
|
|
5806
|
+
executorId;
|
|
5633
5807
|
// Зеркало.
|
|
5634
5808
|
runs = /* @__PURE__ */ new Map();
|
|
5635
5809
|
runtime = /* @__PURE__ */ new Map();
|
|
@@ -5707,6 +5881,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5707
5881
|
this.host = opts.host ?? null;
|
|
5708
5882
|
this.repoExists = opts.repoExists ?? existsSync5;
|
|
5709
5883
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
5884
|
+
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
5710
5885
|
this.outboxPath = opts.outboxPath;
|
|
5711
5886
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
5712
5887
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
@@ -5820,8 +5995,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5820
5995
|
runFields(patch) {
|
|
5821
5996
|
const out = {};
|
|
5822
5997
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
5998
|
+
if (patch.status === "running") out.executor = this.executorId;
|
|
5823
5999
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
5824
6000
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
6001
|
+
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
5825
6002
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
5826
6003
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
5827
6004
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
@@ -5892,6 +6069,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
5892
6069
|
area: input.area ?? null,
|
|
5893
6070
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
5894
6071
|
model: input.model ?? null,
|
|
6072
|
+
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
6073
|
+
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
6074
|
+
effort: input.effort ?? null,
|
|
5895
6075
|
cost_usd: 0,
|
|
5896
6076
|
tokens_in: 0,
|
|
5897
6077
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
@@ -5944,6 +6124,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5944
6124
|
status: "queued",
|
|
5945
6125
|
area: d.area,
|
|
5946
6126
|
model: d.model,
|
|
6127
|
+
effort: d.effort,
|
|
6128
|
+
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
5947
6129
|
name: d.name,
|
|
5948
6130
|
repo: d.repo,
|
|
5949
6131
|
depends_on: d.depends_on,
|
|
@@ -6721,11 +6903,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
6721
6903
|
const n = obj?.maxDrones;
|
|
6722
6904
|
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
6723
6905
|
const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
6906
|
+
const cost = obj?.swarmSettings?.maxCostUsd;
|
|
6724
6907
|
return {
|
|
6725
6908
|
maxDrones: Math.trunc(n),
|
|
6726
6909
|
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
6727
6910
|
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
6728
|
-
models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) }
|
|
6911
|
+
models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) },
|
|
6912
|
+
// TASWM-395: уровни усилия — из того же ответа и с той же семантикой, что модели:
|
|
6913
|
+
// старый прод поля не отдаёт → оба null, то есть env машины и дефолт харнесса.
|
|
6914
|
+
// toEffortLevel заодно отсекает мусор, записанный в uiPrefs мимо схемы.
|
|
6915
|
+
efforts: {
|
|
6916
|
+
drone: toEffortLevel(obj?.swarmSettings?.droneEffort),
|
|
6917
|
+
reviewer: toEffortLevel(obj?.swarmSettings?.reviewerEffort)
|
|
6918
|
+
},
|
|
6919
|
+
// TASWM-377: потолок трат — из того же ответа, тем же одним чтением. НОЛЬ здесь
|
|
6920
|
+
// значим («гарантия выключена осознанно»), поэтому пропускаем и его, а не только
|
|
6921
|
+
// положительные: null остаётся ровно за «ручка не задана / старый прод», и только
|
|
6922
|
+
// он уводит спавн на env-фолбэк и дефолт ядра.
|
|
6923
|
+
maxCostUsd: typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null
|
|
6729
6924
|
};
|
|
6730
6925
|
}
|
|
6731
6926
|
/**
|
|
@@ -6864,19 +7059,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
6864
7059
|
if (res?.isError) return [];
|
|
6865
7060
|
const raw = (res.structuredContent ?? {}).hosts;
|
|
6866
7061
|
if (!Array.isArray(raw)) return [];
|
|
6867
|
-
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) =>
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
7062
|
+
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) => {
|
|
7063
|
+
const resources = parseHostResources(h.resources);
|
|
7064
|
+
return {
|
|
7065
|
+
hostId: h.hostId,
|
|
7066
|
+
hostName: typeof h.hostName === "string" && h.hostName ? h.hostName : h.hostId,
|
|
7067
|
+
lastSeenAt: typeof h.lastSeenAt === "string" ? h.lastSeenAt : null,
|
|
7068
|
+
// TASWM-298: репозитории машины — «на связи» ещё не значит «возьмёт эту задачу».
|
|
7069
|
+
// Прод старой версии поля не пришлёт → пустой список, строка просто без хвоста.
|
|
7070
|
+
reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : [],
|
|
7071
|
+
// TASWM-326: машина на паузе — «на связи, но работу не берёт». Прод старой
|
|
7072
|
+
// версии поля не пришлёт → undefined, строка выглядит как раньше.
|
|
7073
|
+
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
7074
|
+
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
7075
|
+
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
7076
|
+
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
7077
|
+
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
7078
|
+
...resources ? { resources } : {}
|
|
7079
|
+
};
|
|
7080
|
+
});
|
|
6880
7081
|
}
|
|
6881
7082
|
/**
|
|
6882
7083
|
* TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
|
|
@@ -6942,13 +7143,22 @@ var SwarmClient = class _SwarmClient {
|
|
|
6942
7143
|
* разное. Не знаем своего потолка (ещё не читали / sqlite) → ключ не шлём, прод не
|
|
6943
7144
|
* трогает прошлое значение — та же логика, что у repoFullNames.
|
|
6944
7145
|
*
|
|
7146
|
+
* TASWM-394: тем же пульсом уезжают РЕСУРСЫ машины (`resources` — процессор, память,
|
|
7147
|
+
* диск и время замера). Это не новое измерение, а ровно то, что демон УЖЕ посчитал для
|
|
7148
|
+
* своего гейта спавна: числа в интерфейсе обязаны совпадать с теми, по которым он
|
|
7149
|
+
* отказывается поднимать дрона. Без них «машина на связи, свободна» и «машина физически
|
|
7150
|
+
* не может поднять дрона» выглядят в проде одинаково — 12.08 это стоило полудня
|
|
7151
|
+
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
7152
|
+
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
7153
|
+
*
|
|
6945
7154
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6946
7155
|
*/
|
|
6947
|
-
async heartbeat(daemonVersion, pause, capacity) {
|
|
7156
|
+
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
6948
7157
|
try {
|
|
6949
7158
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6950
7159
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
6951
7160
|
if (capacity != null && Number.isFinite(capacity) && capacity > 0) args.capacity = Math.trunc(capacity);
|
|
7161
|
+
if (resources) args.resources = resources;
|
|
6952
7162
|
if (pause !== void 0) {
|
|
6953
7163
|
args.paused = !!pause?.paused;
|
|
6954
7164
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
@@ -7155,6 +7365,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7155
7365
|
area: null,
|
|
7156
7366
|
depends_on: [],
|
|
7157
7367
|
model: null,
|
|
7368
|
+
effort: null,
|
|
7369
|
+
// усилие рассуждения (TASWM-395) — прод-поле, заполняется ниже из ответа
|
|
7158
7370
|
cost_usd: 0,
|
|
7159
7371
|
tokens_in: 0,
|
|
7160
7372
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
@@ -7223,6 +7435,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7223
7435
|
local.area = pr.area ?? null;
|
|
7224
7436
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
7225
7437
|
local.model = pr.model ?? null;
|
|
7438
|
+
local.effort = toEffortLevel(pr.effort);
|
|
7226
7439
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
7227
7440
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
7228
7441
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
@@ -8218,17 +8431,25 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8218
8431
|
}
|
|
8219
8432
|
|
|
8220
8433
|
// src/swarmModels.ts
|
|
8434
|
+
init_src();
|
|
8435
|
+
function pick(v) {
|
|
8436
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8437
|
+
return s.length > 0 ? s : null;
|
|
8438
|
+
}
|
|
8439
|
+
function forKind(kind, swarm, norm) {
|
|
8440
|
+
if (!swarm) return null;
|
|
8441
|
+
return kind === "reviewer" ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
8442
|
+
}
|
|
8221
8443
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
const fromSwarm = run.kind === "reviewer" ? pick2(swarm?.reviewer) ?? pick2(swarm?.drone) : pick2(swarm?.drone);
|
|
8227
|
-
return pick2(run.model) ?? fromSwarm ?? pick2(env.SWARM_DRONE_MODEL) ?? null;
|
|
8444
|
+
return pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8445
|
+
}
|
|
8446
|
+
function resolveRunEffort(run, swarm, env = process.env) {
|
|
8447
|
+
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
8228
8448
|
}
|
|
8229
8449
|
|
|
8230
8450
|
// src/launcher.ts
|
|
8231
8451
|
init_executors();
|
|
8452
|
+
init_src();
|
|
8232
8453
|
var requireFromHere = createRequire(import.meta.url);
|
|
8233
8454
|
function resolveTsxCli() {
|
|
8234
8455
|
try {
|
|
@@ -8240,6 +8461,15 @@ function resolveTsxCli() {
|
|
|
8240
8461
|
}
|
|
8241
8462
|
}
|
|
8242
8463
|
var MCP_SERVER_NAME = "overmind";
|
|
8464
|
+
function launchBudgetUsd(limit, spentUsd, env = process.env) {
|
|
8465
|
+
return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
|
|
8466
|
+
}
|
|
8467
|
+
function envCapUsd(env) {
|
|
8468
|
+
const raw = env.SWARM_MAX_COST_USD;
|
|
8469
|
+
if (raw === void 0 || raw === "") return DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8470
|
+
const n = Number(raw);
|
|
8471
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8472
|
+
}
|
|
8243
8473
|
function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
8244
8474
|
const env = { ...base3 };
|
|
8245
8475
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
@@ -8365,6 +8595,12 @@ function makeLauncher(cfg) {
|
|
|
8365
8595
|
}
|
|
8366
8596
|
};
|
|
8367
8597
|
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
8598
|
+
const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
|
|
8599
|
+
try {
|
|
8600
|
+
cfg.recordEffort?.(run.id, effort);
|
|
8601
|
+
} catch (e) {
|
|
8602
|
+
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)}`);
|
|
8603
|
+
}
|
|
8368
8604
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8369
8605
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8370
8606
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8383,7 +8619,14 @@ function makeLauncher(cfg) {
|
|
|
8383
8619
|
// Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
|
|
8384
8620
|
// сидит в HOME демона, и запирать половину дверей — самообман.
|
|
8385
8621
|
denyPaths: cfg.droneIsolation ? dronePrivatePaths(realHome, cfg.droneIsolation.swarmHome) : [],
|
|
8386
|
-
realHome
|
|
8622
|
+
realHome,
|
|
8623
|
+
// TASWM-377: остаток бюджета ЭТОГО прогона — харнессу, чтобы он резал трату у себя, в
|
|
8624
|
+
// момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
|
|
8625
|
+
// стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
|
|
8626
|
+
// ни жёг, — отсюда и $74 при лимите $20.
|
|
8627
|
+
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
|
|
8628
|
+
// TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
|
|
8629
|
+
effort
|
|
8387
8630
|
});
|
|
8388
8631
|
let stdio = "ignore";
|
|
8389
8632
|
let outFd;
|
|
@@ -8482,6 +8725,9 @@ function makeCostAccountant(deps) {
|
|
|
8482
8725
|
init_executors();
|
|
8483
8726
|
import { readdirSync as readdirSync9, readFileSync as readFileSync11 } from "fs";
|
|
8484
8727
|
import { join as join14 } from "path";
|
|
8728
|
+
function endedItself(outcome) {
|
|
8729
|
+
return outcome === "graceful" || outcome === "budget";
|
|
8730
|
+
}
|
|
8485
8731
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
8486
8732
|
function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
8487
8733
|
const dir = join14(runLogsDir, String(runId));
|
|
@@ -8505,7 +8751,9 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
8505
8751
|
} catch {
|
|
8506
8752
|
return "unknown";
|
|
8507
8753
|
}
|
|
8508
|
-
|
|
8754
|
+
const result = executor.parseResult(raw, null);
|
|
8755
|
+
if (!result) return "crash";
|
|
8756
|
+
return result.stoppedByBudget ? "budget" : "graceful";
|
|
8509
8757
|
}
|
|
8510
8758
|
function makeExitClassifier(runLogsDir, executor) {
|
|
8511
8759
|
const exec = executor ?? resolveExecutor();
|
|
@@ -9207,6 +9455,9 @@ async function mergedPrForBranch(branch, repoDir, run = realRepoRunner) {
|
|
|
9207
9455
|
async function closePR(prUrl, run = realRunner) {
|
|
9208
9456
|
await run(["pr", "close", prUrl, "--comment", "\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D \u043D\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 (swarm)"]);
|
|
9209
9457
|
}
|
|
9458
|
+
async function postPrComment(prUrl, body, run = realRunner) {
|
|
9459
|
+
await run(["pr", "comment", prUrl, "--body", body]);
|
|
9460
|
+
}
|
|
9210
9461
|
var realCommentsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json comments`).stdout;
|
|
9211
9462
|
var VERDICT_RE = /^[^A-Za-z]*(APPROVED?|LGTM|(?:REQUEST(?:ED)?\s+)?CHANGES(?:\s+REQUESTED)?)\b/i;
|
|
9212
9463
|
var APPROVE_RE = /^(APPROVE|LGTM)/i;
|
|
@@ -11113,7 +11364,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
11113
11364
|
// package.json
|
|
11114
11365
|
var package_default = {
|
|
11115
11366
|
name: "@taskless-app/swarm",
|
|
11116
|
-
version: "0.6.
|
|
11367
|
+
version: "0.6.7",
|
|
11117
11368
|
type: "module",
|
|
11118
11369
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11119
11370
|
keywords: [
|
|
@@ -11163,7 +11414,7 @@ var package_default = {
|
|
|
11163
11414
|
execa: "^10.0.0",
|
|
11164
11415
|
zod: "3.25.76"
|
|
11165
11416
|
},
|
|
11166
|
-
gitHead: "
|
|
11417
|
+
gitHead: "08ee2a1a4bdb53ba1c25c92066104f0de3a23433"
|
|
11167
11418
|
};
|
|
11168
11419
|
|
|
11169
11420
|
// src/version.ts
|
|
@@ -11386,7 +11637,8 @@ function timelineState(db, now = Date.now(), overmindLog = []) {
|
|
|
11386
11637
|
heroRunId: r.hero_run_id,
|
|
11387
11638
|
reviewVerdict: r.review_verdict,
|
|
11388
11639
|
reviewPhase: r.review_phase,
|
|
11389
|
-
model: r.model
|
|
11640
|
+
model: r.model,
|
|
11641
|
+
effort: r.effort
|
|
11390
11642
|
});
|
|
11391
11643
|
}
|
|
11392
11644
|
const liveLanes = lanes.filter((l) => l.live);
|
|
@@ -13160,9 +13412,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
13160
13412
|
)
|
|
13161
13413
|
);
|
|
13162
13414
|
return {
|
|
13163
|
-
since:
|
|
13164
|
-
lastFiredAt:
|
|
13165
|
-
fires:
|
|
13415
|
+
since: pick2(since, fresh),
|
|
13416
|
+
lastFiredAt: pick2(lastFiredAt, fresh),
|
|
13417
|
+
fires: pick2(fires, fresh),
|
|
13166
13418
|
// TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
|
|
13167
13419
|
// уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
|
|
13168
13420
|
// недоставленным, гашение не становится лишним — оно становится нужнее.
|
|
@@ -13246,7 +13498,7 @@ function strings(raw) {
|
|
|
13246
13498
|
if (!Array.isArray(raw)) return [];
|
|
13247
13499
|
return raw.filter((v) => typeof v === "string" && v.length > 0);
|
|
13248
13500
|
}
|
|
13249
|
-
function
|
|
13501
|
+
function pick2(src, keys) {
|
|
13250
13502
|
const out = {};
|
|
13251
13503
|
for (const k of keys) if (src[k] != null) out[k] = src[k];
|
|
13252
13504
|
return out;
|
|
@@ -13644,13 +13896,20 @@ var CHANGES_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CHANGES_WAKE_COOLDOWN_MS
|
|
|
13644
13896
|
var CHANGES_WAKE_LIMIT = Number(process.env.SWARM_CHANGES_WAKE_LIMIT) || 3;
|
|
13645
13897
|
var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB";
|
|
13646
13898
|
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";
|
|
13899
|
+
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";
|
|
13900
|
+
var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON];
|
|
13901
|
+
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";
|
|
13902
|
+
function reviewBudgetMark(cycle) {
|
|
13903
|
+
return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
|
|
13904
|
+
}
|
|
13647
13905
|
var NEEDS_HUMAN_MARKS = [
|
|
13648
13906
|
RESOLVE_ESCALATION_MARK,
|
|
13649
13907
|
REVIEW_ESCALATION_MARK,
|
|
13650
13908
|
REVIEW_UNRECOVERABLE_MARK,
|
|
13651
13909
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
13652
13910
|
CI_WAKE_ESCALATION_MARK,
|
|
13653
|
-
CHANGES_WAKE_ESCALATION_MARK
|
|
13911
|
+
CHANGES_WAKE_ESCALATION_MARK,
|
|
13912
|
+
REVIEW_BUDGET_MARK
|
|
13654
13913
|
];
|
|
13655
13914
|
var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
|
|
13656
13915
|
var REVIEW_NO_VERDICT_REASON = "\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430: \u043D\u0438 approve, \u043D\u0438 changes \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434 (TASWM-375)";
|
|
@@ -14032,6 +14291,12 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
14032
14291
|
`\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
|
|
14033
14292
|
"\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
|
|
14034
14293
|
"\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).",
|
|
14294
|
+
// TASWM-377: у сессии есть ПОТОЛОК ТРАТ, и он режет её без предупреждения, прямо посреди
|
|
14295
|
+
// работы. Дрон об этом знать обязан: цена сессии растёт от ЧИСЛА ХОДОВ (каждый ход заново
|
|
14296
|
+
// оплачивает весь накопленный контекст), поэтому «просканирую репозиторий, чтобы вникнуть»
|
|
14297
|
+
// — это не тщательность, а способ не доехать до PR. Замер по прогонам роя: дорогие сессии
|
|
14298
|
+
// отличаются от дешёвых числом заходов, а не размером задачи.
|
|
14299
|
+
"\u{1F4B8} \u0421\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u041F\u041E\u0422\u041E\u041B\u041A\u041E\u041C \u0422\u0420\u0410\u0422: \u0443\u043F\u0440\u0451\u0448\u044C\u0441\u044F \u2014 \u0442\u0435\u0431\u044F \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u044F\u0442 \u043F\u043E\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043E\u0442\u044B, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u043F\u0440\u043E\u043F\u0430\u0434\u0451\u0442, \u0430 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0443\u0434\u0435\u0442 \u0434\u043E\u0434\u0435\u043B\u044B\u0432\u0430\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A. \u0427\u0438\u0442\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043D\u0443\u0436\u043D\u043E \u043F\u0440\u0430\u0432\u043A\u0435 (\u043A\u0430\u0440\u0442\u0430 \u043A\u043E\u0434\u0430\u0431\u0430\u0437\u044B + \u0437\u0430\u0442\u0440\u043E\u043D\u0443\u0442\u044B\u0435 \u0444\u0430\u0439\u043B\u044B), \u0430 \u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0446\u0435\u043B\u0438\u043A\u043E\u043C; \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u044B\u0432\u0430\u0439; \u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043F\u0443\u0448\u044C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0438\u043C\u043E\u0433\u043E \u0448\u0430\u0433\u0430 \u2014 \u0442\u043E\u0433\u0434\u0430 \u0434\u0430\u0436\u0435 \u043E\u0431\u0440\u044B\u0432 \u043F\u043E \u0431\u044E\u0434\u0436\u0435\u0442\u0443 \u043E\u0441\u0442\u0430\u0432\u0438\u0442 \u0440\u0430\u0431\u043E\u0442\u0443 \u0432 origin.",
|
|
14035
14300
|
// TASWM-293: живой случай SCE-5 — ревьюер заблокировал PR за отсутствие CI (у проекта
|
|
14036
14301
|
// его нет и не предполагается), автора разбудили «закрывать замечания», замечаний по коду
|
|
14037
14302
|
// не было, и дрон завёл в ЧУЖОМ репозитории .github/workflows/check.yml, чтобы пройти
|
|
@@ -14175,6 +14440,13 @@ function lastChangesSummary(db, reviewRunId) {
|
|
|
14175
14440
|
}
|
|
14176
14441
|
return null;
|
|
14177
14442
|
}
|
|
14443
|
+
var REVIEW_SCOPE_BLOCK = [
|
|
14444
|
+
"\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:",
|
|
14445
|
+
"\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.",
|
|
14446
|
+
"\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.",
|
|
14447
|
+
"\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.",
|
|
14448
|
+
"\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."
|
|
14449
|
+
].join("\n");
|
|
14178
14450
|
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
14179
14451
|
const name = reviewer.name || `#${reviewer.id}`;
|
|
14180
14452
|
const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
|
|
@@ -14192,6 +14464,8 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
14192
14464
|
] : [],
|
|
14193
14465
|
"",
|
|
14194
14466
|
"\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.",
|
|
14467
|
+
"",
|
|
14468
|
+
REVIEW_SCOPE_BLOCK,
|
|
14195
14469
|
// TASWM-283: ревьюер читает ТУ ЖЕ задачу и так же слеп к её картинкам — без макета
|
|
14196
14470
|
// «сделано ли как просили» он проверить не может, а UI-пункт чек-листа держится
|
|
14197
14471
|
// ровно на этом. Кладём ему тот же каталог вложений, что и исполнителю.
|
|
@@ -15185,6 +15459,67 @@ async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
|
15185
15459
|
}
|
|
15186
15460
|
return true;
|
|
15187
15461
|
}
|
|
15462
|
+
async function failReviewerRun(deps, reviewer, reviewsRunId, opts) {
|
|
15463
|
+
if (!opts.keepWorktree && reviewer.worktree_path) {
|
|
15464
|
+
await deps.removeWorktree(reviewer.repo, reviewer.worktree_path).catch(() => {
|
|
15465
|
+
});
|
|
15466
|
+
}
|
|
15467
|
+
deps.db.updateRun(reviewer.id, { status: "failed", stop_reason: opts.reason });
|
|
15468
|
+
deps.db.addEvent(reviewer.id, "failed", opts.reason);
|
|
15469
|
+
deps.db.addEvent(reviewsRunId, "step", opts.noteForReviewed);
|
|
15470
|
+
narrate(deps, opts.narration);
|
|
15471
|
+
}
|
|
15472
|
+
async function handleBudgetExhausted(deps, run, ctx) {
|
|
15473
|
+
const spentUsd = deps.db.getRun(run.id)?.cost_usd ?? run.cost_usd;
|
|
15474
|
+
const spent = `$${spentUsd.toFixed(2)}`;
|
|
15475
|
+
const reason = `${BUDGET_STOP_REASON} (\u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E ${spent})`;
|
|
15476
|
+
if (run.reviews_run_id != null) {
|
|
15477
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15478
|
+
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})`,
|
|
15479
|
+
now: ctx.now,
|
|
15480
|
+
droneCap: ctx.droneCap,
|
|
15481
|
+
stormActive: ctx.stormActive
|
|
15482
|
+
});
|
|
15483
|
+
if (salvaged) return;
|
|
15484
|
+
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15485
|
+
const cycle = reviewed?.review_cycle ?? 0;
|
|
15486
|
+
const refusal = [
|
|
15487
|
+
`\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}).`,
|
|
15488
|
+
"",
|
|
15489
|
+
"\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.",
|
|
15490
|
+
"\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.",
|
|
15491
|
+
"",
|
|
15492
|
+
"\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."
|
|
15493
|
+
].join("\n");
|
|
15494
|
+
await Promise.all([
|
|
15495
|
+
run.pr_url && deps.prComment ? deps.prComment(run.pr_url, refusal).catch((e) => {
|
|
15496
|
+
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)}`);
|
|
15497
|
+
}) : Promise.resolve(),
|
|
15498
|
+
run.worktree_path ? deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15499
|
+
}) : Promise.resolve()
|
|
15500
|
+
]);
|
|
15501
|
+
await failReviewerRun(deps, run, run.reviews_run_id, {
|
|
15502
|
+
reason,
|
|
15503
|
+
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`,
|
|
15504
|
+
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.`,
|
|
15505
|
+
// worktree уже снесён выше — параллельно с отказом в PR.
|
|
15506
|
+
keepWorktree: true
|
|
15507
|
+
});
|
|
15508
|
+
return;
|
|
15509
|
+
}
|
|
15510
|
+
if (run.resolves_run_id != null) {
|
|
15511
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15512
|
+
});
|
|
15513
|
+
deps.db.updateRun(run.id, { status: "failed", stop_reason: reason });
|
|
15514
|
+
deps.db.addEvent(run.id, "failed", reason);
|
|
15515
|
+
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`);
|
|
15516
|
+
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.`);
|
|
15517
|
+
return;
|
|
15518
|
+
}
|
|
15519
|
+
deps.db.updateRun(run.id, { status: "paused", stop_reason: reason });
|
|
15520
|
+
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`);
|
|
15521
|
+
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.`);
|
|
15522
|
+
}
|
|
15188
15523
|
function buildEscalationCandidates(db) {
|
|
15189
15524
|
const candidates = [];
|
|
15190
15525
|
for (const run of db.listRuns("review")) {
|
|
@@ -15208,6 +15543,7 @@ async function tickOnce(deps) {
|
|
|
15208
15543
|
const now = deps.now ? deps.now() : Date.now();
|
|
15209
15544
|
const worktreeExists = deps.worktreeExists ?? existsSync16;
|
|
15210
15545
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
15546
|
+
deps.sampleHost?.();
|
|
15211
15547
|
if (deps.rehydrate) {
|
|
15212
15548
|
const rh = deps.rehydrate;
|
|
15213
15549
|
const sinceLast = rh.lastAt == null ? Infinity : now - rh.lastAt;
|
|
@@ -15319,7 +15655,7 @@ async function tickOnce(deps) {
|
|
|
15319
15655
|
continue;
|
|
15320
15656
|
}
|
|
15321
15657
|
}
|
|
15322
|
-
if (deps.exitOutcome && deps.exitOutcome(run)
|
|
15658
|
+
if (deps.exitOutcome && endedItself(deps.exitOutcome(run))) continue;
|
|
15323
15659
|
deps.db.recordDeath(run.id, `\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440 (pid ${run.pid} \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442)`);
|
|
15324
15660
|
}
|
|
15325
15661
|
stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
|
|
@@ -15354,12 +15690,11 @@ async function tickOnce(deps) {
|
|
|
15354
15690
|
stormActive
|
|
15355
15691
|
});
|
|
15356
15692
|
if (salvaged) continue;
|
|
15357
|
-
|
|
15693
|
+
await failReviewerRun(deps, run, run.reviews_run_id, {
|
|
15694
|
+
reason: REVIEW_NO_VERDICT_REASON,
|
|
15695
|
+
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`,
|
|
15696
|
+
narration: `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0439: \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`
|
|
15358
15697
|
});
|
|
15359
|
-
deps.db.updateRun(run.id, { status: "failed", stop_reason: REVIEW_NO_VERDICT_REASON });
|
|
15360
|
-
deps.db.addEvent(run.id, "failed", REVIEW_NO_VERDICT_REASON);
|
|
15361
|
-
deps.db.addEvent(run.reviews_run_id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${run.name} (#${run.id}) \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u043E\u0441\u044C, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`);
|
|
15362
|
-
narrate(deps, `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0439: \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
|
|
15363
15698
|
continue;
|
|
15364
15699
|
}
|
|
15365
15700
|
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
@@ -15448,6 +15783,8 @@ async function tickOnce(deps) {
|
|
|
15448
15783
|
const attempts = deps.db.reviewerAttemptCountFor(run.id);
|
|
15449
15784
|
const runEvents = deps.db.events(run.id);
|
|
15450
15785
|
const unrecoverableMark = reviewUnrecoverableMark(run.branch || run.task_slug);
|
|
15786
|
+
const budgetMark = reviewBudgetMark(run.review_cycle ?? 0);
|
|
15787
|
+
const LATCHES = [unrecoverableMark, budgetMark];
|
|
15451
15788
|
let verdictFromComment = null;
|
|
15452
15789
|
let lastAttempt = null;
|
|
15453
15790
|
if (attempts > 0 && deps.verdictComment) {
|
|
@@ -15467,7 +15804,7 @@ async function tickOnce(deps) {
|
|
|
15467
15804
|
await wakeAuthorOnChanges(deps, run, now, droneCap);
|
|
15468
15805
|
}
|
|
15469
15806
|
}
|
|
15470
|
-
} else if (runEvents.some((e) => (e.text ?? "").includes(
|
|
15807
|
+
} else if (runEvents.some((e) => LATCHES.some((mark) => (e.text ?? "").includes(mark)))) {
|
|
15471
15808
|
} else if (attempts >= REVIEW_SPAWN_LIMIT) {
|
|
15472
15809
|
if (!runEvents.some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_ESCALATION_MARK))) {
|
|
15473
15810
|
deps.db.addEvent(run.id, "step", `${REVIEW_ESCALATION_MARK}: ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u0440\u0435\u0432\u044C\u044E \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
|
|
@@ -15550,6 +15887,10 @@ async function tickOnce(deps) {
|
|
|
15550
15887
|
}
|
|
15551
15888
|
continue;
|
|
15552
15889
|
}
|
|
15890
|
+
if (!alive && outcome === "budget") {
|
|
15891
|
+
await handleBudgetExhausted(deps, run, { now, droneCap, stormActive });
|
|
15892
|
+
continue;
|
|
15893
|
+
}
|
|
15553
15894
|
if (!alive && run.kind === "hero") {
|
|
15554
15895
|
const young = now - lastActivityMs(deps.db, run) < RESPAWN_YOUNG_MS;
|
|
15555
15896
|
if (young && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -15707,7 +16048,7 @@ async function tickOnce(deps) {
|
|
|
15707
16048
|
if (run.kind === "hero") continue;
|
|
15708
16049
|
if (run.reviews_run_id != null) continue;
|
|
15709
16050
|
if (run.stop_reason === RELIEF_PAUSE_REASON) continue;
|
|
15710
|
-
if (
|
|
16051
|
+
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
15711
16052
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
15712
16053
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
15713
16054
|
const wt = run.worktree_path;
|
|
@@ -16194,6 +16535,8 @@ async function runDaemon(opts) {
|
|
|
16194
16535
|
} : void 0;
|
|
16195
16536
|
let lastDroneCap = null;
|
|
16196
16537
|
let lastSwarmModels = null;
|
|
16538
|
+
let lastSwarmEfforts = null;
|
|
16539
|
+
let lastMaxCostUsd = null;
|
|
16197
16540
|
let lastOwnCapacity = null;
|
|
16198
16541
|
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
16199
16542
|
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
@@ -16207,7 +16550,15 @@ async function runDaemon(opts) {
|
|
|
16207
16550
|
droneIsolation: DRONE_ISOLATION,
|
|
16208
16551
|
channel: droneChannelLaunch,
|
|
16209
16552
|
droneCap: () => lastDroneCap,
|
|
16210
|
-
swarmModels: () => lastSwarmModels
|
|
16553
|
+
swarmModels: () => lastSwarmModels,
|
|
16554
|
+
swarmEfforts: () => lastSwarmEfforts,
|
|
16555
|
+
// TASWM-395: разрешённый уровень усилия впечатывается в прогон на спавне — иначе в учёте
|
|
16556
|
+
// виден только пин (то есть null у всех, кто шёл на настройке роя). Оттуда же его берут
|
|
16557
|
+
// респавн и `--continue`: прогон, начатый трудным, не должен подешеветь на середине.
|
|
16558
|
+
recordEffort: (runId, effort) => {
|
|
16559
|
+
db.updateRun(runId, { effort });
|
|
16560
|
+
},
|
|
16561
|
+
maxCostUsd: () => lastMaxCostUsd
|
|
16211
16562
|
});
|
|
16212
16563
|
let prepareReader;
|
|
16213
16564
|
const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
|
|
@@ -16253,7 +16604,7 @@ async function runDaemon(opts) {
|
|
|
16253
16604
|
const client = db;
|
|
16254
16605
|
heartbeat = {
|
|
16255
16606
|
push: async () => {
|
|
16256
|
-
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity);
|
|
16607
|
+
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity, hostResourcesForProd());
|
|
16257
16608
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
16258
16609
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
16259
16610
|
if (reply.logRequests.length) {
|
|
@@ -16348,7 +16699,11 @@ async function runDaemon(opts) {
|
|
|
16348
16699
|
// что и кап ниже: второго сетевого канала за ними заводить незачем.
|
|
16349
16700
|
fetch: async () => {
|
|
16350
16701
|
const settings = await client.ownerSwarmSettings();
|
|
16351
|
-
if (settings)
|
|
16702
|
+
if (settings) {
|
|
16703
|
+
lastSwarmModels = settings.models;
|
|
16704
|
+
lastSwarmEfforts = settings.efforts;
|
|
16705
|
+
lastMaxCostUsd = settings.maxCostUsd;
|
|
16706
|
+
}
|
|
16352
16707
|
return settings?.maxDrones ?? null;
|
|
16353
16708
|
},
|
|
16354
16709
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
@@ -16607,6 +16962,9 @@ async function runDaemon(opts) {
|
|
|
16607
16962
|
transcriptMtimeMs: (p) => EXECUTOR.session.mtimeMs(p),
|
|
16608
16963
|
// Ресурс-гейт спавна (TASWM-130): реальные показания хоста (load1 / free RAM).
|
|
16609
16964
|
resourcesOk: hostResourcesOk,
|
|
16965
|
+
// Замер занятости CPU раз в тик (TASWM-397): окно фиксированной длины набирает пульс
|
|
16966
|
+
// тика, гейт ниже только читает готовое показание.
|
|
16967
|
+
sampleHost: () => void sampleCpuBusy(),
|
|
16610
16968
|
// Ресурс-гейт v2 (TASWM-151): жёсткий порог активной разгрузки — те же сырые показания
|
|
16611
16969
|
// хоста (общий кэш в resources.ts), но заведомо строже настроенные пороги.
|
|
16612
16970
|
reliefNeeded: hostReliefNeeded,
|
|
@@ -16737,6 +17095,11 @@ async function runDaemon(opts) {
|
|
|
16737
17095
|
// Сторож прогресса роя (TASWM-270): в отличие от anomalies выше — заведён БЕЗУСЛОВНО
|
|
16738
17096
|
// (см. докблок конструирования watchdog выше — рунги а/б/в работают и без client-режима).
|
|
16739
17097
|
watchdog,
|
|
17098
|
+
// TASWM-377: отказ в PR за мёртвого (ревьюер, срезанный потолком трат). Заведён
|
|
17099
|
+
// БЕЗУСЛОВНО, в отличие от ключей авто-ревью ниже: разбирать уже поднятого ревьюера
|
|
17100
|
+
// приходится и после того, как авто-ревью выключили, — а молчать за него тем более
|
|
17101
|
+
// некому.
|
|
17102
|
+
prComment: postPrComment,
|
|
16740
17103
|
// Авто-ревью (TASWM-148): ключи есть только когда SWARM_AUTO_REVIEW включён (дефолт да) —
|
|
16741
17104
|
// spawnReviewerFn/verdictCommentFn/reviewCutoffAtValue выставляются В ОДНОМ if(SWARM_AUTO_REVIEW)
|
|
16742
17105
|
// блоке выше, поэтому один и тот же гейт на все три (без отдельных тернарных условий).
|
|
@@ -17062,6 +17425,7 @@ if (isEntrypoint) {
|
|
|
17062
17425
|
}
|
|
17063
17426
|
export {
|
|
17064
17427
|
AWAITING_CI_REASON,
|
|
17428
|
+
BUDGET_STOP_REASON,
|
|
17065
17429
|
CHANGES_WAKE_COOLDOWN_MS,
|
|
17066
17430
|
CHANGES_WAKE_ESCALATION_MARK,
|
|
17067
17431
|
CHANGES_WAKE_LIMIT,
|
|
@@ -17089,6 +17453,7 @@ export {
|
|
|
17089
17453
|
RESOLVE_LIMIT,
|
|
17090
17454
|
RESPAWN_LIMIT,
|
|
17091
17455
|
RESPAWN_YOUNG_MS,
|
|
17456
|
+
REVIEW_BUDGET_MARK,
|
|
17092
17457
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
17093
17458
|
REVIEW_CYCLE_LIMIT,
|
|
17094
17459
|
REVIEW_ESCALATION_MARK,
|
|
@@ -17118,6 +17483,7 @@ export {
|
|
|
17118
17483
|
repoHasCiWorkflows,
|
|
17119
17484
|
resolveConflict,
|
|
17120
17485
|
respawnRun,
|
|
17486
|
+
reviewBudgetMark,
|
|
17121
17487
|
reviewUnrecoverableMark,
|
|
17122
17488
|
reviveStuck,
|
|
17123
17489
|
runDaemon,
|