@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/cli.js
CHANGED
|
@@ -638,6 +638,52 @@ var init_hosts = __esm({
|
|
|
638
638
|
}
|
|
639
639
|
});
|
|
640
640
|
|
|
641
|
+
// ../swarm-core/src/hostResources.ts
|
|
642
|
+
function parseHostResources(raw) {
|
|
643
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
644
|
+
const v = raw;
|
|
645
|
+
const num = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
|
|
646
|
+
const memFreeGb = num(v.memFreeGb);
|
|
647
|
+
const memTotalGb = num(v.memTotalGb);
|
|
648
|
+
if (memFreeGb == null || memTotalGb == null) return null;
|
|
649
|
+
if (typeof v.measuredAt !== "string" || Number.isNaN(Date.parse(v.measuredAt))) return null;
|
|
650
|
+
const cpu = num(v.cpuBusyPct);
|
|
651
|
+
return {
|
|
652
|
+
cpuBusyPct: cpu == null ? null : Math.min(100, Math.max(0, cpu)),
|
|
653
|
+
memFreeGb,
|
|
654
|
+
memTotalGb,
|
|
655
|
+
diskFreeGb: num(v.diskFreeGb),
|
|
656
|
+
diskTotalGb: num(v.diskTotalGb),
|
|
657
|
+
measuredAt: v.measuredAt
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function hostResourcesFresh(resources, nowMs, staleMs = HOST_RESOURCES_STALE_MS) {
|
|
661
|
+
if (!resources) return false;
|
|
662
|
+
const at = Date.parse(resources.measuredAt);
|
|
663
|
+
if (Number.isNaN(at)) return false;
|
|
664
|
+
return nowMs - at <= staleMs;
|
|
665
|
+
}
|
|
666
|
+
function gb(value) {
|
|
667
|
+
return value < 10 ? value.toFixed(1) : String(Math.round(value));
|
|
668
|
+
}
|
|
669
|
+
function hostResourcesText(resources, nowMs = Date.now(), staleMs = HOST_RESOURCES_STALE_MS) {
|
|
670
|
+
if (!hostResourcesFresh(resources, nowMs, staleMs)) return null;
|
|
671
|
+
const r = resources;
|
|
672
|
+
const parts = [];
|
|
673
|
+
if (r.cpuBusyPct != null) parts.push(`CPU ${Math.round(r.cpuBusyPct)}%`);
|
|
674
|
+
parts.push(`\u043F\u0430\u043C\u044F\u0442\u044C ${gb(r.memFreeGb)} \u0438\u0437 ${gb(r.memTotalGb)} \u0413\u0411`);
|
|
675
|
+
if (r.diskFreeGb != null && r.diskTotalGb != null) parts.push(`\u0434\u0438\u0441\u043A ${gb(r.diskFreeGb)} \u0438\u0437 ${gb(r.diskTotalGb)} \u0413\u0411`);
|
|
676
|
+
return parts.join(" \xB7 ");
|
|
677
|
+
}
|
|
678
|
+
var HOST_RESOURCES_STALE_MS;
|
|
679
|
+
var init_hostResources = __esm({
|
|
680
|
+
"../swarm-core/src/hostResources.ts"() {
|
|
681
|
+
"use strict";
|
|
682
|
+
init_hosts();
|
|
683
|
+
HOST_RESOURCES_STALE_MS = HOST_HEARTBEAT_STALE_MS;
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
641
687
|
// ../swarm-core/src/hostPool.ts
|
|
642
688
|
var init_hostPool = __esm({
|
|
643
689
|
"../swarm-core/src/hostPool.ts"() {
|
|
@@ -690,7 +736,12 @@ var init_cost = __esm({
|
|
|
690
736
|
});
|
|
691
737
|
|
|
692
738
|
// ../swarm-core/src/runGuards.ts
|
|
693
|
-
|
|
739
|
+
function remainingRunBudgetUsd(maxCostUsd, spentUsd) {
|
|
740
|
+
if (!(maxCostUsd > 0)) return null;
|
|
741
|
+
const spent = Number.isFinite(spentUsd) && spentUsd > 0 ? spentUsd : 0;
|
|
742
|
+
return Math.max(MIN_RUN_BUDGET_USD, Math.round((maxCostUsd - spent) * 100) / 100);
|
|
743
|
+
}
|
|
744
|
+
var DEFAULT_RUN_LIMITS, MIN_RUN_BUDGET_USD;
|
|
694
745
|
var init_runGuards = __esm({
|
|
695
746
|
"../swarm-core/src/runGuards.ts"() {
|
|
696
747
|
"use strict";
|
|
@@ -700,6 +751,7 @@ var init_runGuards = __esm({
|
|
|
700
751
|
stepTimeoutMs: 90 * 6e4,
|
|
701
752
|
lostMs: 30 * 6e4
|
|
702
753
|
};
|
|
754
|
+
MIN_RUN_BUDGET_USD = 0.05;
|
|
703
755
|
}
|
|
704
756
|
});
|
|
705
757
|
|
|
@@ -1087,6 +1139,20 @@ var init_codeZones = __esm({
|
|
|
1087
1139
|
}
|
|
1088
1140
|
});
|
|
1089
1141
|
|
|
1142
|
+
// ../swarm-core/src/effort.ts
|
|
1143
|
+
function toEffortLevel(raw) {
|
|
1144
|
+
const s = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
1145
|
+
return EFFORT_SET.has(s) ? s : null;
|
|
1146
|
+
}
|
|
1147
|
+
var EFFORT_LEVELS, EFFORT_SET;
|
|
1148
|
+
var init_effort = __esm({
|
|
1149
|
+
"../swarm-core/src/effort.ts"() {
|
|
1150
|
+
"use strict";
|
|
1151
|
+
EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
1152
|
+
EFFORT_SET = new Set(EFFORT_LEVELS);
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1090
1156
|
// ../swarm-core/src/index.ts
|
|
1091
1157
|
var init_src = __esm({
|
|
1092
1158
|
"../swarm-core/src/index.ts"() {
|
|
@@ -1104,12 +1170,14 @@ var init_src = __esm({
|
|
|
1104
1170
|
init_names();
|
|
1105
1171
|
init_baseBranch();
|
|
1106
1172
|
init_hosts();
|
|
1173
|
+
init_hostResources();
|
|
1107
1174
|
init_hostPool();
|
|
1108
1175
|
init_hostCommands();
|
|
1109
1176
|
init_relief();
|
|
1110
1177
|
init_cost();
|
|
1111
1178
|
init_runGuards();
|
|
1112
1179
|
init_codeZones();
|
|
1180
|
+
init_effort();
|
|
1113
1181
|
}
|
|
1114
1182
|
});
|
|
1115
1183
|
|
|
@@ -1253,14 +1321,32 @@ function launchCostUsd(result, model) {
|
|
|
1253
1321
|
return costFromUsage(result.usage, model);
|
|
1254
1322
|
}
|
|
1255
1323
|
function launchSpend(result) {
|
|
1256
|
-
const u = result.usage ?? {};
|
|
1257
1324
|
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1258
|
-
|
|
1325
|
+
const sum = (u) => ({
|
|
1259
1326
|
tokensIn: num(u.input_tokens) + num(u.cache_creation_input_tokens) + num(u.cache_read_input_tokens),
|
|
1260
|
-
tokensOut: num(u.output_tokens)
|
|
1261
|
-
|
|
1327
|
+
tokensOut: num(u.output_tokens)
|
|
1328
|
+
});
|
|
1329
|
+
const spend = sum(result.usage ?? {});
|
|
1330
|
+
if (spend.tokensIn === 0 && spend.tokensOut === 0 && result.modelUsage) {
|
|
1331
|
+
for (const m of Object.values(result.modelUsage)) {
|
|
1332
|
+
const perModel = sum(asTokenUsage(m));
|
|
1333
|
+
spend.tokensIn += perModel.tokensIn;
|
|
1334
|
+
spend.tokensOut += perModel.tokensOut;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return { ...spend, durationMs: num(result.duration_ms) };
|
|
1338
|
+
}
|
|
1339
|
+
function asTokenUsage(m) {
|
|
1340
|
+
return {
|
|
1341
|
+
...m?.inputTokens !== void 0 ? { input_tokens: m.inputTokens } : {},
|
|
1342
|
+
...m?.outputTokens !== void 0 ? { output_tokens: m.outputTokens } : {},
|
|
1343
|
+
...m?.cacheCreationInputTokens !== void 0 ? { cache_creation_input_tokens: m.cacheCreationInputTokens } : {},
|
|
1344
|
+
...m?.cacheReadInputTokens !== void 0 ? { cache_read_input_tokens: m.cacheReadInputTokens } : {}
|
|
1262
1345
|
};
|
|
1263
1346
|
}
|
|
1347
|
+
function budgetExhausted(result) {
|
|
1348
|
+
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1349
|
+
}
|
|
1264
1350
|
var init_claudeResult = __esm({
|
|
1265
1351
|
"src/claudeResult.ts"() {
|
|
1266
1352
|
"use strict";
|
|
@@ -1272,8 +1358,10 @@ var init_claudeResult = __esm({
|
|
|
1272
1358
|
import { readdirSync as readdirSync2, statSync, writeFileSync } from "fs";
|
|
1273
1359
|
import { homedir } from "os";
|
|
1274
1360
|
import { join } from "path";
|
|
1275
|
-
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings) {
|
|
1361
|
+
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
1276
1362
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
1363
|
+
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
1364
|
+
const effortFlags = effort ? ["--effort", effort] : [];
|
|
1277
1365
|
const base3 = [
|
|
1278
1366
|
"--dangerously-skip-permissions",
|
|
1279
1367
|
...ISOLATION_FLAGS,
|
|
@@ -1281,6 +1369,8 @@ function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs
|
|
|
1281
1369
|
cfgPath,
|
|
1282
1370
|
...settings ? ["--settings", settings] : [],
|
|
1283
1371
|
...pluginFlags,
|
|
1372
|
+
...budgetFlags,
|
|
1373
|
+
...effortFlags,
|
|
1284
1374
|
"--output-format",
|
|
1285
1375
|
"json",
|
|
1286
1376
|
"-p",
|
|
@@ -1357,7 +1447,7 @@ var init_claudeCode = __esm({
|
|
|
1357
1447
|
const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
|
|
1358
1448
|
return {
|
|
1359
1449
|
bin: resolveClaudeBin(),
|
|
1360
|
-
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings)
|
|
1450
|
+
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
|
|
1361
1451
|
};
|
|
1362
1452
|
},
|
|
1363
1453
|
parseResult(raw, model) {
|
|
@@ -1370,7 +1460,10 @@ var init_claudeCode = __esm({
|
|
|
1370
1460
|
...est.warning ? { warning: est.warning } : {},
|
|
1371
1461
|
tokensIn: spend.tokensIn,
|
|
1372
1462
|
tokensOut: spend.tokensOut,
|
|
1373
|
-
durationMs: spend.durationMs
|
|
1463
|
+
durationMs: spend.durationMs,
|
|
1464
|
+
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1465
|
+
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1466
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1374
1467
|
};
|
|
1375
1468
|
},
|
|
1376
1469
|
session: {
|
|
@@ -1530,6 +1623,13 @@ ${notice}` : spec.prompt;
|
|
|
1530
1623
|
SKIP_GIT_CHECK_FLAG,
|
|
1531
1624
|
BYPASS_FLAG,
|
|
1532
1625
|
...spec.model ? ["-m", spec.model] : [],
|
|
1626
|
+
// TASWM-395: УСИЛИЕ РАССУЖДЕНИЯ (spec.effort) codex пока НЕ получает — и это сказано
|
|
1627
|
+
// вслух, как требует контракт адаптера (см. ExecutorLaunchSpec.effort), а не забыто.
|
|
1628
|
+
// Кандидат есть — `-c model_reasoning_effort=<low|medium|high>` — но словари не совпадают
|
|
1629
|
+
// (у нас пять уровней, xhigh/max пришлось бы схлопывать в high), а главное: непонятый
|
|
1630
|
+
// ключ в `-c` роняет НЕ один флаг, а весь спавн. Проверять это надо на живом codex, и
|
|
1631
|
+
// до такой проверки правильный размен — работающий codex на дефолтном усилии, а не
|
|
1632
|
+
// сломанный на настроенном. Провода до сюда доведены: останется дописать одну строку.
|
|
1533
1633
|
...mcpConfigArgs(spec),
|
|
1534
1634
|
prompt
|
|
1535
1635
|
];
|
|
@@ -1635,7 +1735,7 @@ function tokenFromFile() {
|
|
|
1635
1735
|
return "";
|
|
1636
1736
|
}
|
|
1637
1737
|
}
|
|
1638
|
-
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;
|
|
1738
|
+
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;
|
|
1639
1739
|
var init_config = __esm({
|
|
1640
1740
|
"src/config.ts"() {
|
|
1641
1741
|
"use strict";
|
|
@@ -1665,6 +1765,10 @@ var init_config = __esm({
|
|
|
1665
1765
|
SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
|
|
1666
1766
|
SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
|
|
1667
1767
|
SWARM_MAX_CPU_BUSY = envNum("SWARM_MAX_CPU_BUSY", 0.85);
|
|
1768
|
+
SWARM_CPU_WINDOW_MS = Math.max(1e3, envNum("SWARM_CPU_WINDOW_MS", 3e3));
|
|
1769
|
+
SWARM_CPU_SANITY_BUSY = envNum("SWARM_CPU_SANITY_BUSY", 0.9);
|
|
1770
|
+
SWARM_CPU_SANITY_LOAD_FACTOR = envNum("SWARM_CPU_SANITY_LOAD_FACTOR", 0.5);
|
|
1771
|
+
SWARM_CPU_SANITY_LAG_MS = envNum("SWARM_CPU_SANITY_LAG_MS", 6e4);
|
|
1668
1772
|
SWARM_RELIEF_LOAD_FACTOR = envNum("SWARM_RELIEF_LOAD_FACTOR", 1.3);
|
|
1669
1773
|
SWARM_RELIEF_MIN_FREE_GB = envNum("SWARM_RELIEF_MIN_FREE_GB", 0.8);
|
|
1670
1774
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR = envNum("SWARM_RELIEF_MAX_FD_LOAD_FACTOR", 0.95);
|
|
@@ -1884,7 +1988,7 @@ var init_package = __esm({
|
|
|
1884
1988
|
"package.json"() {
|
|
1885
1989
|
package_default = {
|
|
1886
1990
|
name: "@taskless-app/swarm",
|
|
1887
|
-
version: "0.6.
|
|
1991
|
+
version: "0.6.6",
|
|
1888
1992
|
type: "module",
|
|
1889
1993
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
1890
1994
|
keywords: [
|
|
@@ -1934,7 +2038,7 @@ var init_package = __esm({
|
|
|
1934
2038
|
execa: "^10.0.0",
|
|
1935
2039
|
zod: "3.25.76"
|
|
1936
2040
|
},
|
|
1937
|
-
gitHead: "
|
|
2041
|
+
gitHead: "89e3fe0078755d130512c75ac85f195e6b950392"
|
|
1938
2042
|
};
|
|
1939
2043
|
}
|
|
1940
2044
|
});
|
|
@@ -2301,6 +2405,10 @@ function rowToRun(row) {
|
|
|
2301
2405
|
area: row.area ?? null,
|
|
2302
2406
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
2303
2407
|
model: row.model ?? null,
|
|
2408
|
+
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
2409
|
+
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
2410
|
+
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
2411
|
+
effort: toEffortLevel(row.effort),
|
|
2304
2412
|
cost_usd: row.cost_usd ?? 0,
|
|
2305
2413
|
// Токены/время работы (TASWM-353) — старые прогоны без колонок → 0.
|
|
2306
2414
|
tokens_in: row.tokens_in ?? 0,
|
|
@@ -2370,6 +2478,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2370
2478
|
area TEXT,
|
|
2371
2479
|
depends_on TEXT,
|
|
2372
2480
|
model TEXT,
|
|
2481
|
+
effort TEXT,
|
|
2373
2482
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
2374
2483
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
2375
2484
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
@@ -2471,7 +2580,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2471
2580
|
// Чем обошёлся прогон, кроме денег (TASWM-353): токены вход/выход и время работы.
|
|
2472
2581
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
2473
2582
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
2474
|
-
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0"
|
|
2583
|
+
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
2584
|
+
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
2585
|
+
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
2586
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
2475
2587
|
]) {
|
|
2476
2588
|
try {
|
|
2477
2589
|
this.db.exec(alter);
|
|
@@ -2491,7 +2603,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2491
2603
|
createRun(input) {
|
|
2492
2604
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
2493
2605
|
const res = this.db.prepare(
|
|
2494
|
-
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2606
|
+
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2495
2607
|
).run(
|
|
2496
2608
|
input.task_slug,
|
|
2497
2609
|
name,
|
|
@@ -2504,6 +2616,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2504
2616
|
input.area ?? null,
|
|
2505
2617
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
2506
2618
|
input.model ?? null,
|
|
2619
|
+
input.effort ?? null,
|
|
2620
|
+
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
2507
2621
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
2508
2622
|
input.resolves_run_id ?? null,
|
|
2509
2623
|
input.reviews_run_id ?? null,
|
|
@@ -4105,25 +4219,66 @@ function cpuBusyFromTicks(prev, cur) {
|
|
|
4105
4219
|
if (dTotal <= 0) return null;
|
|
4106
4220
|
return Math.min(1, Math.max(0, 1 - dIdle / dTotal));
|
|
4107
4221
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4222
|
+
function cpuBusyFromSamples(prev, cur, minWindowMs = SWARM_CPU_WINDOW_MS) {
|
|
4223
|
+
if (!prev) return null;
|
|
4224
|
+
if (cur.at - prev.at < minWindowMs) return null;
|
|
4225
|
+
return cpuBusyFromTicks(prev, cur);
|
|
4226
|
+
}
|
|
4227
|
+
function cpuBusySane(busy, load1, cpus, highForMs, sanityBusy = SWARM_CPU_SANITY_BUSY, sanityLoadFactor = SWARM_CPU_SANITY_LOAD_FACTOR, lagMs = SWARM_CPU_SANITY_LAG_MS) {
|
|
4228
|
+
if (busy < sanityBusy) return true;
|
|
4229
|
+
if (highForMs < lagMs) return true;
|
|
4230
|
+
const cores = cpus > 0 ? cpus : 1;
|
|
4231
|
+
if (!Number.isFinite(load1) || load1 < 0) return true;
|
|
4232
|
+
return load1 >= cores * sanityLoadFactor;
|
|
4233
|
+
}
|
|
4234
|
+
var cpuWindowBase = null;
|
|
4235
|
+
var cpuBusyValue = null;
|
|
4236
|
+
var cpuHighSince = null;
|
|
4237
|
+
var cpuSanityWarnAt = 0;
|
|
4238
|
+
var CPU_SANITY_WARN_EVERY_MS = 5 * 6e4;
|
|
4239
|
+
function sampleCpuBusy(now = Date.now(), ticks = readCpuTicks(os.cpus())) {
|
|
4240
|
+
const cur = { at: now, ...ticks };
|
|
4241
|
+
if (!cpuWindowBase) {
|
|
4242
|
+
cpuWindowBase = cur;
|
|
4243
|
+
return cpuBusyValue;
|
|
4244
|
+
}
|
|
4245
|
+
if (cur.at - cpuWindowBase.at < SWARM_CPU_WINDOW_MS) return cpuBusyValue;
|
|
4246
|
+
cpuBusyValue = cpuBusyFromSamples(cpuWindowBase, cur);
|
|
4247
|
+
cpuWindowBase = cur;
|
|
4248
|
+
return cpuBusyValue;
|
|
4249
|
+
}
|
|
4250
|
+
function cpuBusyReading() {
|
|
4251
|
+
return cpuBusyValue;
|
|
4252
|
+
}
|
|
4253
|
+
function trustedCpuBusy(busy, load1, cpus, now) {
|
|
4254
|
+
if (busy == null || busy < SWARM_CPU_SANITY_BUSY) {
|
|
4255
|
+
cpuHighSince = null;
|
|
4256
|
+
return busy;
|
|
4257
|
+
}
|
|
4258
|
+
cpuHighSince ??= now;
|
|
4259
|
+
if (cpuBusySane(busy, load1, cpus, now - cpuHighSince)) return busy;
|
|
4260
|
+
if (now - cpuSanityWarnAt >= CPU_SANITY_WARN_EVERY_MS) {
|
|
4261
|
+
cpuSanityWarnAt = now;
|
|
4262
|
+
console.warn(
|
|
4263
|
+
`[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.`
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4266
|
+
return null;
|
|
4114
4267
|
}
|
|
4115
4268
|
var RESOURCES_CACHE_MS = 250;
|
|
4116
4269
|
var readingCache = null;
|
|
4117
4270
|
function hostReading() {
|
|
4118
4271
|
const now = Date.now();
|
|
4119
4272
|
if (readingCache && now - readingCache.at < RESOURCES_CACHE_MS) return readingCache.reading;
|
|
4273
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
4274
|
+
const cpus = os.cpus().length;
|
|
4275
|
+
sampleCpuBusy(now);
|
|
4120
4276
|
const reading = {
|
|
4121
|
-
load1
|
|
4122
|
-
cpus
|
|
4277
|
+
load1,
|
|
4278
|
+
cpus,
|
|
4123
4279
|
freeGb: availableMemBytes() / 1e9,
|
|
4124
4280
|
fdLoadRatio: fileDescriptorLoadRatio(),
|
|
4125
|
-
cpuBusy:
|
|
4126
|
-
// TASWM-234: раз в тик (кэш выше) — иначе окно дельты схлопнется
|
|
4281
|
+
cpuBusy: trustedCpuBusy(cpuBusyReading(), load1, cpus, now),
|
|
4127
4282
|
totalGb: os.totalmem() / 1e9,
|
|
4128
4283
|
disk: diskGb()
|
|
4129
4284
|
};
|
|
@@ -4224,6 +4379,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
4224
4379
|
// предикат «репо есть локально» для решения, брать ли свободный прогон.
|
|
4225
4380
|
host;
|
|
4226
4381
|
repoExists;
|
|
4382
|
+
// TASWM-398: ЧЕМ эта машина исполняет прогоны — id адаптера (claude-code|codex).
|
|
4383
|
+
executorId;
|
|
4227
4384
|
// Зеркало.
|
|
4228
4385
|
runs = /* @__PURE__ */ new Map();
|
|
4229
4386
|
runtime = /* @__PURE__ */ new Map();
|
|
@@ -4301,6 +4458,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4301
4458
|
this.host = opts.host ?? null;
|
|
4302
4459
|
this.repoExists = opts.repoExists ?? existsSync4;
|
|
4303
4460
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
4461
|
+
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
4304
4462
|
this.outboxPath = opts.outboxPath;
|
|
4305
4463
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
4306
4464
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
@@ -4414,8 +4572,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
4414
4572
|
runFields(patch) {
|
|
4415
4573
|
const out = {};
|
|
4416
4574
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
4575
|
+
if (patch.status === "running") out.executor = this.executorId;
|
|
4417
4576
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
4418
4577
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
4578
|
+
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
4419
4579
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
4420
4580
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
4421
4581
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
@@ -4486,6 +4646,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
4486
4646
|
area: input.area ?? null,
|
|
4487
4647
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
4488
4648
|
model: input.model ?? null,
|
|
4649
|
+
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
4650
|
+
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
4651
|
+
effort: input.effort ?? null,
|
|
4489
4652
|
cost_usd: 0,
|
|
4490
4653
|
tokens_in: 0,
|
|
4491
4654
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
@@ -4538,6 +4701,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
4538
4701
|
status: "queued",
|
|
4539
4702
|
area: d.area,
|
|
4540
4703
|
model: d.model,
|
|
4704
|
+
effort: d.effort,
|
|
4705
|
+
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
4541
4706
|
name: d.name,
|
|
4542
4707
|
repo: d.repo,
|
|
4543
4708
|
depends_on: d.depends_on,
|
|
@@ -5291,6 +5456,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
5291
5456
|
* → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
|
|
5292
5457
|
*/
|
|
5293
5458
|
async ownerMaxDrones() {
|
|
5459
|
+
return (await this.ownerSwarmSettings())?.maxDrones ?? null;
|
|
5460
|
+
}
|
|
5461
|
+
/**
|
|
5462
|
+
* TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
|
|
5463
|
+
* которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
|
|
5464
|
+
* двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
|
|
5465
|
+
* соседним полем того же ответа был бы платой ни за что.
|
|
5466
|
+
*
|
|
5467
|
+
* Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
|
|
5468
|
+
* (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
|
|
5469
|
+
* не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
|
|
5470
|
+
*/
|
|
5471
|
+
async ownerSwarmSettings() {
|
|
5294
5472
|
let res;
|
|
5295
5473
|
try {
|
|
5296
5474
|
res = await this.call("who_am_i", {});
|
|
@@ -5300,7 +5478,27 @@ var SwarmClient = class _SwarmClient {
|
|
|
5300
5478
|
if (res?.isError) return null;
|
|
5301
5479
|
const obj = jsonFromTextContent(res);
|
|
5302
5480
|
const n = obj?.maxDrones;
|
|
5303
|
-
|
|
5481
|
+
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
5482
|
+
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
5483
|
+
const cost = obj?.swarmSettings?.maxCostUsd;
|
|
5484
|
+
return {
|
|
5485
|
+
maxDrones: Math.trunc(n),
|
|
5486
|
+
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
5487
|
+
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
5488
|
+
models: { drone: str(obj?.swarmSettings?.droneModel), reviewer: str(obj?.swarmSettings?.reviewerModel) },
|
|
5489
|
+
// TASWM-395: уровни усилия — из того же ответа и с той же семантикой, что модели:
|
|
5490
|
+
// старый прод поля не отдаёт → оба null, то есть env машины и дефолт харнесса.
|
|
5491
|
+
// toEffortLevel заодно отсекает мусор, записанный в uiPrefs мимо схемы.
|
|
5492
|
+
efforts: {
|
|
5493
|
+
drone: toEffortLevel(obj?.swarmSettings?.droneEffort),
|
|
5494
|
+
reviewer: toEffortLevel(obj?.swarmSettings?.reviewerEffort)
|
|
5495
|
+
},
|
|
5496
|
+
// TASWM-377: потолок трат — из того же ответа, тем же одним чтением. НОЛЬ здесь
|
|
5497
|
+
// значим («гарантия выключена осознанно»), поэтому пропускаем и его, а не только
|
|
5498
|
+
// положительные: null остаётся ровно за «ручка не задана / старый прод», и только
|
|
5499
|
+
// он уводит спавн на env-фолбэк и дефолт ядра.
|
|
5500
|
+
maxCostUsd: typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null
|
|
5501
|
+
};
|
|
5304
5502
|
}
|
|
5305
5503
|
/**
|
|
5306
5504
|
* Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
|
|
@@ -5438,19 +5636,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5438
5636
|
if (res?.isError) return [];
|
|
5439
5637
|
const raw = (res.structuredContent ?? {}).hosts;
|
|
5440
5638
|
if (!Array.isArray(raw)) return [];
|
|
5441
|
-
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) =>
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5639
|
+
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) => {
|
|
5640
|
+
const resources = parseHostResources(h.resources);
|
|
5641
|
+
return {
|
|
5642
|
+
hostId: h.hostId,
|
|
5643
|
+
hostName: typeof h.hostName === "string" && h.hostName ? h.hostName : h.hostId,
|
|
5644
|
+
lastSeenAt: typeof h.lastSeenAt === "string" ? h.lastSeenAt : null,
|
|
5645
|
+
// TASWM-298: репозитории машины — «на связи» ещё не значит «возьмёт эту задачу».
|
|
5646
|
+
// Прод старой версии поля не пришлёт → пустой список, строка просто без хвоста.
|
|
5647
|
+
reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : [],
|
|
5648
|
+
// TASWM-326: машина на паузе — «на связи, но работу не берёт». Прод старой
|
|
5649
|
+
// версии поля не пришлёт → undefined, строка выглядит как раньше.
|
|
5650
|
+
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
5651
|
+
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
5652
|
+
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
5653
|
+
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
5654
|
+
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
5655
|
+
...resources ? { resources } : {}
|
|
5656
|
+
};
|
|
5657
|
+
});
|
|
5454
5658
|
}
|
|
5455
5659
|
/**
|
|
5456
5660
|
* TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
|
|
@@ -5516,13 +5720,22 @@ var SwarmClient = class _SwarmClient {
|
|
|
5516
5720
|
* разное. Не знаем своего потолка (ещё не читали / sqlite) → ключ не шлём, прод не
|
|
5517
5721
|
* трогает прошлое значение — та же логика, что у repoFullNames.
|
|
5518
5722
|
*
|
|
5723
|
+
* TASWM-394: тем же пульсом уезжают РЕСУРСЫ машины (`resources` — процессор, память,
|
|
5724
|
+
* диск и время замера). Это не новое измерение, а ровно то, что демон УЖЕ посчитал для
|
|
5725
|
+
* своего гейта спавна: числа в интерфейсе обязаны совпадать с теми, по которым он
|
|
5726
|
+
* отказывается поднимать дрона. Без них «машина на связи, свободна» и «машина физически
|
|
5727
|
+
* не может поднять дрона» выглядят в проде одинаково — 12.08 это стоило полудня
|
|
5728
|
+
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
5729
|
+
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
5730
|
+
*
|
|
5519
5731
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
5520
5732
|
*/
|
|
5521
|
-
async heartbeat(daemonVersion, pause, capacity) {
|
|
5733
|
+
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
5522
5734
|
try {
|
|
5523
5735
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
5524
5736
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
5525
5737
|
if (capacity != null && Number.isFinite(capacity) && capacity > 0) args.capacity = Math.trunc(capacity);
|
|
5738
|
+
if (resources) args.resources = resources;
|
|
5526
5739
|
if (pause !== void 0) {
|
|
5527
5740
|
args.paused = !!pause?.paused;
|
|
5528
5741
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
@@ -5729,6 +5942,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5729
5942
|
area: null,
|
|
5730
5943
|
depends_on: [],
|
|
5731
5944
|
model: null,
|
|
5945
|
+
effort: null,
|
|
5946
|
+
// усилие рассуждения (TASWM-395) — прод-поле, заполняется ниже из ответа
|
|
5732
5947
|
cost_usd: 0,
|
|
5733
5948
|
tokens_in: 0,
|
|
5734
5949
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
@@ -5797,6 +6012,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5797
6012
|
local.area = pr.area ?? null;
|
|
5798
6013
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
5799
6014
|
local.model = pr.model ?? null;
|
|
6015
|
+
local.effort = toEffortLevel(pr.effort);
|
|
5800
6016
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
5801
6017
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
5802
6018
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
@@ -6634,7 +6850,10 @@ function hostLines(hosts, opts = {}) {
|
|
|
6634
6850
|
const here = selfHostId && h.hostId === selfHostId ? " \u2190 \u044D\u0442\u0430 \u043C\u0430\u0448\u0438\u043D\u0430" : "";
|
|
6635
6851
|
const repos = h.reposServed?.length ? `
|
|
6636
6852
|
\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438: ${h.reposServed.join(", ")}` : "";
|
|
6637
|
-
|
|
6853
|
+
const res = hostResourcesText(h.resources, nowMs);
|
|
6854
|
+
const load = res ? `
|
|
6855
|
+
${res}` : "";
|
|
6856
|
+
return ` ${h.hostName} ${hostStateText(h, nowMs)} (${h.hostId})${here}${load}${repos}`;
|
|
6638
6857
|
});
|
|
6639
6858
|
}
|
|
6640
6859
|
|
|
@@ -7234,7 +7453,8 @@ function timelineState(db2, now = Date.now(), overmindLog = []) {
|
|
|
7234
7453
|
heroRunId: r.hero_run_id,
|
|
7235
7454
|
reviewVerdict: r.review_verdict,
|
|
7236
7455
|
reviewPhase: r.review_phase,
|
|
7237
|
-
model: r.model
|
|
7456
|
+
model: r.model,
|
|
7457
|
+
effort: r.effort
|
|
7238
7458
|
});
|
|
7239
7459
|
}
|
|
7240
7460
|
const liveLanes = lanes.filter((l) => l.live);
|
|
@@ -8709,8 +8929,26 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8709
8929
|
};
|
|
8710
8930
|
}
|
|
8711
8931
|
|
|
8932
|
+
// src/swarmModels.ts
|
|
8933
|
+
init_src();
|
|
8934
|
+
function pick(v) {
|
|
8935
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8936
|
+
return s.length > 0 ? s : null;
|
|
8937
|
+
}
|
|
8938
|
+
function forKind(kind, swarm, norm) {
|
|
8939
|
+
if (!swarm) return null;
|
|
8940
|
+
return kind === "reviewer" ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
8941
|
+
}
|
|
8942
|
+
function resolveRunModel(run, swarm, env = process.env) {
|
|
8943
|
+
return pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8944
|
+
}
|
|
8945
|
+
function resolveRunEffort(run, swarm, env = process.env) {
|
|
8946
|
+
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
8947
|
+
}
|
|
8948
|
+
|
|
8712
8949
|
// src/launcher.ts
|
|
8713
8950
|
init_executors();
|
|
8951
|
+
init_src();
|
|
8714
8952
|
var requireFromHere = createRequire(import.meta.url);
|
|
8715
8953
|
function resolveTsxCli() {
|
|
8716
8954
|
try {
|
|
@@ -8722,6 +8960,15 @@ function resolveTsxCli() {
|
|
|
8722
8960
|
}
|
|
8723
8961
|
}
|
|
8724
8962
|
var MCP_SERVER_NAME = "overmind";
|
|
8963
|
+
function launchBudgetUsd(limit, spentUsd, env = process.env) {
|
|
8964
|
+
return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
|
|
8965
|
+
}
|
|
8966
|
+
function envCapUsd(env) {
|
|
8967
|
+
const raw = env.SWARM_MAX_COST_USD;
|
|
8968
|
+
if (raw === void 0 || raw === "") return DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8969
|
+
const n = Number(raw);
|
|
8970
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8971
|
+
}
|
|
8725
8972
|
function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
8726
8973
|
const env = { ...base3 };
|
|
8727
8974
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
@@ -8846,7 +9093,13 @@ function makeLauncher(cfg) {
|
|
|
8846
9093
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
8847
9094
|
}
|
|
8848
9095
|
};
|
|
8849
|
-
const model = run
|
|
9096
|
+
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
9097
|
+
const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
|
|
9098
|
+
try {
|
|
9099
|
+
cfg.recordEffort?.(run.id, effort);
|
|
9100
|
+
} catch (e) {
|
|
9101
|
+
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)}`);
|
|
9102
|
+
}
|
|
8850
9103
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8851
9104
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8852
9105
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8865,7 +9118,14 @@ function makeLauncher(cfg) {
|
|
|
8865
9118
|
// Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
|
|
8866
9119
|
// сидит в HOME демона, и запирать половину дверей — самообман.
|
|
8867
9120
|
denyPaths: cfg.droneIsolation ? dronePrivatePaths(realHome, cfg.droneIsolation.swarmHome) : [],
|
|
8868
|
-
realHome
|
|
9121
|
+
realHome,
|
|
9122
|
+
// TASWM-377: остаток бюджета ЭТОГО прогона — харнессу, чтобы он резал трату у себя, в
|
|
9123
|
+
// момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
|
|
9124
|
+
// стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
|
|
9125
|
+
// ни жёг, — отсюда и $74 при лимите $20.
|
|
9126
|
+
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
|
|
9127
|
+
// TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
|
|
9128
|
+
effort
|
|
8869
9129
|
});
|
|
8870
9130
|
let stdio = "ignore";
|
|
8871
9131
|
let outFd;
|