@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/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,36 @@ 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
|
+
|
|
1156
|
+
// ../swarm-core/src/queuePlan.ts
|
|
1157
|
+
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS, QUEUE_REVIEW_TAIL_MS;
|
|
1158
|
+
var init_queuePlan = __esm({
|
|
1159
|
+
"../swarm-core/src/queuePlan.ts"() {
|
|
1160
|
+
"use strict";
|
|
1161
|
+
init_status();
|
|
1162
|
+
init_hostPool();
|
|
1163
|
+
init_hosts();
|
|
1164
|
+
init_time();
|
|
1165
|
+
MIN_MS = 6e4;
|
|
1166
|
+
QUEUE_NOMINAL_RUN_MS = 25 * MIN_MS;
|
|
1167
|
+
QUEUE_MIN_TAIL_MS = 3 * MIN_MS;
|
|
1168
|
+
QUEUE_REVIEW_TAIL_MS = 10 * MIN_MS;
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1090
1172
|
// ../swarm-core/src/index.ts
|
|
1091
1173
|
var init_src = __esm({
|
|
1092
1174
|
"../swarm-core/src/index.ts"() {
|
|
@@ -1104,12 +1186,15 @@ var init_src = __esm({
|
|
|
1104
1186
|
init_names();
|
|
1105
1187
|
init_baseBranch();
|
|
1106
1188
|
init_hosts();
|
|
1189
|
+
init_hostResources();
|
|
1107
1190
|
init_hostPool();
|
|
1108
1191
|
init_hostCommands();
|
|
1109
1192
|
init_relief();
|
|
1110
1193
|
init_cost();
|
|
1111
1194
|
init_runGuards();
|
|
1112
1195
|
init_codeZones();
|
|
1196
|
+
init_effort();
|
|
1197
|
+
init_queuePlan();
|
|
1113
1198
|
}
|
|
1114
1199
|
});
|
|
1115
1200
|
|
|
@@ -1253,14 +1338,32 @@ function launchCostUsd(result, model) {
|
|
|
1253
1338
|
return costFromUsage(result.usage, model);
|
|
1254
1339
|
}
|
|
1255
1340
|
function launchSpend(result) {
|
|
1256
|
-
const u = result.usage ?? {};
|
|
1257
1341
|
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1258
|
-
|
|
1342
|
+
const sum = (u) => ({
|
|
1259
1343
|
tokensIn: num(u.input_tokens) + num(u.cache_creation_input_tokens) + num(u.cache_read_input_tokens),
|
|
1260
|
-
tokensOut: num(u.output_tokens)
|
|
1261
|
-
|
|
1344
|
+
tokensOut: num(u.output_tokens)
|
|
1345
|
+
});
|
|
1346
|
+
const spend = sum(result.usage ?? {});
|
|
1347
|
+
if (spend.tokensIn === 0 && spend.tokensOut === 0 && result.modelUsage) {
|
|
1348
|
+
for (const m of Object.values(result.modelUsage)) {
|
|
1349
|
+
const perModel = sum(asTokenUsage(m));
|
|
1350
|
+
spend.tokensIn += perModel.tokensIn;
|
|
1351
|
+
spend.tokensOut += perModel.tokensOut;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return { ...spend, durationMs: num(result.duration_ms) };
|
|
1355
|
+
}
|
|
1356
|
+
function asTokenUsage(m) {
|
|
1357
|
+
return {
|
|
1358
|
+
...m?.inputTokens !== void 0 ? { input_tokens: m.inputTokens } : {},
|
|
1359
|
+
...m?.outputTokens !== void 0 ? { output_tokens: m.outputTokens } : {},
|
|
1360
|
+
...m?.cacheCreationInputTokens !== void 0 ? { cache_creation_input_tokens: m.cacheCreationInputTokens } : {},
|
|
1361
|
+
...m?.cacheReadInputTokens !== void 0 ? { cache_read_input_tokens: m.cacheReadInputTokens } : {}
|
|
1262
1362
|
};
|
|
1263
1363
|
}
|
|
1364
|
+
function budgetExhausted(result) {
|
|
1365
|
+
return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
|
|
1366
|
+
}
|
|
1264
1367
|
var init_claudeResult = __esm({
|
|
1265
1368
|
"src/claudeResult.ts"() {
|
|
1266
1369
|
"use strict";
|
|
@@ -1272,8 +1375,10 @@ var init_claudeResult = __esm({
|
|
|
1272
1375
|
import { readdirSync as readdirSync2, statSync, writeFileSync } from "fs";
|
|
1273
1376
|
import { homedir } from "os";
|
|
1274
1377
|
import { join } from "path";
|
|
1275
|
-
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings) {
|
|
1378
|
+
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
1276
1379
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
1380
|
+
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
1381
|
+
const effortFlags = effort ? ["--effort", effort] : [];
|
|
1277
1382
|
const base3 = [
|
|
1278
1383
|
"--dangerously-skip-permissions",
|
|
1279
1384
|
...ISOLATION_FLAGS,
|
|
@@ -1281,6 +1386,8 @@ function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs
|
|
|
1281
1386
|
cfgPath,
|
|
1282
1387
|
...settings ? ["--settings", settings] : [],
|
|
1283
1388
|
...pluginFlags,
|
|
1389
|
+
...budgetFlags,
|
|
1390
|
+
...effortFlags,
|
|
1284
1391
|
"--output-format",
|
|
1285
1392
|
"json",
|
|
1286
1393
|
"-p",
|
|
@@ -1357,7 +1464,7 @@ var init_claudeCode = __esm({
|
|
|
1357
1464
|
const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
|
|
1358
1465
|
return {
|
|
1359
1466
|
bin: resolveClaudeBin(),
|
|
1360
|
-
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings)
|
|
1467
|
+
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
|
|
1361
1468
|
};
|
|
1362
1469
|
},
|
|
1363
1470
|
parseResult(raw, model) {
|
|
@@ -1370,7 +1477,10 @@ var init_claudeCode = __esm({
|
|
|
1370
1477
|
...est.warning ? { warning: est.warning } : {},
|
|
1371
1478
|
tokensIn: spend.tokensIn,
|
|
1372
1479
|
tokensOut: spend.tokensOut,
|
|
1373
|
-
durationMs: spend.durationMs
|
|
1480
|
+
durationMs: spend.durationMs,
|
|
1481
|
+
// TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
|
|
1482
|
+
// разбор файла: у claude он лежит в тех же полях, что деньги и токены.
|
|
1483
|
+
...budgetExhausted(result) ? { stoppedByBudget: true } : {}
|
|
1374
1484
|
};
|
|
1375
1485
|
},
|
|
1376
1486
|
session: {
|
|
@@ -1530,6 +1640,13 @@ ${notice}` : spec.prompt;
|
|
|
1530
1640
|
SKIP_GIT_CHECK_FLAG,
|
|
1531
1641
|
BYPASS_FLAG,
|
|
1532
1642
|
...spec.model ? ["-m", spec.model] : [],
|
|
1643
|
+
// TASWM-395: УСИЛИЕ РАССУЖДЕНИЯ (spec.effort) codex пока НЕ получает — и это сказано
|
|
1644
|
+
// вслух, как требует контракт адаптера (см. ExecutorLaunchSpec.effort), а не забыто.
|
|
1645
|
+
// Кандидат есть — `-c model_reasoning_effort=<low|medium|high>` — но словари не совпадают
|
|
1646
|
+
// (у нас пять уровней, xhigh/max пришлось бы схлопывать в high), а главное: непонятый
|
|
1647
|
+
// ключ в `-c` роняет НЕ один флаг, а весь спавн. Проверять это надо на живом codex, и
|
|
1648
|
+
// до такой проверки правильный размен — работающий codex на дефолтном усилии, а не
|
|
1649
|
+
// сломанный на настроенном. Провода до сюда доведены: останется дописать одну строку.
|
|
1533
1650
|
...mcpConfigArgs(spec),
|
|
1534
1651
|
prompt
|
|
1535
1652
|
];
|
|
@@ -1635,7 +1752,7 @@ function tokenFromFile() {
|
|
|
1635
1752
|
return "";
|
|
1636
1753
|
}
|
|
1637
1754
|
}
|
|
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;
|
|
1755
|
+
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
1756
|
var init_config = __esm({
|
|
1640
1757
|
"src/config.ts"() {
|
|
1641
1758
|
"use strict";
|
|
@@ -1665,6 +1782,10 @@ var init_config = __esm({
|
|
|
1665
1782
|
SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
|
|
1666
1783
|
SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
|
|
1667
1784
|
SWARM_MAX_CPU_BUSY = envNum("SWARM_MAX_CPU_BUSY", 0.85);
|
|
1785
|
+
SWARM_CPU_WINDOW_MS = Math.max(1e3, envNum("SWARM_CPU_WINDOW_MS", 3e3));
|
|
1786
|
+
SWARM_CPU_SANITY_BUSY = envNum("SWARM_CPU_SANITY_BUSY", 0.9);
|
|
1787
|
+
SWARM_CPU_SANITY_LOAD_FACTOR = envNum("SWARM_CPU_SANITY_LOAD_FACTOR", 0.5);
|
|
1788
|
+
SWARM_CPU_SANITY_LAG_MS = envNum("SWARM_CPU_SANITY_LAG_MS", 6e4);
|
|
1668
1789
|
SWARM_RELIEF_LOAD_FACTOR = envNum("SWARM_RELIEF_LOAD_FACTOR", 1.3);
|
|
1669
1790
|
SWARM_RELIEF_MIN_FREE_GB = envNum("SWARM_RELIEF_MIN_FREE_GB", 0.8);
|
|
1670
1791
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR = envNum("SWARM_RELIEF_MAX_FD_LOAD_FACTOR", 0.95);
|
|
@@ -1884,7 +2005,7 @@ var init_package = __esm({
|
|
|
1884
2005
|
"package.json"() {
|
|
1885
2006
|
package_default = {
|
|
1886
2007
|
name: "@taskless-app/swarm",
|
|
1887
|
-
version: "0.6.
|
|
2008
|
+
version: "0.6.7",
|
|
1888
2009
|
type: "module",
|
|
1889
2010
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
1890
2011
|
keywords: [
|
|
@@ -1934,7 +2055,7 @@ var init_package = __esm({
|
|
|
1934
2055
|
execa: "^10.0.0",
|
|
1935
2056
|
zod: "3.25.76"
|
|
1936
2057
|
},
|
|
1937
|
-
gitHead: "
|
|
2058
|
+
gitHead: "08ee2a1a4bdb53ba1c25c92066104f0de3a23433"
|
|
1938
2059
|
};
|
|
1939
2060
|
}
|
|
1940
2061
|
});
|
|
@@ -2301,6 +2422,10 @@ function rowToRun(row) {
|
|
|
2301
2422
|
area: row.area ?? null,
|
|
2302
2423
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
2303
2424
|
model: row.model ?? null,
|
|
2425
|
+
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
2426
|
+
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
2427
|
+
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
2428
|
+
effort: toEffortLevel(row.effort),
|
|
2304
2429
|
cost_usd: row.cost_usd ?? 0,
|
|
2305
2430
|
// Токены/время работы (TASWM-353) — старые прогоны без колонок → 0.
|
|
2306
2431
|
tokens_in: row.tokens_in ?? 0,
|
|
@@ -2370,6 +2495,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2370
2495
|
area TEXT,
|
|
2371
2496
|
depends_on TEXT,
|
|
2372
2497
|
model TEXT,
|
|
2498
|
+
effort TEXT,
|
|
2373
2499
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
2374
2500
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
2375
2501
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
@@ -2471,7 +2597,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2471
2597
|
// Чем обошёлся прогон, кроме денег (TASWM-353): токены вход/выход и время работы.
|
|
2472
2598
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
2473
2599
|
"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"
|
|
2600
|
+
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
2601
|
+
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
2602
|
+
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
2603
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
2475
2604
|
]) {
|
|
2476
2605
|
try {
|
|
2477
2606
|
this.db.exec(alter);
|
|
@@ -2491,7 +2620,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2491
2620
|
createRun(input) {
|
|
2492
2621
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
2493
2622
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2623
|
+
"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
2624
|
).run(
|
|
2496
2625
|
input.task_slug,
|
|
2497
2626
|
name,
|
|
@@ -2504,6 +2633,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2504
2633
|
input.area ?? null,
|
|
2505
2634
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
2506
2635
|
input.model ?? null,
|
|
2636
|
+
input.effort ?? null,
|
|
2637
|
+
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
2507
2638
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
2508
2639
|
input.resolves_run_id ?? null,
|
|
2509
2640
|
input.reviews_run_id ?? null,
|
|
@@ -4105,25 +4236,66 @@ function cpuBusyFromTicks(prev, cur) {
|
|
|
4105
4236
|
if (dTotal <= 0) return null;
|
|
4106
4237
|
return Math.min(1, Math.max(0, 1 - dIdle / dTotal));
|
|
4107
4238
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4239
|
+
function cpuBusyFromSamples(prev, cur, minWindowMs = SWARM_CPU_WINDOW_MS) {
|
|
4240
|
+
if (!prev) return null;
|
|
4241
|
+
if (cur.at - prev.at < minWindowMs) return null;
|
|
4242
|
+
return cpuBusyFromTicks(prev, cur);
|
|
4243
|
+
}
|
|
4244
|
+
function cpuBusySane(busy, load1, cpus, highForMs, sanityBusy = SWARM_CPU_SANITY_BUSY, sanityLoadFactor = SWARM_CPU_SANITY_LOAD_FACTOR, lagMs = SWARM_CPU_SANITY_LAG_MS) {
|
|
4245
|
+
if (busy < sanityBusy) return true;
|
|
4246
|
+
if (highForMs < lagMs) return true;
|
|
4247
|
+
const cores = cpus > 0 ? cpus : 1;
|
|
4248
|
+
if (!Number.isFinite(load1) || load1 < 0) return true;
|
|
4249
|
+
return load1 >= cores * sanityLoadFactor;
|
|
4250
|
+
}
|
|
4251
|
+
var cpuWindowBase = null;
|
|
4252
|
+
var cpuBusyValue = null;
|
|
4253
|
+
var cpuHighSince = null;
|
|
4254
|
+
var cpuSanityWarnAt = 0;
|
|
4255
|
+
var CPU_SANITY_WARN_EVERY_MS = 5 * 6e4;
|
|
4256
|
+
function sampleCpuBusy(now = Date.now(), ticks = readCpuTicks(os.cpus())) {
|
|
4257
|
+
const cur = { at: now, ...ticks };
|
|
4258
|
+
if (!cpuWindowBase) {
|
|
4259
|
+
cpuWindowBase = cur;
|
|
4260
|
+
return cpuBusyValue;
|
|
4261
|
+
}
|
|
4262
|
+
if (cur.at - cpuWindowBase.at < SWARM_CPU_WINDOW_MS) return cpuBusyValue;
|
|
4263
|
+
cpuBusyValue = cpuBusyFromSamples(cpuWindowBase, cur);
|
|
4264
|
+
cpuWindowBase = cur;
|
|
4265
|
+
return cpuBusyValue;
|
|
4266
|
+
}
|
|
4267
|
+
function cpuBusyReading() {
|
|
4268
|
+
return cpuBusyValue;
|
|
4269
|
+
}
|
|
4270
|
+
function trustedCpuBusy(busy, load1, cpus, now) {
|
|
4271
|
+
if (busy == null || busy < SWARM_CPU_SANITY_BUSY) {
|
|
4272
|
+
cpuHighSince = null;
|
|
4273
|
+
return busy;
|
|
4274
|
+
}
|
|
4275
|
+
cpuHighSince ??= now;
|
|
4276
|
+
if (cpuBusySane(busy, load1, cpus, now - cpuHighSince)) return busy;
|
|
4277
|
+
if (now - cpuSanityWarnAt >= CPU_SANITY_WARN_EVERY_MS) {
|
|
4278
|
+
cpuSanityWarnAt = now;
|
|
4279
|
+
console.warn(
|
|
4280
|
+
`[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.`
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
return null;
|
|
4114
4284
|
}
|
|
4115
4285
|
var RESOURCES_CACHE_MS = 250;
|
|
4116
4286
|
var readingCache = null;
|
|
4117
4287
|
function hostReading() {
|
|
4118
4288
|
const now = Date.now();
|
|
4119
4289
|
if (readingCache && now - readingCache.at < RESOURCES_CACHE_MS) return readingCache.reading;
|
|
4290
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
4291
|
+
const cpus = os.cpus().length;
|
|
4292
|
+
sampleCpuBusy(now);
|
|
4120
4293
|
const reading = {
|
|
4121
|
-
load1
|
|
4122
|
-
cpus
|
|
4294
|
+
load1,
|
|
4295
|
+
cpus,
|
|
4123
4296
|
freeGb: availableMemBytes() / 1e9,
|
|
4124
4297
|
fdLoadRatio: fileDescriptorLoadRatio(),
|
|
4125
|
-
cpuBusy:
|
|
4126
|
-
// TASWM-234: раз в тик (кэш выше) — иначе окно дельты схлопнется
|
|
4298
|
+
cpuBusy: trustedCpuBusy(cpuBusyReading(), load1, cpus, now),
|
|
4127
4299
|
totalGb: os.totalmem() / 1e9,
|
|
4128
4300
|
disk: diskGb()
|
|
4129
4301
|
};
|
|
@@ -4224,6 +4396,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
4224
4396
|
// предикат «репо есть локально» для решения, брать ли свободный прогон.
|
|
4225
4397
|
host;
|
|
4226
4398
|
repoExists;
|
|
4399
|
+
// TASWM-398: ЧЕМ эта машина исполняет прогоны — id адаптера (claude-code|codex).
|
|
4400
|
+
executorId;
|
|
4227
4401
|
// Зеркало.
|
|
4228
4402
|
runs = /* @__PURE__ */ new Map();
|
|
4229
4403
|
runtime = /* @__PURE__ */ new Map();
|
|
@@ -4301,6 +4475,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4301
4475
|
this.host = opts.host ?? null;
|
|
4302
4476
|
this.repoExists = opts.repoExists ?? existsSync4;
|
|
4303
4477
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
4478
|
+
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
4304
4479
|
this.outboxPath = opts.outboxPath;
|
|
4305
4480
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
4306
4481
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
@@ -4414,8 +4589,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
4414
4589
|
runFields(patch) {
|
|
4415
4590
|
const out = {};
|
|
4416
4591
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
4592
|
+
if (patch.status === "running") out.executor = this.executorId;
|
|
4417
4593
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
4418
4594
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
4595
|
+
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
4419
4596
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
4420
4597
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
4421
4598
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
@@ -4486,6 +4663,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
4486
4663
|
area: input.area ?? null,
|
|
4487
4664
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
4488
4665
|
model: input.model ?? null,
|
|
4666
|
+
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
4667
|
+
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
4668
|
+
effort: input.effort ?? null,
|
|
4489
4669
|
cost_usd: 0,
|
|
4490
4670
|
tokens_in: 0,
|
|
4491
4671
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
@@ -4538,6 +4718,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
4538
4718
|
status: "queued",
|
|
4539
4719
|
area: d.area,
|
|
4540
4720
|
model: d.model,
|
|
4721
|
+
effort: d.effort,
|
|
4722
|
+
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
4541
4723
|
name: d.name,
|
|
4542
4724
|
repo: d.repo,
|
|
4543
4725
|
depends_on: d.depends_on,
|
|
@@ -5315,11 +5497,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
5315
5497
|
const n = obj?.maxDrones;
|
|
5316
5498
|
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
5317
5499
|
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
5500
|
+
const cost = obj?.swarmSettings?.maxCostUsd;
|
|
5318
5501
|
return {
|
|
5319
5502
|
maxDrones: Math.trunc(n),
|
|
5320
5503
|
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
5321
5504
|
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
5322
|
-
models: { drone: str(obj?.swarmSettings?.droneModel), reviewer: str(obj?.swarmSettings?.reviewerModel) }
|
|
5505
|
+
models: { drone: str(obj?.swarmSettings?.droneModel), reviewer: str(obj?.swarmSettings?.reviewerModel) },
|
|
5506
|
+
// TASWM-395: уровни усилия — из того же ответа и с той же семантикой, что модели:
|
|
5507
|
+
// старый прод поля не отдаёт → оба null, то есть env машины и дефолт харнесса.
|
|
5508
|
+
// toEffortLevel заодно отсекает мусор, записанный в uiPrefs мимо схемы.
|
|
5509
|
+
efforts: {
|
|
5510
|
+
drone: toEffortLevel(obj?.swarmSettings?.droneEffort),
|
|
5511
|
+
reviewer: toEffortLevel(obj?.swarmSettings?.reviewerEffort)
|
|
5512
|
+
},
|
|
5513
|
+
// TASWM-377: потолок трат — из того же ответа, тем же одним чтением. НОЛЬ здесь
|
|
5514
|
+
// значим («гарантия выключена осознанно»), поэтому пропускаем и его, а не только
|
|
5515
|
+
// положительные: null остаётся ровно за «ручка не задана / старый прод», и только
|
|
5516
|
+
// он уводит спавн на env-фолбэк и дефолт ядра.
|
|
5517
|
+
maxCostUsd: typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : null
|
|
5323
5518
|
};
|
|
5324
5519
|
}
|
|
5325
5520
|
/**
|
|
@@ -5458,19 +5653,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5458
5653
|
if (res?.isError) return [];
|
|
5459
5654
|
const raw = (res.structuredContent ?? {}).hosts;
|
|
5460
5655
|
if (!Array.isArray(raw)) return [];
|
|
5461
|
-
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) =>
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5656
|
+
return raw.filter((h) => typeof h === "object" && h !== null).filter((h) => typeof h.hostId === "string" && h.hostId).map((h) => {
|
|
5657
|
+
const resources = parseHostResources(h.resources);
|
|
5658
|
+
return {
|
|
5659
|
+
hostId: h.hostId,
|
|
5660
|
+
hostName: typeof h.hostName === "string" && h.hostName ? h.hostName : h.hostId,
|
|
5661
|
+
lastSeenAt: typeof h.lastSeenAt === "string" ? h.lastSeenAt : null,
|
|
5662
|
+
// TASWM-298: репозитории машины — «на связи» ещё не значит «возьмёт эту задачу».
|
|
5663
|
+
// Прод старой версии поля не пришлёт → пустой список, строка просто без хвоста.
|
|
5664
|
+
reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : [],
|
|
5665
|
+
// TASWM-326: машина на паузе — «на связи, но работу не берёт». Прод старой
|
|
5666
|
+
// версии поля не пришлёт → undefined, строка выглядит как раньше.
|
|
5667
|
+
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
5668
|
+
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
5669
|
+
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
5670
|
+
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
5671
|
+
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
5672
|
+
...resources ? { resources } : {}
|
|
5673
|
+
};
|
|
5674
|
+
});
|
|
5474
5675
|
}
|
|
5475
5676
|
/**
|
|
5476
5677
|
* TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
|
|
@@ -5536,13 +5737,22 @@ var SwarmClient = class _SwarmClient {
|
|
|
5536
5737
|
* разное. Не знаем своего потолка (ещё не читали / sqlite) → ключ не шлём, прод не
|
|
5537
5738
|
* трогает прошлое значение — та же логика, что у repoFullNames.
|
|
5538
5739
|
*
|
|
5740
|
+
* TASWM-394: тем же пульсом уезжают РЕСУРСЫ машины (`resources` — процессор, память,
|
|
5741
|
+
* диск и время замера). Это не новое измерение, а ровно то, что демон УЖЕ посчитал для
|
|
5742
|
+
* своего гейта спавна: числа в интерфейсе обязаны совпадать с теми, по которым он
|
|
5743
|
+
* отказывается поднимать дрона. Без них «машина на связи, свободна» и «машина физически
|
|
5744
|
+
* не может поднять дрона» выглядят в проде одинаково — 12.08 это стоило полудня
|
|
5745
|
+
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
5746
|
+
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
5747
|
+
*
|
|
5539
5748
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
5540
5749
|
*/
|
|
5541
|
-
async heartbeat(daemonVersion, pause, capacity) {
|
|
5750
|
+
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
5542
5751
|
try {
|
|
5543
5752
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
5544
5753
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
5545
5754
|
if (capacity != null && Number.isFinite(capacity) && capacity > 0) args.capacity = Math.trunc(capacity);
|
|
5755
|
+
if (resources) args.resources = resources;
|
|
5546
5756
|
if (pause !== void 0) {
|
|
5547
5757
|
args.paused = !!pause?.paused;
|
|
5548
5758
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
@@ -5749,6 +5959,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5749
5959
|
area: null,
|
|
5750
5960
|
depends_on: [],
|
|
5751
5961
|
model: null,
|
|
5962
|
+
effort: null,
|
|
5963
|
+
// усилие рассуждения (TASWM-395) — прод-поле, заполняется ниже из ответа
|
|
5752
5964
|
cost_usd: 0,
|
|
5753
5965
|
tokens_in: 0,
|
|
5754
5966
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
@@ -5817,6 +6029,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5817
6029
|
local.area = pr.area ?? null;
|
|
5818
6030
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
5819
6031
|
local.model = pr.model ?? null;
|
|
6032
|
+
local.effort = toEffortLevel(pr.effort);
|
|
5820
6033
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
5821
6034
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
5822
6035
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
@@ -6654,7 +6867,10 @@ function hostLines(hosts, opts = {}) {
|
|
|
6654
6867
|
const here = selfHostId && h.hostId === selfHostId ? " \u2190 \u044D\u0442\u0430 \u043C\u0430\u0448\u0438\u043D\u0430" : "";
|
|
6655
6868
|
const repos = h.reposServed?.length ? `
|
|
6656
6869
|
\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438: ${h.reposServed.join(", ")}` : "";
|
|
6657
|
-
|
|
6870
|
+
const res = hostResourcesText(h.resources, nowMs);
|
|
6871
|
+
const load = res ? `
|
|
6872
|
+
${res}` : "";
|
|
6873
|
+
return ` ${h.hostName} ${hostStateText(h, nowMs)} (${h.hostId})${here}${load}${repos}`;
|
|
6658
6874
|
});
|
|
6659
6875
|
}
|
|
6660
6876
|
|
|
@@ -7254,7 +7470,8 @@ function timelineState(db2, now = Date.now(), overmindLog = []) {
|
|
|
7254
7470
|
heroRunId: r.hero_run_id,
|
|
7255
7471
|
reviewVerdict: r.review_verdict,
|
|
7256
7472
|
reviewPhase: r.review_phase,
|
|
7257
|
-
model: r.model
|
|
7473
|
+
model: r.model,
|
|
7474
|
+
effort: r.effort
|
|
7258
7475
|
});
|
|
7259
7476
|
}
|
|
7260
7477
|
const liveLanes = lanes.filter((l) => l.live);
|
|
@@ -8730,17 +8947,25 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8730
8947
|
}
|
|
8731
8948
|
|
|
8732
8949
|
// src/swarmModels.ts
|
|
8950
|
+
init_src();
|
|
8951
|
+
function pick(v) {
|
|
8952
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8953
|
+
return s.length > 0 ? s : null;
|
|
8954
|
+
}
|
|
8955
|
+
function forKind(kind, swarm, norm) {
|
|
8956
|
+
if (!swarm) return null;
|
|
8957
|
+
return kind === "reviewer" ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
8958
|
+
}
|
|
8733
8959
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
const fromSwarm = run.kind === "reviewer" ? pick(swarm?.reviewer) ?? pick(swarm?.drone) : pick(swarm?.drone);
|
|
8739
|
-
return pick(run.model) ?? fromSwarm ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8960
|
+
return pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8961
|
+
}
|
|
8962
|
+
function resolveRunEffort(run, swarm, env = process.env) {
|
|
8963
|
+
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
8740
8964
|
}
|
|
8741
8965
|
|
|
8742
8966
|
// src/launcher.ts
|
|
8743
8967
|
init_executors();
|
|
8968
|
+
init_src();
|
|
8744
8969
|
var requireFromHere = createRequire(import.meta.url);
|
|
8745
8970
|
function resolveTsxCli() {
|
|
8746
8971
|
try {
|
|
@@ -8752,6 +8977,15 @@ function resolveTsxCli() {
|
|
|
8752
8977
|
}
|
|
8753
8978
|
}
|
|
8754
8979
|
var MCP_SERVER_NAME = "overmind";
|
|
8980
|
+
function launchBudgetUsd(limit, spentUsd, env = process.env) {
|
|
8981
|
+
return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
|
|
8982
|
+
}
|
|
8983
|
+
function envCapUsd(env) {
|
|
8984
|
+
const raw = env.SWARM_MAX_COST_USD;
|
|
8985
|
+
if (raw === void 0 || raw === "") return DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8986
|
+
const n = Number(raw);
|
|
8987
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_RUN_LIMITS.maxCostUsd;
|
|
8988
|
+
}
|
|
8755
8989
|
function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
8756
8990
|
const env = { ...base3 };
|
|
8757
8991
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
@@ -8877,6 +9111,12 @@ function makeLauncher(cfg) {
|
|
|
8877
9111
|
}
|
|
8878
9112
|
};
|
|
8879
9113
|
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
9114
|
+
const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
|
|
9115
|
+
try {
|
|
9116
|
+
cfg.recordEffort?.(run.id, effort);
|
|
9117
|
+
} catch (e) {
|
|
9118
|
+
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)}`);
|
|
9119
|
+
}
|
|
8880
9120
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8881
9121
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8882
9122
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8895,7 +9135,14 @@ function makeLauncher(cfg) {
|
|
|
8895
9135
|
// Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
|
|
8896
9136
|
// сидит в HOME демона, и запирать половину дверей — самообман.
|
|
8897
9137
|
denyPaths: cfg.droneIsolation ? dronePrivatePaths(realHome, cfg.droneIsolation.swarmHome) : [],
|
|
8898
|
-
realHome
|
|
9138
|
+
realHome,
|
|
9139
|
+
// TASWM-377: остаток бюджета ЭТОГО прогона — харнессу, чтобы он резал трату у себя, в
|
|
9140
|
+
// момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
|
|
9141
|
+
// стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
|
|
9142
|
+
// ни жёг, — отсюда и $74 при лимите $20.
|
|
9143
|
+
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
|
|
9144
|
+
// TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
|
|
9145
|
+
effort
|
|
8899
9146
|
});
|
|
8900
9147
|
let stdio = "ignore";
|
|
8901
9148
|
let outFd;
|