@taskless-app/swarm 0.6.32 → 0.6.33
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 +2 -2
- package/dist/overmind.js +389 -185
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -3764,9 +3764,9 @@ var init_procCleanup = __esm({
|
|
|
3764
3764
|
});
|
|
3765
3765
|
|
|
3766
3766
|
// src/git.ts
|
|
3767
|
-
import { existsSync as
|
|
3767
|
+
import { existsSync as existsSync12, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3768
3768
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3769
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3769
|
+
import { isAbsolute as isAbsolute2, join as join19, sep as sep2 } from "path";
|
|
3770
3770
|
import { $ as $2 } from "execa";
|
|
3771
3771
|
function gitNet(repo, args, timeoutMs) {
|
|
3772
3772
|
return withHardTimeout(
|
|
@@ -3789,7 +3789,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3789
3789
|
}
|
|
3790
3790
|
function isLinkedWorktree(dir) {
|
|
3791
3791
|
try {
|
|
3792
|
-
return statSync8(
|
|
3792
|
+
return statSync8(join19(dir, ".git")).isFile();
|
|
3793
3793
|
} catch {
|
|
3794
3794
|
return false;
|
|
3795
3795
|
}
|
|
@@ -3801,7 +3801,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
3801
3801
|
}
|
|
3802
3802
|
}
|
|
3803
3803
|
async function ensureWorktreeDirGone(worktree) {
|
|
3804
|
-
if (!
|
|
3804
|
+
if (!existsSync12(worktree)) return false;
|
|
3805
3805
|
if (!isLinkedWorktree(worktree)) {
|
|
3806
3806
|
console.error(
|
|
3807
3807
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -3816,7 +3816,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
3816
3816
|
);
|
|
3817
3817
|
return true;
|
|
3818
3818
|
}
|
|
3819
|
-
if (
|
|
3819
|
+
if (existsSync12(worktree)) {
|
|
3820
3820
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
3821
3821
|
}
|
|
3822
3822
|
return true;
|
|
@@ -3864,7 +3864,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3864
3864
|
}
|
|
3865
3865
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
3866
3866
|
if (busyAt) {
|
|
3867
|
-
const exists =
|
|
3867
|
+
const exists = existsSync12(busyAt);
|
|
3868
3868
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
3869
3869
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
3870
3870
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -3899,8 +3899,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3899
3899
|
try {
|
|
3900
3900
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3901
3901
|
if (!commonDir) return false;
|
|
3902
|
-
const infoDir =
|
|
3903
|
-
const excludeFile =
|
|
3902
|
+
const infoDir = join19(commonDir, "info");
|
|
3903
|
+
const excludeFile = join19(infoDir, "exclude");
|
|
3904
3904
|
let current = "";
|
|
3905
3905
|
try {
|
|
3906
3906
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3920,7 +3920,7 @@ async function gitPath(worktree, flag) {
|
|
|
3920
3920
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3921
3921
|
const raw = out.stdout.trim();
|
|
3922
3922
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3923
|
-
return isAbsolute2(raw) ? raw :
|
|
3923
|
+
return isAbsolute2(raw) ? raw : join19(worktree, raw);
|
|
3924
3924
|
}
|
|
3925
3925
|
async function ensureWorktreeHygiene(worktree) {
|
|
3926
3926
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3930,12 +3930,12 @@ async function installGuardHooks(worktree) {
|
|
|
3930
3930
|
try {
|
|
3931
3931
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3932
3932
|
if (!gitDir) return false;
|
|
3933
|
-
const hooksDir =
|
|
3933
|
+
const hooksDir = join19(gitDir, "swarm-hooks");
|
|
3934
3934
|
const prev = await previousHooksPath(worktree);
|
|
3935
3935
|
await mkdir(hooksDir, { recursive: true });
|
|
3936
3936
|
for (const hook of GUARD_HOOKS) {
|
|
3937
|
-
await writeFile(
|
|
3938
|
-
await chmod(
|
|
3937
|
+
await writeFile(join19(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3938
|
+
await chmod(join19(hooksDir, hook), 493);
|
|
3939
3939
|
}
|
|
3940
3940
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3941
3941
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3954,7 +3954,7 @@ async function previousHooksPath(worktree) {
|
|
|
3954
3954
|
if (out.exitCode === 0 && value) return value;
|
|
3955
3955
|
}
|
|
3956
3956
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3957
|
-
return commonDir ?
|
|
3957
|
+
return commonDir ? join19(commonDir, "hooks") : null;
|
|
3958
3958
|
}
|
|
3959
3959
|
async function removeWorktree(repo, path) {
|
|
3960
3960
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3979,7 +3979,7 @@ async function worktreeDirty(path) {
|
|
|
3979
3979
|
async function repairMovedWorktrees(targetDir) {
|
|
3980
3980
|
const subdirs = (dir) => {
|
|
3981
3981
|
try {
|
|
3982
|
-
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
3982
|
+
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join19(dir, d.name));
|
|
3983
3983
|
} catch {
|
|
3984
3984
|
return [];
|
|
3985
3985
|
}
|
|
@@ -4096,6 +4096,7 @@ var init_git = __esm({
|
|
|
4096
4096
|
var droneChannel_exports = {};
|
|
4097
4097
|
__export(droneChannel_exports, {
|
|
4098
4098
|
ChannelUnavailableError: () => ChannelUnavailableError,
|
|
4099
|
+
PATCHABLE: () => PATCHABLE,
|
|
4099
4100
|
channelDroneStore: () => channelDroneStore,
|
|
4100
4101
|
closeDroneChannel: () => closeDroneChannel,
|
|
4101
4102
|
loadChannelSecret: () => loadChannelSecret,
|
|
@@ -4108,18 +4109,18 @@ __export(droneChannel_exports, {
|
|
|
4108
4109
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
4109
4110
|
});
|
|
4110
4111
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
4111
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as
|
|
4112
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
4112
4113
|
import { dirname as dirname4 } from "path";
|
|
4113
4114
|
import { serve } from "@hono/node-server";
|
|
4114
4115
|
function loadChannelSecret(path) {
|
|
4115
4116
|
try {
|
|
4116
|
-
const s =
|
|
4117
|
+
const s = readFileSync18(path, "utf8").trim();
|
|
4117
4118
|
if (s.length >= 32) return s;
|
|
4118
4119
|
} catch {
|
|
4119
4120
|
}
|
|
4120
4121
|
const secret = randomBytes(32).toString("hex");
|
|
4121
4122
|
mkdirSync9(dirname4(path), { recursive: true });
|
|
4122
|
-
|
|
4123
|
+
writeFileSync12(path, secret, { mode: 384 });
|
|
4123
4124
|
try {
|
|
4124
4125
|
chmodSync2(path, 384);
|
|
4125
4126
|
} catch {
|
|
@@ -4179,6 +4180,16 @@ function invalidPatchValue(field, v) {
|
|
|
4179
4180
|
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
|
|
4180
4181
|
case "pr_url":
|
|
4181
4182
|
return v === null || typeof v === "string" ? null : "pr_url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439";
|
|
4183
|
+
// TASWM-532: имя ветки уходит в аргументы git у ревьюера и резолвера, поэтому проверяем
|
|
4184
|
+
// форму, а не только тип: пустая строка и пробелы внутри — это не ref, а сбой чтения.
|
|
4185
|
+
case "branch":
|
|
4186
|
+
return typeof v === "string" && v.length > 0 && v.length <= BRANCH_LEN_MAX && !BAD_IN_BRANCH.test(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0438\u043C\u044F \u0432\u0435\u0442\u043A\u0438: ${String(v).slice(0, 80)}`;
|
|
4187
|
+
case "stop_reason":
|
|
4188
|
+
return v === null || typeof v === "string" && v.length <= STOP_REASON_LEN_MAX ? null : "stop_reason \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439 (\u0438\u043B\u0438 null) \u0432 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u0434\u043B\u0438\u043D\u044B";
|
|
4189
|
+
// Словарь кодов возврата один на весь продукт (swarm-core) — проверяем его же функцией,
|
|
4190
|
+
// как verify_verdict выше, а не повторяем литералы ещё одним списком.
|
|
4191
|
+
case "return_reason":
|
|
4192
|
+
return v === null || isReturnReason(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u043A\u043E\u0434 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430: ${String(v)}`;
|
|
4182
4193
|
case "changed_paths": {
|
|
4183
4194
|
if (!Array.isArray(v)) return "changed_paths \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C \u043F\u0443\u0442\u0435\u0439";
|
|
4184
4195
|
if (v.length > CHANGED_PATHS_MAX) return `changed_paths: \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u0443\u0442\u0435\u0439 (${v.length} > ${CHANGED_PATHS_MAX})`;
|
|
@@ -4242,10 +4253,18 @@ function makeChannelHandler(deps) {
|
|
|
4242
4253
|
if (id == null) return bad(403, "\u043F\u0440\u043E\u0433\u043E\u043D \u0432\u043D\u0435 \u0441\u043A\u043E\u0443\u043F\u0430 \u043A\u0430\u043D\u0430\u043B\u0430");
|
|
4243
4254
|
const raw = p.patch ?? {};
|
|
4244
4255
|
const patch = {};
|
|
4256
|
+
const refuse = (status, why) => {
|
|
4257
|
+
log(`[\u043A\u0430\u043D\u0430\u043B] \u043F\u0430\u0442\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${id} \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (${status}): ${why}`);
|
|
4258
|
+
try {
|
|
4259
|
+
deps.store.addEvent(id, "step", `\u043A\u0430\u043D\u0430\u043B \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0430\u0442\u0447 \u0434\u0440\u043E\u043D\u0430 (${status}): ${why}`, null);
|
|
4260
|
+
} catch {
|
|
4261
|
+
}
|
|
4262
|
+
return bad(status, why);
|
|
4263
|
+
};
|
|
4245
4264
|
for (const [k, v] of Object.entries(raw)) {
|
|
4246
|
-
if (!PATCHABLE.has(k)) return
|
|
4265
|
+
if (!PATCHABLE.has(k)) return refuse(403, `\u043F\u043E\u043B\u0435 \xAB${k}\xBB \u043A\u0430\u043D\u0430\u043B\u0443 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F`);
|
|
4247
4266
|
const why = invalidPatchValue(k, v);
|
|
4248
|
-
if (why) return
|
|
4267
|
+
if (why) return refuse(400, why);
|
|
4249
4268
|
patch[k] = v;
|
|
4250
4269
|
}
|
|
4251
4270
|
deps.store.updateRun(id, patch);
|
|
@@ -4438,7 +4457,7 @@ function channelDroneStore(opts) {
|
|
|
4438
4457
|
openQuestionsFor: async () => (await call("openQuestionsFor")).questions
|
|
4439
4458
|
};
|
|
4440
4459
|
}
|
|
4441
|
-
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4460
|
+
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, BRANCH_LEN_MAX, STOP_REASON_LEN_MAX, BAD_IN_BRANCH, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4442
4461
|
var init_droneChannel = __esm({
|
|
4443
4462
|
"src/droneChannel.ts"() {
|
|
4444
4463
|
"use strict";
|
|
@@ -4458,10 +4477,24 @@ var init_droneChannel = __esm({
|
|
|
4458
4477
|
// worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
|
|
4459
4478
|
// pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
|
|
4460
4479
|
// сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
|
|
4461
|
-
"changed_paths"
|
|
4480
|
+
"changed_paths",
|
|
4481
|
+
// TASWM-532: ФАКТИЧЕСКАЯ ветка сданного PR. Её подмешивает в патч сдачи не модель, а наш код
|
|
4482
|
+
// (droneServer.done → branchFromPrPatch по ответу `gh pr view --json headRefName`) — тот же
|
|
4483
|
+
// довод, что у changed_paths строкой выше. «Ветка и так известна из прогона» — неправда:
|
|
4484
|
+
// поле пишется на спавне из слага задачи, а дрон мог продолжать ЧУЖОЙ PR, и по этому полю
|
|
4485
|
+
// ревьюер строит worktree `origin/<branch>` (TASWM-356).
|
|
4486
|
+
"branch",
|
|
4487
|
+
// TASWM-532: возврат задачи автору (`return_task`) пишет их одним патчем со статусом
|
|
4488
|
+
// "returned" — проза для ленты задачи и код причины для распределения по бэклогу
|
|
4489
|
+
// (TASWM-505). Без них 403 ложился на возврат ровно так же, как на сдачу.
|
|
4490
|
+
"stop_reason",
|
|
4491
|
+
"return_reason"
|
|
4462
4492
|
]);
|
|
4463
4493
|
CHANGED_PATHS_MAX = 1e3;
|
|
4464
4494
|
CHANGED_PATH_LEN_MAX = 1024;
|
|
4495
|
+
BRANCH_LEN_MAX = 255;
|
|
4496
|
+
STOP_REASON_LEN_MAX = 2e3;
|
|
4497
|
+
BAD_IN_BRANCH = /[\s~^:?*[\\\p{Cc}]/u;
|
|
4465
4498
|
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
4466
4499
|
ChannelUnavailableError = class extends Error {
|
|
4467
4500
|
constructor(downMs, message) {
|
|
@@ -4477,7 +4510,7 @@ var init_droneChannel = __esm({
|
|
|
4477
4510
|
|
|
4478
4511
|
// src/attachments.ts
|
|
4479
4512
|
import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4480
|
-
import { join as
|
|
4513
|
+
import { join as join23 } from "path";
|
|
4481
4514
|
function attachmentsFromDoc(doc, origin) {
|
|
4482
4515
|
const out = [];
|
|
4483
4516
|
const walk2 = (node) => {
|
|
@@ -4568,7 +4601,7 @@ async function materializeAttachments(opts) {
|
|
|
4568
4601
|
};
|
|
4569
4602
|
const result = { files: [], skipped: [] };
|
|
4570
4603
|
if (opts.refs.length === 0) return result;
|
|
4571
|
-
const dir =
|
|
4604
|
+
const dir = join23(opts.worktree, ATTACHMENTS_DIR);
|
|
4572
4605
|
await io.removeDir(dir);
|
|
4573
4606
|
let dirReady = false;
|
|
4574
4607
|
let total = 0;
|
|
@@ -4614,7 +4647,7 @@ async function materializeAttachments(opts) {
|
|
|
4614
4647
|
await io.mkdir(dir);
|
|
4615
4648
|
dirReady = true;
|
|
4616
4649
|
}
|
|
4617
|
-
await io.writeFile(
|
|
4650
|
+
await io.writeFile(join23(dir, name), file.bytes);
|
|
4618
4651
|
} catch (e) {
|
|
4619
4652
|
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText2(e)})`);
|
|
4620
4653
|
continue;
|
|
@@ -4767,7 +4800,7 @@ var init_attachments = __esm({
|
|
|
4767
4800
|
|
|
4768
4801
|
// src/shotUpload.ts
|
|
4769
4802
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4770
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
4803
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join24 } from "path";
|
|
4771
4804
|
function shotMimetype(path) {
|
|
4772
4805
|
const dot = path.lastIndexOf(".");
|
|
4773
4806
|
if (dot < 0) return null;
|
|
@@ -4793,7 +4826,7 @@ async function readShots(paths, opts) {
|
|
|
4793
4826
|
}
|
|
4794
4827
|
let bytes;
|
|
4795
4828
|
try {
|
|
4796
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4829
|
+
bytes = await read(isAbsolute3(path) ? path : join24(opts.worktree, path));
|
|
4797
4830
|
} catch (e) {
|
|
4798
4831
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
4799
4832
|
continue;
|
|
@@ -4874,8 +4907,8 @@ var init_shotUpload = __esm({
|
|
|
4874
4907
|
|
|
4875
4908
|
// src/oauth.ts
|
|
4876
4909
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
4877
|
-
import { readFileSync as
|
|
4878
|
-
import { join as
|
|
4910
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, unlinkSync as unlinkSync4 } from "fs";
|
|
4911
|
+
import { join as join25 } from "path";
|
|
4879
4912
|
function makePkce() {
|
|
4880
4913
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4881
4914
|
const challenge = createHash3("sha256").update(verifier).digest("base64url");
|
|
@@ -4959,13 +4992,13 @@ function toStore(clientId, t, now) {
|
|
|
4959
4992
|
}
|
|
4960
4993
|
function loadStore() {
|
|
4961
4994
|
try {
|
|
4962
|
-
return JSON.parse(
|
|
4995
|
+
return JSON.parse(readFileSync19(STORE_PATH2, "utf8"));
|
|
4963
4996
|
} catch {
|
|
4964
4997
|
return null;
|
|
4965
4998
|
}
|
|
4966
4999
|
}
|
|
4967
5000
|
function saveStore(s) {
|
|
4968
|
-
|
|
5001
|
+
writeFileSync13(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
4969
5002
|
}
|
|
4970
5003
|
function clearStore() {
|
|
4971
5004
|
try {
|
|
@@ -4975,7 +5008,7 @@ function clearStore() {
|
|
|
4975
5008
|
}
|
|
4976
5009
|
function loadClientId() {
|
|
4977
5010
|
try {
|
|
4978
|
-
return JSON.parse(
|
|
5011
|
+
return JSON.parse(readFileSync19(CLIENT_PATH, "utf8")).clientId;
|
|
4979
5012
|
} catch {
|
|
4980
5013
|
return null;
|
|
4981
5014
|
}
|
|
@@ -4984,7 +5017,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
4984
5017
|
const existing = loadClientId();
|
|
4985
5018
|
if (existing) return existing;
|
|
4986
5019
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
4987
|
-
|
|
5020
|
+
writeFileSync13(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
4988
5021
|
return clientId;
|
|
4989
5022
|
}
|
|
4990
5023
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -5010,8 +5043,8 @@ var init_oauth = __esm({
|
|
|
5010
5043
|
AUTHORIZE = "/api/oauth/authorize";
|
|
5011
5044
|
TOKEN = "/api/oauth/token";
|
|
5012
5045
|
SCOPE = "openid profile email taskless:full";
|
|
5013
|
-
STORE_PATH2 =
|
|
5014
|
-
CLIENT_PATH =
|
|
5046
|
+
STORE_PATH2 = join25(SWARM_HOME, "oauth.json");
|
|
5047
|
+
CLIENT_PATH = join25(SWARM_HOME, "oauth-client.json");
|
|
5015
5048
|
base2 = (api) => api.replace(/\/$/, "");
|
|
5016
5049
|
}
|
|
5017
5050
|
});
|
|
@@ -5057,11 +5090,11 @@ var init_authState = __esm({
|
|
|
5057
5090
|
});
|
|
5058
5091
|
|
|
5059
5092
|
// src/workspace.ts
|
|
5060
|
-
import { readFileSync as
|
|
5061
|
-
import { join as
|
|
5093
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5 } from "fs";
|
|
5094
|
+
import { join as join26 } from "path";
|
|
5062
5095
|
function loadWorkspace() {
|
|
5063
5096
|
try {
|
|
5064
|
-
const w = JSON.parse(
|
|
5097
|
+
const w = JSON.parse(readFileSync20(WORKSPACE_PATH, "utf8"));
|
|
5065
5098
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
5066
5099
|
} catch {
|
|
5067
5100
|
return null;
|
|
@@ -5075,7 +5108,7 @@ var init_workspace = __esm({
|
|
|
5075
5108
|
"src/workspace.ts"() {
|
|
5076
5109
|
"use strict";
|
|
5077
5110
|
init_config();
|
|
5078
|
-
WORKSPACE_PATH =
|
|
5111
|
+
WORKSPACE_PATH = join26(SWARM_HOME, "workspace.json");
|
|
5079
5112
|
}
|
|
5080
5113
|
});
|
|
5081
5114
|
|
|
@@ -5656,7 +5689,7 @@ __export(db_exports, {
|
|
|
5656
5689
|
});
|
|
5657
5690
|
import { createRequire as createRequire2 } from "module";
|
|
5658
5691
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5659
|
-
import { existsSync as
|
|
5692
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5660
5693
|
import { resolve as resolve3 } from "path";
|
|
5661
5694
|
function rowToQuestion(row) {
|
|
5662
5695
|
return {
|
|
@@ -6279,7 +6312,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6279
6312
|
const repoN = normalizeRepo(repo);
|
|
6280
6313
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
6281
6314
|
if (already) return { imported: false, runs: already.run_count };
|
|
6282
|
-
if (!
|
|
6315
|
+
if (!existsSync15(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
6283
6316
|
const src = new this.databaseCtor(abs);
|
|
6284
6317
|
try {
|
|
6285
6318
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -6648,8 +6681,8 @@ var init_droneStore = __esm({
|
|
|
6648
6681
|
|
|
6649
6682
|
// src/overmind.ts
|
|
6650
6683
|
import { homedir as homedir6 } from "os";
|
|
6651
|
-
import { existsSync as
|
|
6652
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6684
|
+
import { existsSync as existsSync20, readFileSync as readFileSync24, readdirSync as readdirSync13, renameSync as renameSync12 } from "fs";
|
|
6685
|
+
import { basename as basename6, dirname as dirname5, join as join33 } from "path";
|
|
6653
6686
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6654
6687
|
import { spawn as spawn4 } from "child_process";
|
|
6655
6688
|
|
|
@@ -9988,7 +10021,13 @@ var DEFAULT_RELIEF_CONFIG = {
|
|
|
9988
10021
|
function resolveReliefConfig(cfg) {
|
|
9989
10022
|
return { ...DEFAULT_RELIEF_CONFIG, ...cfg ?? {} };
|
|
9990
10023
|
}
|
|
9991
|
-
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM
|
|
10024
|
+
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM), \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043F\u043E\u0441\u043B\u0435 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 (TASWM-151)`;
|
|
10025
|
+
function reliefPauseReason(canContinue) {
|
|
10026
|
+
return canContinue ? `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 --continue: worktree \u0438 \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435)` : `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E)`;
|
|
10027
|
+
}
|
|
10028
|
+
function isReliefPause(stopReason) {
|
|
10029
|
+
return !!stopReason && stopReason.startsWith(RELIEF_PAUSE_MARK);
|
|
10030
|
+
}
|
|
9992
10031
|
function reliefResumeReason(mode) {
|
|
9993
10032
|
return mode === "continue" ? `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 (--continue: \u0440\u0430\u0431\u043E\u0442\u0430 \u0432 origin + \u0441\u0435\u0441\u0441\u0438\u044F \u0446\u0435\u043B\u044B)` : `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443, \u0441\u0442\u0430\u0440\u0442 \u0437\u0430\u043D\u043E\u0432\u043E (\u0441\u0435\u0441\u0441\u0438\u044F/worktree \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B)`;
|
|
9994
10033
|
}
|
|
@@ -10012,6 +10051,115 @@ function pickResumeCandidate(pausedForRelief) {
|
|
|
10012
10051
|
return candidates.reduce((oldest, r) => r.updated_at < oldest.updated_at ? r : oldest);
|
|
10013
10052
|
}
|
|
10014
10053
|
|
|
10054
|
+
// src/reliefLoop.ts
|
|
10055
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
10056
|
+
import { join as join14 } from "path";
|
|
10057
|
+
var numEnv3 = (name, def) => {
|
|
10058
|
+
const v = Number(process.env[name]);
|
|
10059
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
10060
|
+
};
|
|
10061
|
+
var DEFAULT_RELIEF_LOOP_CONFIG = {
|
|
10062
|
+
limit: numEnv3("SWARM_RELIEF_LOOP_LIMIT", 3),
|
|
10063
|
+
backoffMaxMs: numEnv3("SWARM_RELIEF_BACKOFF_MAX_MS", 10 * 6e4),
|
|
10064
|
+
seriesTtlMs: numEnv3("SWARM_RELIEF_LOOP_TTL_MS", 6 * 60 * 6e4)
|
|
10065
|
+
};
|
|
10066
|
+
function resolveReliefLoopConfig(cfg) {
|
|
10067
|
+
return { ...DEFAULT_RELIEF_LOOP_CONFIG, ...cfg ?? {} };
|
|
10068
|
+
}
|
|
10069
|
+
function reliefLoopKey(repo, slug) {
|
|
10070
|
+
return `${repo}#${slug}`;
|
|
10071
|
+
}
|
|
10072
|
+
function recordReliefLoopPause(state, key, now, delivered, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10073
|
+
const prev = state[key];
|
|
10074
|
+
const continues = !!prev && !delivered && now - prev.lastPauseAt <= cfg.seriesTtlMs;
|
|
10075
|
+
const entry = continues ? { pauses: prev.pauses + 1, lastPauseAt: now, ...prev.escalated ? { escalated: true } : {} } : { pauses: 1, lastPauseAt: now };
|
|
10076
|
+
state[key] = entry;
|
|
10077
|
+
return entry;
|
|
10078
|
+
}
|
|
10079
|
+
function clearReliefLoop(state, key) {
|
|
10080
|
+
delete state[key];
|
|
10081
|
+
}
|
|
10082
|
+
function reliefBackoffMs(pauses, baseMs, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10083
|
+
if (pauses <= 1) return baseMs;
|
|
10084
|
+
const grown = baseMs * 2 ** Math.min(pauses - 1, 30);
|
|
10085
|
+
return Math.min(grown, cfg.backoffMaxMs);
|
|
10086
|
+
}
|
|
10087
|
+
function reliefLoopTripped(entry, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10088
|
+
return !!entry && entry.pauses >= cfg.limit;
|
|
10089
|
+
}
|
|
10090
|
+
function reliefLoopStatePath(home) {
|
|
10091
|
+
return join14(home, "relief-loop.json");
|
|
10092
|
+
}
|
|
10093
|
+
function loadReliefLoopState(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10094
|
+
const exists = io.exists ?? existsSync10;
|
|
10095
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync13(p, "utf8"));
|
|
10096
|
+
const path = reliefLoopStatePath(home);
|
|
10097
|
+
if (!exists(path)) return {};
|
|
10098
|
+
try {
|
|
10099
|
+
const raw = JSON.parse(readFile2(path));
|
|
10100
|
+
const out = {};
|
|
10101
|
+
for (const [key, value] of Object.entries(raw ?? {})) {
|
|
10102
|
+
const v = value;
|
|
10103
|
+
const pauses = Number(v?.pauses);
|
|
10104
|
+
const lastPauseAt = Number(v?.lastPauseAt);
|
|
10105
|
+
if (!Number.isFinite(pauses) || pauses <= 0 || !Number.isFinite(lastPauseAt)) continue;
|
|
10106
|
+
if (now - lastPauseAt > cfg.seriesTtlMs) continue;
|
|
10107
|
+
out[key] = { pauses, lastPauseAt, ...v?.escalated ? { escalated: true } : {} };
|
|
10108
|
+
}
|
|
10109
|
+
return out;
|
|
10110
|
+
} catch {
|
|
10111
|
+
return {};
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
function saveReliefLoopState(home, state, io = {}) {
|
|
10115
|
+
const path = reliefLoopStatePath(home);
|
|
10116
|
+
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
10117
|
+
writeFileSync11(`${p}.tmp`, data);
|
|
10118
|
+
renameSync8(`${p}.tmp`, p);
|
|
10119
|
+
});
|
|
10120
|
+
writeFile4(path, JSON.stringify(state, null, 2));
|
|
10121
|
+
}
|
|
10122
|
+
var ReliefLoopStore = class {
|
|
10123
|
+
constructor(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10124
|
+
this.home = home;
|
|
10125
|
+
this.io = io;
|
|
10126
|
+
this.cfg = cfg;
|
|
10127
|
+
this.state = loadReliefLoopState(home, now, io, cfg);
|
|
10128
|
+
}
|
|
10129
|
+
home;
|
|
10130
|
+
io;
|
|
10131
|
+
cfg;
|
|
10132
|
+
state;
|
|
10133
|
+
get(key) {
|
|
10134
|
+
return this.state[key];
|
|
10135
|
+
}
|
|
10136
|
+
/** Учесть снятие и сразу сохранить. Возвращает запись после учёта. */
|
|
10137
|
+
recordPause(key, now, delivered) {
|
|
10138
|
+
const entry = recordReliefLoopPause(this.state, key, now, delivered, this.cfg);
|
|
10139
|
+
this.flush();
|
|
10140
|
+
return entry;
|
|
10141
|
+
}
|
|
10142
|
+
/** Пометить, что человека уже позвали — чтобы не звать на каждом тике. */
|
|
10143
|
+
markEscalated(key) {
|
|
10144
|
+
const entry = this.state[key];
|
|
10145
|
+
if (!entry || entry.escalated) return;
|
|
10146
|
+
entry.escalated = true;
|
|
10147
|
+
this.flush();
|
|
10148
|
+
}
|
|
10149
|
+
clear(key) {
|
|
10150
|
+
if (!(key in this.state)) return;
|
|
10151
|
+
clearReliefLoop(this.state, key);
|
|
10152
|
+
this.flush();
|
|
10153
|
+
}
|
|
10154
|
+
flush() {
|
|
10155
|
+
try {
|
|
10156
|
+
saveReliefLoopState(this.home, this.state, this.io);
|
|
10157
|
+
} catch (e) {
|
|
10158
|
+
console.warn("[overmind] \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043F\u0435\u0442\u043B\u0438 \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0438 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C:", e);
|
|
10159
|
+
}
|
|
10160
|
+
}
|
|
10161
|
+
};
|
|
10162
|
+
|
|
10015
10163
|
// src/reliefCost.ts
|
|
10016
10164
|
function median(xs) {
|
|
10017
10165
|
const s = [...xs].sort((a, b) => a - b);
|
|
@@ -10128,15 +10276,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
10128
10276
|
|
|
10129
10277
|
// src/launcher.ts
|
|
10130
10278
|
import { spawn as spawn2 } from "child_process";
|
|
10131
|
-
import { closeSync as closeSync3, existsSync as
|
|
10279
|
+
import { closeSync as closeSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
|
|
10132
10280
|
import { createRequire } from "module";
|
|
10133
10281
|
import { homedir as homedir5 } from "os";
|
|
10134
|
-
import { dirname as dirname3, join as
|
|
10282
|
+
import { dirname as dirname3, join as join16 } from "path";
|
|
10135
10283
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10136
10284
|
|
|
10137
10285
|
// src/droneHome.ts
|
|
10138
10286
|
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
10139
|
-
import { join as
|
|
10287
|
+
import { join as join15, relative, sep } from "path";
|
|
10140
10288
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
10141
10289
|
function droneHomeDeny(env = process.env) {
|
|
10142
10290
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -10160,20 +10308,20 @@ function buildDroneHome(opts) {
|
|
|
10160
10308
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
10161
10309
|
for (const existing of readdirSync7(droneHome)) {
|
|
10162
10310
|
if (wanted.has(existing)) continue;
|
|
10163
|
-
const p =
|
|
10311
|
+
const p = join15(droneHome, existing);
|
|
10164
10312
|
try {
|
|
10165
10313
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
10166
10314
|
} catch {
|
|
10167
10315
|
}
|
|
10168
10316
|
}
|
|
10169
10317
|
for (const name of wanted) {
|
|
10170
|
-
const link2 =
|
|
10318
|
+
const link2 = join15(droneHome, name);
|
|
10171
10319
|
try {
|
|
10172
10320
|
lstatSync(link2);
|
|
10173
10321
|
continue;
|
|
10174
10322
|
} catch {
|
|
10175
10323
|
}
|
|
10176
|
-
const target =
|
|
10324
|
+
const target = join15(realHome, name);
|
|
10177
10325
|
let isDir;
|
|
10178
10326
|
try {
|
|
10179
10327
|
isDir = statSync7(target).isDirectory();
|
|
@@ -10194,7 +10342,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
10194
10342
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
10195
10343
|
];
|
|
10196
10344
|
function realPathEnvPins(realHome) {
|
|
10197
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10345
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join15(realHome, ...segments)]));
|
|
10198
10346
|
}
|
|
10199
10347
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
10200
10348
|
return [
|
|
@@ -10306,7 +10454,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10306
10454
|
const env = { ...base3 };
|
|
10307
10455
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10308
10456
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10309
|
-
env.TURBO_CACHE_DIR =
|
|
10457
|
+
env.TURBO_CACHE_DIR = join16(worktree, ".turbo", "cache");
|
|
10310
10458
|
}
|
|
10311
10459
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10312
10460
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10334,13 +10482,13 @@ var DAEMON_STATE_ENV = [
|
|
|
10334
10482
|
"TASKLESS_TOKEN"
|
|
10335
10483
|
];
|
|
10336
10484
|
function bundledRoleDir(kind) {
|
|
10337
|
-
return
|
|
10485
|
+
return join16(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
10338
10486
|
}
|
|
10339
10487
|
function roleDir(worktree, kind) {
|
|
10340
|
-
return
|
|
10488
|
+
return join16(worktree, ".claude", "roles", kind);
|
|
10341
10489
|
}
|
|
10342
10490
|
function roleDirs(worktree, kind) {
|
|
10343
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10491
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync11(dir));
|
|
10344
10492
|
}
|
|
10345
10493
|
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
10346
10494
|
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
@@ -10356,7 +10504,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
10356
10504
|
const m = /^(\d+)\.json/.exec(f);
|
|
10357
10505
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10358
10506
|
}
|
|
10359
|
-
return
|
|
10507
|
+
return join16(dir, `${next}.json`);
|
|
10360
10508
|
}
|
|
10361
10509
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10362
10510
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -10439,9 +10587,9 @@ function makeLauncher(cfg) {
|
|
|
10439
10587
|
}
|
|
10440
10588
|
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
10441
10589
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10442
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10590
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(join16(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10443
10591
|
console.error(
|
|
10444
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
10592
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join16(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
10445
10593
|
);
|
|
10446
10594
|
}
|
|
10447
10595
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10502,8 +10650,8 @@ function makeLauncher(cfg) {
|
|
|
10502
10650
|
init_procCleanup();
|
|
10503
10651
|
|
|
10504
10652
|
// src/cost.ts
|
|
10505
|
-
import { readFileSync as
|
|
10506
|
-
import { join as
|
|
10653
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync9, renameSync as renameSync9 } from "fs";
|
|
10654
|
+
import { join as join17 } from "path";
|
|
10507
10655
|
init_executors();
|
|
10508
10656
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
10509
10657
|
function unprocessedLogFiles(dir) {
|
|
@@ -10525,10 +10673,10 @@ function accountRunCost(deps, run) {
|
|
|
10525
10673
|
let durationMs = 0;
|
|
10526
10674
|
const processed = [];
|
|
10527
10675
|
for (const f of files) {
|
|
10528
|
-
const full =
|
|
10676
|
+
const full = join17(dir, f);
|
|
10529
10677
|
let raw = null;
|
|
10530
10678
|
try {
|
|
10531
|
-
raw =
|
|
10679
|
+
raw = readFileSync14(full, "utf8");
|
|
10532
10680
|
} catch {
|
|
10533
10681
|
continue;
|
|
10534
10682
|
}
|
|
@@ -10554,7 +10702,7 @@ function accountRunCost(deps, run) {
|
|
|
10554
10702
|
}
|
|
10555
10703
|
for (const full of processed) {
|
|
10556
10704
|
try {
|
|
10557
|
-
|
|
10705
|
+
renameSync9(full, `${full}.done`);
|
|
10558
10706
|
} catch {
|
|
10559
10707
|
}
|
|
10560
10708
|
}
|
|
@@ -10577,8 +10725,8 @@ function makeCostSweeper(deps) {
|
|
|
10577
10725
|
}
|
|
10578
10726
|
|
|
10579
10727
|
// src/runOutcome.ts
|
|
10580
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
10581
|
-
import { join as
|
|
10728
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync15 } from "fs";
|
|
10729
|
+
import { join as join18 } from "path";
|
|
10582
10730
|
init_executors();
|
|
10583
10731
|
function endedItself(outcome) {
|
|
10584
10732
|
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
@@ -10605,7 +10753,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
10605
10753
|
}
|
|
10606
10754
|
if (!latest) return null;
|
|
10607
10755
|
try {
|
|
10608
|
-
return
|
|
10756
|
+
return readFileSync15(join18(dir, latest.name), "utf8");
|
|
10609
10757
|
} catch {
|
|
10610
10758
|
return null;
|
|
10611
10759
|
}
|
|
@@ -10638,20 +10786,20 @@ init_src();
|
|
|
10638
10786
|
init_git();
|
|
10639
10787
|
|
|
10640
10788
|
// src/deps.ts
|
|
10641
|
-
import { existsSync as
|
|
10642
|
-
import { join as
|
|
10789
|
+
import { existsSync as existsSync14, readdirSync as readdirSync12, readFileSync as readFileSync17 } from "fs";
|
|
10790
|
+
import { join as join22 } from "path";
|
|
10643
10791
|
import { execa } from "execa";
|
|
10644
10792
|
|
|
10645
10793
|
// src/prepareLock.ts
|
|
10646
10794
|
import { createHash } from "crypto";
|
|
10647
10795
|
import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
|
|
10648
|
-
import { join as
|
|
10796
|
+
import { join as join20 } from "path";
|
|
10649
10797
|
import { tmpdir } from "os";
|
|
10650
10798
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
10651
10799
|
var POLL_MS = 300;
|
|
10652
10800
|
var STALE_MS = 20 * 6e4;
|
|
10653
10801
|
function machineLockPath(key, dir = tmpdir()) {
|
|
10654
|
-
return
|
|
10802
|
+
return join20(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
10655
10803
|
}
|
|
10656
10804
|
function defaultTryCreate(path, content) {
|
|
10657
10805
|
try {
|
|
@@ -10712,8 +10860,8 @@ init_config();
|
|
|
10712
10860
|
init_config();
|
|
10713
10861
|
import { createHash as createHash2 } from "crypto";
|
|
10714
10862
|
import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
10715
|
-
import { existsSync as
|
|
10716
|
-
import { join as
|
|
10863
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
10864
|
+
import { join as join21 } from "path";
|
|
10717
10865
|
function fingerprint(parts) {
|
|
10718
10866
|
const h = createHash2("sha256");
|
|
10719
10867
|
for (const p of parts) {
|
|
@@ -10727,8 +10875,8 @@ async function cloneTree(src, dst) {
|
|
|
10727
10875
|
const entries = await readdir(src, { withFileTypes: true });
|
|
10728
10876
|
await Promise.all(
|
|
10729
10877
|
entries.map(async (e) => {
|
|
10730
|
-
const from =
|
|
10731
|
-
const to =
|
|
10878
|
+
const from = join21(src, e.name);
|
|
10879
|
+
const to = join21(dst, e.name);
|
|
10732
10880
|
if (e.isDirectory()) return cloneTree(from, to);
|
|
10733
10881
|
if (e.isSymbolicLink()) {
|
|
10734
10882
|
return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
|
|
@@ -10741,11 +10889,11 @@ function cacheRoot(io) {
|
|
|
10741
10889
|
return io.root ?? ENV_CACHE_DIR;
|
|
10742
10890
|
}
|
|
10743
10891
|
function snapshotDir(io, key) {
|
|
10744
|
-
return
|
|
10892
|
+
return join21(cacheRoot(io), key);
|
|
10745
10893
|
}
|
|
10746
10894
|
function readManifest(dir) {
|
|
10747
10895
|
try {
|
|
10748
|
-
const parsed = JSON.parse(
|
|
10896
|
+
const parsed = JSON.parse(readFileSync16(join21(dir, "manifest.json"), "utf8"));
|
|
10749
10897
|
if (!parsed || typeof parsed !== "object") return null;
|
|
10750
10898
|
const m = parsed;
|
|
10751
10899
|
if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
|
|
@@ -10757,11 +10905,11 @@ function readManifest(dir) {
|
|
|
10757
10905
|
}
|
|
10758
10906
|
}
|
|
10759
10907
|
function hasEnvSnapshot(key, io = {}) {
|
|
10760
|
-
const exists = io.exists ??
|
|
10761
|
-
return exists(
|
|
10908
|
+
const exists = io.exists ?? existsSync13;
|
|
10909
|
+
return exists(join21(snapshotDir(io, key), "manifest.json"));
|
|
10762
10910
|
}
|
|
10763
10911
|
async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
10764
|
-
const exists = io.exists ??
|
|
10912
|
+
const exists = io.exists ?? existsSync13;
|
|
10765
10913
|
const clone = io.clone ?? cloneTree;
|
|
10766
10914
|
const dir = snapshotDir(io, key);
|
|
10767
10915
|
const manifest = readManifest(dir);
|
|
@@ -10770,13 +10918,13 @@ async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
|
10770
10918
|
try {
|
|
10771
10919
|
for (let i = 0; i < manifest.dirs.length; i++) {
|
|
10772
10920
|
const rel = manifest.dirs[i] ?? "";
|
|
10773
|
-
const target =
|
|
10921
|
+
const target = join21(worktree, rel);
|
|
10774
10922
|
if (exists(target)) continue;
|
|
10775
|
-
await clone(
|
|
10923
|
+
await clone(join21(dir, "trees", String(i)), target);
|
|
10776
10924
|
made.push(rel);
|
|
10777
10925
|
}
|
|
10778
10926
|
} catch {
|
|
10779
|
-
for (const rel of made) await rm2(
|
|
10927
|
+
for (const rel of made) await rm2(join21(worktree, rel), { recursive: true, force: true }).catch(() => {
|
|
10780
10928
|
});
|
|
10781
10929
|
return null;
|
|
10782
10930
|
}
|
|
@@ -10789,19 +10937,19 @@ async function touch(dir) {
|
|
|
10789
10937
|
});
|
|
10790
10938
|
}
|
|
10791
10939
|
async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
|
|
10792
|
-
const exists = io.exists ??
|
|
10940
|
+
const exists = io.exists ?? existsSync13;
|
|
10793
10941
|
const clone = io.clone ?? cloneTree;
|
|
10794
10942
|
if (hasEnvSnapshot(key, io)) return true;
|
|
10795
|
-
const present = dirs.filter((rel) => exists(
|
|
10943
|
+
const present = dirs.filter((rel) => exists(join21(worktree, rel)));
|
|
10796
10944
|
if (!present.length) return false;
|
|
10797
10945
|
const root = cacheRoot(io);
|
|
10798
|
-
const tmp =
|
|
10946
|
+
const tmp = join21(root, `.tmp-${key}-${process.pid}`);
|
|
10799
10947
|
try {
|
|
10800
10948
|
await rm2(tmp, { recursive: true, force: true });
|
|
10801
|
-
await mkdir2(
|
|
10802
|
-
for (let i = 0; i < present.length; i++) await clone(
|
|
10949
|
+
await mkdir2(join21(tmp, "trees"), { recursive: true });
|
|
10950
|
+
for (let i = 0; i < present.length; i++) await clone(join21(worktree, present[i] ?? ""), join21(tmp, "trees", String(i)));
|
|
10803
10951
|
const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10804
|
-
await writeFile2(
|
|
10952
|
+
await writeFile2(join21(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
10805
10953
|
await rename(tmp, snapshotDir(io, key));
|
|
10806
10954
|
return true;
|
|
10807
10955
|
} catch {
|
|
@@ -10821,13 +10969,13 @@ async function pruneEnvSnapshots(io = {}) {
|
|
|
10821
10969
|
}
|
|
10822
10970
|
const withTime = [];
|
|
10823
10971
|
for (const name of names) {
|
|
10824
|
-
const s = await stat(
|
|
10972
|
+
const s = await stat(join21(root, name)).catch(() => null);
|
|
10825
10973
|
if (s) withTime.push({ name, mtime: s.mtimeMs });
|
|
10826
10974
|
}
|
|
10827
10975
|
withTime.sort((a, b) => b.mtime - a.mtime);
|
|
10828
10976
|
let removed = 0;
|
|
10829
10977
|
for (const { name } of withTime.slice(Math.max(0, keep))) {
|
|
10830
|
-
await rm2(
|
|
10978
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10831
10979
|
});
|
|
10832
10980
|
removed++;
|
|
10833
10981
|
}
|
|
@@ -10843,9 +10991,9 @@ async function sweepEnvCacheTemp(io = {}) {
|
|
|
10843
10991
|
}
|
|
10844
10992
|
let removed = 0;
|
|
10845
10993
|
for (const name of names) {
|
|
10846
|
-
const s = await lstat(
|
|
10994
|
+
const s = await lstat(join21(root, name)).catch(() => null);
|
|
10847
10995
|
if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
|
|
10848
|
-
await rm2(
|
|
10996
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10849
10997
|
});
|
|
10850
10998
|
removed++;
|
|
10851
10999
|
}
|
|
@@ -10872,7 +11020,7 @@ async function runCommand(cmd, worktree) {
|
|
|
10872
11020
|
}
|
|
10873
11021
|
function safeReadText(p) {
|
|
10874
11022
|
try {
|
|
10875
|
-
return
|
|
11023
|
+
return readFileSync17(p, "utf8");
|
|
10876
11024
|
} catch {
|
|
10877
11025
|
return null;
|
|
10878
11026
|
}
|
|
@@ -10912,21 +11060,21 @@ function overridePlan(override) {
|
|
|
10912
11060
|
};
|
|
10913
11061
|
}
|
|
10914
11062
|
function detectPackageManager(worktree, io = {}) {
|
|
10915
|
-
const exists = io.exists ??
|
|
11063
|
+
const exists = io.exists ?? existsSync14;
|
|
10916
11064
|
const readText = io.readText ?? safeReadText;
|
|
10917
|
-
const hasPkgJson = exists(
|
|
11065
|
+
const hasPkgJson = exists(join22(worktree, "package.json"));
|
|
10918
11066
|
if (!hasPkgJson) {
|
|
10919
|
-
if (exists(
|
|
11067
|
+
if (exists(join22(worktree, "deno.json")) || exists(join22(worktree, "deno.jsonc")) || exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10920
11068
|
return null;
|
|
10921
11069
|
}
|
|
10922
|
-
const pkg = readJson(readText,
|
|
11070
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
10923
11071
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
10924
11072
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
10925
|
-
if (exists(
|
|
10926
|
-
if (exists(
|
|
10927
|
-
if (exists(
|
|
10928
|
-
if (exists(
|
|
10929
|
-
if (exists(
|
|
11073
|
+
if (exists(join22(worktree, "bun.lock")) || exists(join22(worktree, "bun.lockb"))) return "bun";
|
|
11074
|
+
if (exists(join22(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
11075
|
+
if (exists(join22(worktree, "yarn.lock"))) return "yarn";
|
|
11076
|
+
if (exists(join22(worktree, "package-lock.json"))) return "npm";
|
|
11077
|
+
if (exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10930
11078
|
return "npm";
|
|
10931
11079
|
}
|
|
10932
11080
|
function jsInstallCommand(pm) {
|
|
@@ -10982,7 +11130,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
10982
11130
|
// конфигурация — дело сборки, не подготовки
|
|
10983
11131
|
];
|
|
10984
11132
|
function detectPlan(worktree, io) {
|
|
10985
|
-
const exists = io.exists ??
|
|
11133
|
+
const exists = io.exists ?? existsSync14;
|
|
10986
11134
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10987
11135
|
const pm = detectPackageManager(worktree, io);
|
|
10988
11136
|
if (pm) {
|
|
@@ -10990,7 +11138,7 @@ function detectPlan(worktree, io) {
|
|
|
10990
11138
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
10991
11139
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
10992
11140
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
10993
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
11141
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join22(worktree, "node_modules"), source: "detect" }
|
|
10994
11142
|
];
|
|
10995
11143
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
10996
11144
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -10999,12 +11147,12 @@ function detectPlan(worktree, io) {
|
|
|
10999
11147
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
11000
11148
|
}
|
|
11001
11149
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
11002
|
-
const hit = marker.files.some((f) => exists(
|
|
11150
|
+
const hit = marker.files.some((f) => exists(join22(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
11003
11151
|
if (!hit) continue;
|
|
11004
11152
|
let ecosystem = marker.ecosystem;
|
|
11005
11153
|
let command = marker.command;
|
|
11006
11154
|
for (const refine of marker.refine ?? []) {
|
|
11007
|
-
if (!exists(
|
|
11155
|
+
if (!exists(join22(worktree, refine.file))) continue;
|
|
11008
11156
|
ecosystem = refine.ecosystem;
|
|
11009
11157
|
command = refine.command;
|
|
11010
11158
|
break;
|
|
@@ -11061,12 +11209,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
11061
11209
|
function ciWorkflowPlan(worktree, io) {
|
|
11062
11210
|
const readText = io.readText ?? safeReadText;
|
|
11063
11211
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
11064
|
-
const dir =
|
|
11212
|
+
const dir = join22(worktree, ".github", "workflows");
|
|
11065
11213
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
11066
11214
|
const seen = /* @__PURE__ */ new Set();
|
|
11067
11215
|
const commands = [];
|
|
11068
11216
|
for (const name of files.sort()) {
|
|
11069
|
-
const yaml = readText(
|
|
11217
|
+
const yaml = readText(join22(dir, name));
|
|
11070
11218
|
if (yaml === null) continue;
|
|
11071
11219
|
for (const raw of workflowRunLines(yaml)) {
|
|
11072
11220
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -11081,7 +11229,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
11081
11229
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
11082
11230
|
}
|
|
11083
11231
|
function workspaceGlobs(worktree, readText) {
|
|
11084
|
-
const yaml = readText(
|
|
11232
|
+
const yaml = readText(join22(worktree, "pnpm-workspace.yaml"));
|
|
11085
11233
|
if (yaml !== null) {
|
|
11086
11234
|
const globs = [];
|
|
11087
11235
|
let inPackages = false;
|
|
@@ -11101,7 +11249,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
11101
11249
|
}
|
|
11102
11250
|
if (globs.length) return globs;
|
|
11103
11251
|
}
|
|
11104
|
-
const pkg = readJson(readText,
|
|
11252
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
11105
11253
|
const ws = pkg?.workspaces;
|
|
11106
11254
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
11107
11255
|
return list.filter((g) => typeof g === "string");
|
|
@@ -11112,14 +11260,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
11112
11260
|
if (glob.startsWith("!")) continue;
|
|
11113
11261
|
const star = glob.indexOf("*");
|
|
11114
11262
|
if (star === -1) {
|
|
11115
|
-
const dir =
|
|
11116
|
-
if (io.exists(
|
|
11263
|
+
const dir = join22(worktree, glob);
|
|
11264
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
11117
11265
|
continue;
|
|
11118
11266
|
}
|
|
11119
|
-
const base3 =
|
|
11267
|
+
const base3 = join22(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
11120
11268
|
for (const name of io.listDirs(base3)) {
|
|
11121
|
-
const dir =
|
|
11122
|
-
if (io.exists(
|
|
11269
|
+
const dir = join22(base3, name);
|
|
11270
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
11123
11271
|
}
|
|
11124
11272
|
}
|
|
11125
11273
|
return [...dirs];
|
|
@@ -11140,18 +11288,18 @@ function entryPaths(pkg) {
|
|
|
11140
11288
|
return out;
|
|
11141
11289
|
}
|
|
11142
11290
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
11143
|
-
const exists = io.exists ??
|
|
11291
|
+
const exists = io.exists ?? existsSync14;
|
|
11144
11292
|
const readText = io.readText ?? safeReadText;
|
|
11145
11293
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11146
11294
|
const globs = workspaceGlobs(worktree, readText);
|
|
11147
11295
|
if (!globs.length) return [];
|
|
11148
11296
|
const pkgs = [];
|
|
11149
11297
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
11150
|
-
const pkg = readJson(readText,
|
|
11298
|
+
const pkg = readJson(readText, join22(dir, "package.json"));
|
|
11151
11299
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
11152
11300
|
const scripts = pkg.scripts ?? {};
|
|
11153
11301
|
const entries = entryPaths(pkg);
|
|
11154
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
11302
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join22(dir, e)));
|
|
11155
11303
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
11156
11304
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
11157
11305
|
}
|
|
@@ -11179,7 +11327,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
11179
11327
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
11180
11328
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
11181
11329
|
for (const file of [".env", ".env.example"]) {
|
|
11182
|
-
const raw = readText(
|
|
11330
|
+
const raw = readText(join22(worktree, file));
|
|
11183
11331
|
if (raw === null) continue;
|
|
11184
11332
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
11185
11333
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -11195,11 +11343,11 @@ function isLocalDatabase(url) {
|
|
|
11195
11343
|
}
|
|
11196
11344
|
}
|
|
11197
11345
|
function prismaPackages(worktree, io) {
|
|
11198
|
-
const exists = io.exists ??
|
|
11346
|
+
const exists = io.exists ?? existsSync14;
|
|
11199
11347
|
const readText = io.readText ?? safeReadText;
|
|
11200
11348
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11201
11349
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
11202
|
-
return dirs.filter((dir) => exists(
|
|
11350
|
+
return dirs.filter((dir) => exists(join22(dir, "prisma", "schema.prisma")) && listDirs(join22(dir, "prisma", "migrations")).length > 0);
|
|
11203
11351
|
}
|
|
11204
11352
|
function jsExecBin(pm, command) {
|
|
11205
11353
|
switch (pm) {
|
|
@@ -11229,25 +11377,25 @@ function migrateCommands(worktree, pm, io) {
|
|
|
11229
11377
|
var ENV_DEFINING_FILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock", "pnpm-workspace.yaml"];
|
|
11230
11378
|
function envCachePlan(worktree, io = {}) {
|
|
11231
11379
|
if (!ENV_CACHE_ENABLED) return null;
|
|
11232
|
-
const exists = io.exists ??
|
|
11380
|
+
const exists = io.exists ?? existsSync14;
|
|
11233
11381
|
const readText = io.readText ?? safeReadText;
|
|
11234
11382
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11235
11383
|
const pm = detectPackageManager(worktree, io);
|
|
11236
11384
|
if (!pm) return null;
|
|
11237
11385
|
const parts = [`pm:${pm}`];
|
|
11238
11386
|
for (const name of ENV_DEFINING_FILES) {
|
|
11239
|
-
const raw = readText(
|
|
11387
|
+
const raw = readText(join22(worktree, name));
|
|
11240
11388
|
if (raw !== null) parts.push(`${name}
|
|
11241
11389
|
${raw}`);
|
|
11242
11390
|
}
|
|
11243
11391
|
const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
|
|
11244
11392
|
const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
|
|
11245
11393
|
for (const rel of ["", ...rels]) {
|
|
11246
|
-
const raw = readText(
|
|
11394
|
+
const raw = readText(join22(worktree, rel, "package.json"));
|
|
11247
11395
|
if (raw !== null) parts.push(`${rel}/package.json
|
|
11248
11396
|
${raw}`);
|
|
11249
11397
|
}
|
|
11250
|
-
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) =>
|
|
11398
|
+
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join22(rel, "node_modules"))] };
|
|
11251
11399
|
}
|
|
11252
11400
|
function planPrepare(worktree, io = {}) {
|
|
11253
11401
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
@@ -11255,7 +11403,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
11255
11403
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
11256
11404
|
}
|
|
11257
11405
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
11258
|
-
const exists = io.exists ??
|
|
11406
|
+
const exists = io.exists ?? existsSync14;
|
|
11259
11407
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
11260
11408
|
const approve = io.approve ?? (() => true);
|
|
11261
11409
|
const cachePlan = envCachePlan(worktree, io);
|
|
@@ -11272,7 +11420,7 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
11272
11420
|
restored: [],
|
|
11273
11421
|
cached: false
|
|
11274
11422
|
};
|
|
11275
|
-
if (cachePlan && !exists(
|
|
11423
|
+
if (cachePlan && !exists(join22(worktree, "node_modules"))) {
|
|
11276
11424
|
report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
|
|
11277
11425
|
}
|
|
11278
11426
|
const plan = planPrepare(worktree, io);
|
|
@@ -11561,7 +11709,7 @@ function parkReasonUnretriable(reason) {
|
|
|
11561
11709
|
// package.json
|
|
11562
11710
|
var package_default = {
|
|
11563
11711
|
name: "@taskless-app/swarm",
|
|
11564
|
-
version: "0.6.
|
|
11712
|
+
version: "0.6.33",
|
|
11565
11713
|
type: "module",
|
|
11566
11714
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11567
11715
|
keywords: [
|
|
@@ -11612,7 +11760,7 @@ var package_default = {
|
|
|
11612
11760
|
execa: "^10.0.0",
|
|
11613
11761
|
zod: "3.25.76"
|
|
11614
11762
|
},
|
|
11615
|
-
gitHead: "
|
|
11763
|
+
gitHead: "7b17b06b041af953afde46fbd41fc68ebc6dc4e9"
|
|
11616
11764
|
};
|
|
11617
11765
|
|
|
11618
11766
|
// src/version.ts
|
|
@@ -11688,6 +11836,7 @@ function reviewBudgetMark(cycle) {
|
|
|
11688
11836
|
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";
|
|
11689
11837
|
var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
|
|
11690
11838
|
var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
|
|
11839
|
+
var RELIEF_LOOP_ESCALATION_MARK = "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C";
|
|
11691
11840
|
var ESCALATIONS = [
|
|
11692
11841
|
{
|
|
11693
11842
|
mark: SELF_REFERENCE_MARK,
|
|
@@ -11750,6 +11899,21 @@ var ESCALATIONS = [
|
|
|
11750
11899
|
sides: ["author"],
|
|
11751
11900
|
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0443 PR \u043D\u0435 \u0431\u0443\u0434\u0435\u0442"
|
|
11752
11901
|
},
|
|
11902
|
+
{
|
|
11903
|
+
mark: RELIEF_LOOP_ESCALATION_MARK,
|
|
11904
|
+
// Проверяющего это не касается: работы, которую проверять, ещё нет. Автор — да, и это
|
|
11905
|
+
// главное: сторож прогресса (watchdog.decideFreshAction) обязан УВИДЕТЬ, что рой уже
|
|
11906
|
+
// сдался, иначе он поднимет свежего актора в ту же перегрузку — буквально следующий круг
|
|
11907
|
+
// петли, ровно то, что он и делал 27.08. Ручную побудку `swarm wake` этот маркер, как и
|
|
11908
|
+
// остальные author-маркеры, не запрещает — он открывает ей дверь (см. stoppedForHuman).
|
|
11909
|
+
//
|
|
11910
|
+
// Надзора НЕТ намеренно: маркер ложится на прогон в статусе `stalled`, а надзорная труба
|
|
11911
|
+
// (buildEscalationCandidates) ходит только по `listRuns("review")` — запись там была бы
|
|
11912
|
+
// мёртвой. Человека по такому прогону зовёт детектор run_stalled (anomalies.ts), причём
|
|
11913
|
+
// повторный заход по той же задаче он поднимает до уровня «нужен оператор» сам.
|
|
11914
|
+
sides: ["author"],
|
|
11915
|
+
why: "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u044F\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D N \u0440\u0430\u0437 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0439 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C, \u0437\u043E\u0432\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 run_stalled"
|
|
11916
|
+
},
|
|
11753
11917
|
{
|
|
11754
11918
|
mark: CI_WAIT_ESCALATION_MARK,
|
|
11755
11919
|
// Сторон НЕТ — и это решение, а не пропуск. Маркер лежит на прогоне РЕВЬЮЕРА (paused,
|
|
@@ -12213,7 +12377,7 @@ init_git();
|
|
|
12213
12377
|
|
|
12214
12378
|
// src/diff.ts
|
|
12215
12379
|
import { statSync as statSync10 } from "fs";
|
|
12216
|
-
import { join as
|
|
12380
|
+
import { join as join27 } from "path";
|
|
12217
12381
|
import { $ as $4 } from "execa";
|
|
12218
12382
|
var MAX_FILES = 200;
|
|
12219
12383
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -12381,7 +12545,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
12381
12545
|
files.push(pf);
|
|
12382
12546
|
}
|
|
12383
12547
|
}
|
|
12384
|
-
files.sort((a, b) => mtime(
|
|
12548
|
+
files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
|
|
12385
12549
|
const truncatedFiles = files.length > MAX_FILES;
|
|
12386
12550
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
12387
12551
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -14464,22 +14628,22 @@ function makeMergeGateParts(deps) {
|
|
|
14464
14628
|
}
|
|
14465
14629
|
|
|
14466
14630
|
// src/envGate.ts
|
|
14467
|
-
import { existsSync as
|
|
14468
|
-
import { join as
|
|
14631
|
+
import { existsSync as existsSync17 } from "fs";
|
|
14632
|
+
import { join as join29 } from "path";
|
|
14469
14633
|
import { createConnection } from "net";
|
|
14470
14634
|
import { execa as execa2 } from "execa";
|
|
14471
14635
|
|
|
14472
14636
|
// src/platform.ts
|
|
14473
|
-
import { existsSync as
|
|
14474
|
-
import { delimiter, join as
|
|
14637
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
|
|
14638
|
+
import { delimiter, join as join28 } from "path";
|
|
14475
14639
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
14476
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
14640
|
+
if (name.includes("/") || name.includes("\\")) return existsSync16(name) ? name : null;
|
|
14477
14641
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
14478
14642
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
14479
14643
|
for (const dir of dirs) {
|
|
14480
14644
|
for (const ext of exts) {
|
|
14481
|
-
const candidate =
|
|
14482
|
-
if (
|
|
14645
|
+
const candidate = join28(dir, name + ext);
|
|
14646
|
+
if (existsSync16(candidate)) return candidate;
|
|
14483
14647
|
}
|
|
14484
14648
|
}
|
|
14485
14649
|
return null;
|
|
@@ -14562,8 +14726,8 @@ var COMPOSE_MARKERS = [
|
|
|
14562
14726
|
"compose.yaml"
|
|
14563
14727
|
];
|
|
14564
14728
|
function detectRequirements(repo, io = {}) {
|
|
14565
|
-
const exists = io.exists ??
|
|
14566
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
14729
|
+
const exists = io.exists ?? existsSync17;
|
|
14730
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join29(repo, f)));
|
|
14567
14731
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
14568
14732
|
}
|
|
14569
14733
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -16306,22 +16470,22 @@ function closeWebServer(server) {
|
|
|
16306
16470
|
|
|
16307
16471
|
// src/hostCommands.ts
|
|
16308
16472
|
init_src();
|
|
16309
|
-
import { readFileSync as
|
|
16310
|
-
import { join as
|
|
16473
|
+
import { readFileSync as readFileSync22, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
|
|
16474
|
+
import { join as join30 } from "path";
|
|
16311
16475
|
function hostCommandStatePath(home) {
|
|
16312
|
-
return
|
|
16476
|
+
return join30(home, "host-command.json");
|
|
16313
16477
|
}
|
|
16314
16478
|
function readState(path) {
|
|
16315
16479
|
try {
|
|
16316
|
-
return JSON.parse(
|
|
16480
|
+
return JSON.parse(readFileSync22(path, "utf8"));
|
|
16317
16481
|
} catch {
|
|
16318
16482
|
return {};
|
|
16319
16483
|
}
|
|
16320
16484
|
}
|
|
16321
16485
|
function writeState(path, state) {
|
|
16322
16486
|
try {
|
|
16323
|
-
|
|
16324
|
-
|
|
16487
|
+
writeFileSync15(`${path}.tmp`, JSON.stringify(state));
|
|
16488
|
+
renameSync10(`${path}.tmp`, path);
|
|
16325
16489
|
} catch {
|
|
16326
16490
|
}
|
|
16327
16491
|
}
|
|
@@ -16483,8 +16647,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
16483
16647
|
|
|
16484
16648
|
// src/selfUpdate.ts
|
|
16485
16649
|
init_git();
|
|
16486
|
-
import { existsSync as
|
|
16487
|
-
import { join as
|
|
16650
|
+
import { existsSync as existsSync18 } from "fs";
|
|
16651
|
+
import { join as join31 } from "path";
|
|
16488
16652
|
import { $ as $5 } from "execa";
|
|
16489
16653
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
16490
16654
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -16497,7 +16661,7 @@ function tail(text, max = 400) {
|
|
|
16497
16661
|
}
|
|
16498
16662
|
async function runSelfUpdate(io) {
|
|
16499
16663
|
const run = io.run ?? defaultRun;
|
|
16500
|
-
const exists = io.exists ??
|
|
16664
|
+
const exists = io.exists ?? existsSync18;
|
|
16501
16665
|
const repo = io.repo;
|
|
16502
16666
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
16503
16667
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -16526,7 +16690,7 @@ async function runSelfUpdate(io) {
|
|
|
16526
16690
|
};
|
|
16527
16691
|
}
|
|
16528
16692
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
16529
|
-
if (!exists(
|
|
16693
|
+
if (!exists(join31(repo, "pnpm-lock.yaml"))) {
|
|
16530
16694
|
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
16531
16695
|
}
|
|
16532
16696
|
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -16685,19 +16849,19 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
16685
16849
|
}
|
|
16686
16850
|
|
|
16687
16851
|
// src/anomalyState.ts
|
|
16688
|
-
import { existsSync as
|
|
16689
|
-
import { join as
|
|
16852
|
+
import { existsSync as existsSync19, readFileSync as readFileSync23, renameSync as renameSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
16853
|
+
import { join as join32 } from "path";
|
|
16690
16854
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
16691
16855
|
var MAX_RESOLVED = 200;
|
|
16692
16856
|
function emptyState() {
|
|
16693
16857
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
16694
16858
|
}
|
|
16695
16859
|
function anomalyStatePath(home) {
|
|
16696
|
-
return
|
|
16860
|
+
return join32(home, "anomaly-state.json");
|
|
16697
16861
|
}
|
|
16698
16862
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
16699
|
-
const exists = io.exists ??
|
|
16700
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
16863
|
+
const exists = io.exists ?? existsSync19;
|
|
16864
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync23(p, "utf8"));
|
|
16701
16865
|
const path = anomalyStatePath(home);
|
|
16702
16866
|
if (!exists(path)) return emptyState();
|
|
16703
16867
|
try {
|
|
@@ -16727,8 +16891,8 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
16727
16891
|
function saveAnomalyState(home, state, io = {}) {
|
|
16728
16892
|
const path = anomalyStatePath(home);
|
|
16729
16893
|
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
16730
|
-
|
|
16731
|
-
|
|
16894
|
+
writeFileSync16(`${p}.tmp`, data);
|
|
16895
|
+
renameSync11(`${p}.tmp`, p);
|
|
16732
16896
|
});
|
|
16733
16897
|
writeFile4(path, JSON.stringify(state, null, 2));
|
|
16734
16898
|
}
|
|
@@ -16882,6 +17046,7 @@ function decideParkAction(db, droneCap, exclusivityBlocked) {
|
|
|
16882
17046
|
function decideFreshAction(db, wt) {
|
|
16883
17047
|
const stalled = db.listRuns("stalled").filter((r) => r.resolves_run_id == null && r.reviews_run_id == null);
|
|
16884
17048
|
for (const r of stalled) {
|
|
17049
|
+
if (findEscalation(db.events(r.id).map((e) => e.text ?? ""), r, "author")) continue;
|
|
16885
17050
|
const path = r.worktree_path;
|
|
16886
17051
|
const recoverable = !!path && wt.exists(path) && wt.hasTranscript(path);
|
|
16887
17052
|
if (recoverable) continue;
|
|
@@ -17229,6 +17394,7 @@ function verdictCommentMark(createdAt) {
|
|
|
17229
17394
|
return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
|
|
17230
17395
|
}
|
|
17231
17396
|
var WATCHDOG_PARK_REASON = "\u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0441\u043B\u043E\u0442 \u0437\u0430\u043D\u044F\u0442 \u043D\u0438\u0437\u043A\u043E\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u044B\u043C \u2014 \u043F\u0430\u0440\u043A\u0443\u044E, \u0432\u043F\u0443\u0441\u043A\u0430\u044E \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0436\u0434\u0443\u0449\u0435\u0433\u043E (TASWM-270)";
|
|
17397
|
+
var RELIEF_LOOP_STOP_REASON = "\u0440\u0435\u0441\u0443\u0440\u0441-\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C: \u0441\u043D\u044F\u0442\u0438\u0439 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (TASWM-531)";
|
|
17232
17398
|
function livePpids() {
|
|
17233
17399
|
return livePpidsFrom(listProcs());
|
|
17234
17400
|
}
|
|
@@ -17854,10 +18020,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
17854
18020
|
}
|
|
17855
18021
|
return parts.join("\n");
|
|
17856
18022
|
}
|
|
17857
|
-
var HERO_CHARTER_PATH =
|
|
18023
|
+
var HERO_CHARTER_PATH = join33(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
17858
18024
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
17859
18025
|
const name = run.name || `#${run.id}`;
|
|
17860
|
-
const charter =
|
|
18026
|
+
const charter = readFileSync24(HERO_CHARTER_PATH, "utf8");
|
|
17861
18027
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
17862
18028
|
|
|
17863
18029
|
${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
|
|
@@ -18165,7 +18331,7 @@ async function wakeRun(deps, runId) {
|
|
|
18165
18331
|
if (run.status === "review" && run.pr_url && !needsFixIteration(run) && !stoppedForHuman) {
|
|
18166
18332
|
return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0441\u0434\u0430\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E (${run.pr_url}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E; \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u043E\u0439 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443` };
|
|
18167
18333
|
}
|
|
18168
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18334
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
18169
18335
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18170
18336
|
const wt = run.worktree_path;
|
|
18171
18337
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -18285,7 +18451,7 @@ function repoHasCi(deps, run) {
|
|
|
18285
18451
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
18286
18452
|
for (const base3 of [worktreePath, repo]) {
|
|
18287
18453
|
if (!base3) continue;
|
|
18288
|
-
const dir =
|
|
18454
|
+
const dir = join33(base3, ".github", "workflows");
|
|
18289
18455
|
try {
|
|
18290
18456
|
if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
18291
18457
|
} catch {
|
|
@@ -18471,7 +18637,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
18471
18637
|
return true;
|
|
18472
18638
|
}
|
|
18473
18639
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
18474
|
-
const exists = deps.worktreeExists ??
|
|
18640
|
+
const exists = deps.worktreeExists ?? existsSync20;
|
|
18475
18641
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
18476
18642
|
return exists(worktree) && transcript(worktree);
|
|
18477
18643
|
}
|
|
@@ -18980,7 +19146,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
18980
19146
|
}
|
|
18981
19147
|
if (!candidate) return;
|
|
18982
19148
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
18983
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19149
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
18984
19150
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18985
19151
|
const wt = candidate.worktree_path;
|
|
18986
19152
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -19095,17 +19261,31 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19095
19261
|
console.error(`[overmind] \u0440\u0435\u043A\u043B\u0435\u0439\u043C worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
19096
19262
|
}
|
|
19097
19263
|
}
|
|
19264
|
+
let saved = false;
|
|
19098
19265
|
if (candidate.worktree_path && deps.pauseSnapshot) {
|
|
19099
19266
|
try {
|
|
19100
|
-
|
|
19267
|
+
saved = await deps.pauseSnapshot(candidate.worktree_path);
|
|
19101
19268
|
if (saved) deps.db.addEvent(candidate.id, "step", "\u043D\u0435\u0434\u043E\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043E+\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u043F\u0435\u0440\u0435\u0434 \u043F\u0430\u0443\u0437\u043E\u0439 (TASWM-229) \u2014 --continue \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u0441 \u044D\u0442\u043E\u0439 \u0442\u043E\u0447\u043A\u0438");
|
|
19102
19269
|
} catch (e) {
|
|
19103
19270
|
console.error(`[overmind] \u0441\u043D\u0430\u043F\u0448\u043E\u0442 worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
19104
19271
|
}
|
|
19105
19272
|
}
|
|
19273
|
+
const canContinue = !!candidate.worktree_path && worktreeAndTranscriptAlive(deps, candidate.worktree_path);
|
|
19106
19274
|
deps.db.updateRun(candidate.id, { status: "paused", stop_reason: RELIEF_PAUSE_REASON });
|
|
19107
|
-
deps.db.addEvent(candidate.id, "paused",
|
|
19275
|
+
deps.db.addEvent(candidate.id, "paused", reliefPauseReason(canContinue));
|
|
19108
19276
|
state.lastPauseAt = now;
|
|
19277
|
+
const loopEntry = deps.reliefLoop?.recordPause(
|
|
19278
|
+
reliefLoopKey(normalizeRepo(candidate.repo || deps.repoFallback), candidate.task_slug),
|
|
19279
|
+
now,
|
|
19280
|
+
candidate.pr_url != null || saved
|
|
19281
|
+
);
|
|
19282
|
+
if (loopEntry && loopEntry.pauses > 1) {
|
|
19283
|
+
deps.db.addEvent(
|
|
19284
|
+
candidate.id,
|
|
19285
|
+
"step",
|
|
19286
|
+
`${RELIEF_PAUSE_MARK}: \u0441\u043D\u044F\u0442\u0438\u0435 ${loopEntry.pauses}-\u0435 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u043D\u0430 ${Math.round(reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs) / 1e3)}\u0441 (TASWM-531)`
|
|
19287
|
+
);
|
|
19288
|
+
}
|
|
19109
19289
|
recordReliefPause(
|
|
19110
19290
|
state.cost ??= { pauses: [] },
|
|
19111
19291
|
{ runId: candidate.id, label: droneLabel(candidate), at: now, resumedAt: null, stepsSinceResume },
|
|
@@ -19137,7 +19317,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19137
19317
|
state.lastLoggedGateReason = null;
|
|
19138
19318
|
}
|
|
19139
19319
|
if (countActiveDrones(deps.db) >= droneCap) return;
|
|
19140
|
-
const pausedForRelief = deps.db.listRuns("paused").filter((r) => r.stop_reason
|
|
19320
|
+
const pausedForRelief = deps.db.listRuns("paused").filter((r) => isReliefPause(r.stop_reason));
|
|
19141
19321
|
const recovering = pausedForRelief.length > 0 || state.overloadedAt != null && now - state.overloadedAt < cfg.rampWindowMs;
|
|
19142
19322
|
if (!recovering) {
|
|
19143
19323
|
await batchSpawnQueue(deps, droneCap);
|
|
@@ -19148,10 +19328,27 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19148
19328
|
if (resumeCandidate) {
|
|
19149
19329
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
19150
19330
|
if (!deps.respawn) return;
|
|
19331
|
+
const loopKey = reliefLoopKey(normalizeRepo(resumeCandidate.repo || deps.repoFallback), resumeCandidate.task_slug);
|
|
19332
|
+
const loopEntry = deps.reliefLoop?.get(loopKey);
|
|
19333
|
+
if (loopEntry) {
|
|
19334
|
+
const loopCfg = resolveReliefLoopConfig(deps.reliefLoopConfig);
|
|
19335
|
+
if (reliefLoopTripped(loopEntry, loopCfg)) {
|
|
19336
|
+
if (!loopEntry.escalated) {
|
|
19337
|
+
deps.db.updateRun(resumeCandidate.id, { status: "stalled", stop_reason: RELIEF_LOOP_STOP_REASON });
|
|
19338
|
+
deps.db.addEvent(
|
|
19339
|
+
resumeCandidate.id,
|
|
19340
|
+
"step",
|
|
19341
|
+
`${RELIEF_LOOP_ESCALATION_MARK}: \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u0438\u043C\u0430\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0438 \u043D\u0438 \u043E\u0434\u0438\u043D \u043A\u0440\u0443\u0433 \u043D\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0441\u0434\u0430\u0447\u0438 (\u043D\u0438 PR, \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u0430) \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (\u043F\u043E\u0442\u043E\u043B\u043E\u043A ${loopCfg.limit}, TASWM-531)`
|
|
19342
|
+
);
|
|
19343
|
+
deps.reliefLoop?.markEscalated(loopKey);
|
|
19344
|
+
narrate(deps, `${droneLabel(resumeCandidate)} \u0441\u043D\u0438\u043C\u0430\u043B\u0441\u044F \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u043E\u0439 ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u044D\u0442\u043E \u043F\u0435\u0442\u043B\u044F, \u0434\u0430\u043B\u044C\u0448\u0435 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.`);
|
|
19345
|
+
}
|
|
19346
|
+
return;
|
|
19347
|
+
}
|
|
19348
|
+
if (now - loopEntry.lastPauseAt < reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs, loopCfg)) return;
|
|
19349
|
+
}
|
|
19151
19350
|
const wt = resumeCandidate.worktree_path;
|
|
19152
|
-
const
|
|
19153
|
-
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
19154
|
-
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
19351
|
+
const canContinue = !!wt && worktreeAndTranscriptAlive(deps, wt);
|
|
19155
19352
|
const mode = canContinue ? "continue" : "fresh";
|
|
19156
19353
|
if (mode === "continue" && wt && deps.reclaimWorktree) {
|
|
19157
19354
|
try {
|
|
@@ -19190,7 +19387,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
19190
19387
|
(runId) => {
|
|
19191
19388
|
const run = runById.get(runId);
|
|
19192
19389
|
if (!run) return now;
|
|
19193
|
-
if (run.status === "paused" && run.stop_reason
|
|
19390
|
+
if (run.status === "paused" && isReliefPause(run.stop_reason)) return null;
|
|
19194
19391
|
return sqliteMs(run.updated_at);
|
|
19195
19392
|
},
|
|
19196
19393
|
now
|
|
@@ -19206,7 +19403,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
19206
19403
|
if (!reliefAlertDue(cost, thresholds)) return [];
|
|
19207
19404
|
const pressure = deps.hostPressure?.() ?? { culprits: [], readings: [], totalGb: 0 };
|
|
19208
19405
|
const occupying = allRuns.filter(
|
|
19209
|
-
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && r.stop_reason
|
|
19406
|
+
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && isReliefPause(r.stop_reason))
|
|
19210
19407
|
);
|
|
19211
19408
|
const repoOf = (r) => normalizeRepo(r.repo || deps.repoFallback || "");
|
|
19212
19409
|
const busyRepos = [...new Set(occupying.map(repoOf).filter(Boolean))].filter(
|
|
@@ -19427,7 +19624,7 @@ function buildEscalationCandidates(db) {
|
|
|
19427
19624
|
}
|
|
19428
19625
|
async function tickOnce(deps) {
|
|
19429
19626
|
const now = deps.now ? deps.now() : Date.now();
|
|
19430
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19627
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
19431
19628
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
19432
19629
|
deps.sampleHost?.();
|
|
19433
19630
|
if (deps.rehydrate) {
|
|
@@ -20013,7 +20210,7 @@ async function tickOnce(deps) {
|
|
|
20013
20210
|
}
|
|
20014
20211
|
for (const run of deps.db.listRuns("paused")) {
|
|
20015
20212
|
if (run.kind === "hero") continue;
|
|
20016
|
-
if (run.stop_reason === AWAITING_CI_REASON || run.stop_reason
|
|
20213
|
+
if (run.stop_reason === AWAITING_CI_REASON || isReliefPause(run.stop_reason)) continue;
|
|
20017
20214
|
const last = lastActivityMs(deps.db, run);
|
|
20018
20215
|
if (now - last <= STALL_FLOOR_MS) continue;
|
|
20019
20216
|
const evs = deps.db.events(run.id);
|
|
@@ -20029,7 +20226,7 @@ async function tickOnce(deps) {
|
|
|
20029
20226
|
for (const run of deps.db.listRuns("paused")) {
|
|
20030
20227
|
if (run.kind === "hero") continue;
|
|
20031
20228
|
if (run.reviews_run_id != null) continue;
|
|
20032
|
-
if (run.stop_reason
|
|
20229
|
+
if (isReliefPause(run.stop_reason)) continue;
|
|
20033
20230
|
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
20034
20231
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
20035
20232
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
@@ -20223,7 +20420,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
20223
20420
|
model: reviewRun.model,
|
|
20224
20421
|
review_model: reviewRun.review_model
|
|
20225
20422
|
});
|
|
20226
|
-
const wt =
|
|
20423
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
20227
20424
|
try {
|
|
20228
20425
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20229
20426
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -20302,7 +20499,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
20302
20499
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
20303
20500
|
workspace_id: reviewRun.workspace_id
|
|
20304
20501
|
});
|
|
20305
|
-
const wt =
|
|
20502
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
20306
20503
|
try {
|
|
20307
20504
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20308
20505
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -20383,7 +20580,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
20383
20580
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
20384
20581
|
workspace_id: verifiedRun.workspace_id
|
|
20385
20582
|
});
|
|
20386
|
-
const wt =
|
|
20583
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
20387
20584
|
try {
|
|
20388
20585
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20389
20586
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -20416,7 +20613,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20416
20613
|
if (mode === "continue") {
|
|
20417
20614
|
const prompt2 = nudge ?? (run.kind === "hero" ? HERO_CONTINUE_NUDGE : CONTINUE_NUDGE);
|
|
20418
20615
|
const pid2 = deps.launch({ worktree: wt, run, prompt: prompt2, continueSession: true });
|
|
20419
|
-
deps.db.updateRun(run.id, { status: "running", pid: pid2 });
|
|
20616
|
+
deps.db.updateRun(run.id, { status: "running", pid: pid2, ...worktreePathPatch(run, wt) });
|
|
20420
20617
|
return;
|
|
20421
20618
|
}
|
|
20422
20619
|
const [task, verifyMode, projectBase, zoneNotes] = await Promise.all([
|
|
@@ -20429,7 +20626,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20429
20626
|
]);
|
|
20430
20627
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
20431
20628
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
20432
|
-
if (!
|
|
20629
|
+
if (!existsSync20(wt)) {
|
|
20433
20630
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
20434
20631
|
const source = await addWorktree(repo, wt, branch, {
|
|
20435
20632
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -20453,10 +20650,13 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20453
20650
|
const notice = depsPromptNotice(depsReport);
|
|
20454
20651
|
const prompt = [freshPrompt, notice, nudge].filter(Boolean).join("\n\n");
|
|
20455
20652
|
const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
|
|
20456
|
-
deps.db.updateRun(run.id, { status: "running", pid });
|
|
20653
|
+
deps.db.updateRun(run.id, { status: "running", pid, ...worktreePathPatch(run, wt) });
|
|
20654
|
+
}
|
|
20655
|
+
function worktreePathPatch(run, wt) {
|
|
20656
|
+
return run.worktree_path === wt ? {} : { worktree_path: wt };
|
|
20457
20657
|
}
|
|
20458
20658
|
async function syncCrashedOnStartup(deps) {
|
|
20459
|
-
const worktreeExists = deps.worktreeExists ??
|
|
20659
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
20460
20660
|
let orphaned = 0;
|
|
20461
20661
|
let merged = 0;
|
|
20462
20662
|
let requeued = 0;
|
|
@@ -20610,7 +20810,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
20610
20810
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
20611
20811
|
targetDir: WORKTREES_DIR,
|
|
20612
20812
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
20613
|
-
exists: (d) =>
|
|
20813
|
+
exists: (d) => existsSync20(d),
|
|
20614
20814
|
isNonEmptyDir: (d) => {
|
|
20615
20815
|
try {
|
|
20616
20816
|
return readdirSync13(d).length > 0;
|
|
@@ -20619,13 +20819,13 @@ async function migrateWorktreesAtStartup() {
|
|
|
20619
20819
|
}
|
|
20620
20820
|
},
|
|
20621
20821
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
20622
|
-
rename: (from, to) =>
|
|
20822
|
+
rename: (from, to) => renameSync12(from, to),
|
|
20623
20823
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
20624
20824
|
log: (m) => console.log(m)
|
|
20625
20825
|
});
|
|
20626
20826
|
} catch (e) {
|
|
20627
20827
|
console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
|
|
20628
|
-
return
|
|
20828
|
+
return existsSync20(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
20629
20829
|
}
|
|
20630
20830
|
}
|
|
20631
20831
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -21278,7 +21478,7 @@ async function runDaemon(opts) {
|
|
|
21278
21478
|
isDirty: (p) => worktreeDirty(p),
|
|
21279
21479
|
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
21280
21480
|
pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
|
|
21281
|
-
worktreeExists:
|
|
21481
|
+
worktreeExists: existsSync20,
|
|
21282
21482
|
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
21283
21483
|
narrate: narrator.narrate
|
|
21284
21484
|
});
|
|
@@ -21318,6 +21518,9 @@ async function runDaemon(opts) {
|
|
|
21318
21518
|
// разгрузки (TASWM-316) — заводим ОДИН раз на цикл демона, tickOnce (manageRelief)
|
|
21319
21519
|
// мутирует эти же поля каждый тик.
|
|
21320
21520
|
relief: { cost: { pauses: [] } },
|
|
21521
|
+
// Счёт снятий разгрузкой подряд без сдачи (TASWM-531) — durable, в отличие от relief-состояния
|
|
21522
|
+
// выше: он обязан пережить и рестарт демона, и подмену прогона сторожем прогресса.
|
|
21523
|
+
reliefLoop: new ReliefLoopStore(SWARM_HOME),
|
|
21321
21524
|
// Гейт окружения машины (TASWM-313): не спавним дрона, если проекту не хватает сервисов
|
|
21322
21525
|
// (лежащий докер = заведомо красные api-тесты). Только client-режим — требования живут в
|
|
21323
21526
|
// настройках проекта; без него гейта нет и рой работает как раньше. Дедуп событий —
|
|
@@ -21401,7 +21604,7 @@ async function runDaemon(opts) {
|
|
|
21401
21604
|
mode,
|
|
21402
21605
|
nudge
|
|
21403
21606
|
),
|
|
21404
|
-
worktreeExists: (p) =>
|
|
21607
|
+
worktreeExists: (p) => existsSync20(p),
|
|
21405
21608
|
// TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
|
|
21406
21609
|
// процесса демона и рестарт не переживает (см. preStartAttempts).
|
|
21407
21610
|
startedAt: Date.now(),
|
|
@@ -21870,6 +22073,7 @@ export {
|
|
|
21870
22073
|
HERO_CONTINUE_NUDGE,
|
|
21871
22074
|
OPEN_PR_HOLD_MARK,
|
|
21872
22075
|
OVERMIND_ENTRY,
|
|
22076
|
+
RELIEF_LOOP_STOP_REASON,
|
|
21873
22077
|
RESOLVE_ESCALATION_MARK,
|
|
21874
22078
|
RESOLVE_LIMIT,
|
|
21875
22079
|
RESPAWN_LIMIT,
|