@taskless-app/swarm 0.6.0 → 0.6.2
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/README.md +21 -0
- package/dist/cli.js +136 -8
- package/dist/overmind.js +421 -157
- package/package.json +2 -1
package/dist/overmind.js
CHANGED
|
@@ -746,10 +746,12 @@ function hostCommandLabel(cmd) {
|
|
|
746
746
|
if (isRunScopedCommand(cmd.kind) && cmd.runSlug) return `${base3} ${cmd.runSlug}`;
|
|
747
747
|
return base3;
|
|
748
748
|
}
|
|
749
|
-
var HOST_COMMAND_KINDS, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, UPDATE_STEP_LABELS, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_LABELS, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED, MAX_DRONES_LIMIT;
|
|
749
|
+
var SWARM_PACKAGE_NAME, MANUAL_UPDATE_COMMAND, HOST_COMMAND_KINDS, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, UPDATE_STEP_LABELS, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_ACK_GRACE_MS, HOST_COMMAND_MUTE_REASON, HOST_COMMAND_LABELS, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED, MAX_DRONES_LIMIT;
|
|
750
750
|
var init_hostCommands = __esm({
|
|
751
751
|
"../swarm-core/src/hostCommands.ts"() {
|
|
752
752
|
"use strict";
|
|
753
|
+
SWARM_PACKAGE_NAME = "@taskless-app/swarm";
|
|
754
|
+
MANUAL_UPDATE_COMMAND = `npm i -g ${SWARM_PACKAGE_NAME}@latest`;
|
|
753
755
|
HOST_COMMAND_KINDS = ["pause", "resume", "restart", "update", "set_max_drones", "stop_run", "wake_run"];
|
|
754
756
|
HOST_COMMAND_STATUSES = ["pending", "delivered", "running", "done", "failed", "rejected", "expired"];
|
|
755
757
|
TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "rejected", "expired"]);
|
|
@@ -763,6 +765,8 @@ var init_hostCommands = __esm({
|
|
|
763
765
|
};
|
|
764
766
|
HOST_COMMAND_TTL_MS = 30 * 6e4;
|
|
765
767
|
HOST_COMMAND_KEEPALIVE_MS = 10 * 6e4;
|
|
768
|
+
HOST_COMMAND_ACK_GRACE_MS = 5 * 6e4;
|
|
769
|
+
HOST_COMMAND_MUTE_REASON = `\u043C\u0430\u0448\u0438\u043D\u0430 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u0430 \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u2014 \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E, \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B. \u041E\u0431\u043D\u043E\u0432\u0438 \u0432\u0440\u0443\u0447\u043D\u0443\u044E: ${MANUAL_UPDATE_COMMAND}`;
|
|
766
770
|
HOST_COMMAND_LABELS = {
|
|
767
771
|
pause: "\u043F\u0430\u0443\u0437\u0430",
|
|
768
772
|
resume: "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C",
|
|
@@ -3263,7 +3267,7 @@ var init_droneChannel = __esm({
|
|
|
3263
3267
|
|
|
3264
3268
|
// src/attachments.ts
|
|
3265
3269
|
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
3266
|
-
import { join as
|
|
3270
|
+
import { join as join18 } from "path";
|
|
3267
3271
|
function attachmentsFromDoc(doc, origin) {
|
|
3268
3272
|
const out = [];
|
|
3269
3273
|
const walk2 = (node) => {
|
|
@@ -3354,7 +3358,7 @@ async function materializeAttachments(opts) {
|
|
|
3354
3358
|
};
|
|
3355
3359
|
const result = { files: [], skipped: [] };
|
|
3356
3360
|
if (opts.refs.length === 0) return result;
|
|
3357
|
-
const dir =
|
|
3361
|
+
const dir = join18(opts.worktree, ATTACHMENTS_DIR);
|
|
3358
3362
|
await io.removeDir(dir);
|
|
3359
3363
|
let dirReady = false;
|
|
3360
3364
|
let total = 0;
|
|
@@ -3400,7 +3404,7 @@ async function materializeAttachments(opts) {
|
|
|
3400
3404
|
await io.mkdir(dir);
|
|
3401
3405
|
dirReady = true;
|
|
3402
3406
|
}
|
|
3403
|
-
await io.writeFile(
|
|
3407
|
+
await io.writeFile(join18(dir, name), file.bytes);
|
|
3404
3408
|
} catch (e) {
|
|
3405
3409
|
result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
3406
3410
|
continue;
|
|
@@ -3553,7 +3557,7 @@ var init_attachments = __esm({
|
|
|
3553
3557
|
|
|
3554
3558
|
// src/shotUpload.ts
|
|
3555
3559
|
import { readFile as fsReadFile } from "fs/promises";
|
|
3556
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
3560
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join19 } from "path";
|
|
3557
3561
|
function shotMimetype(path) {
|
|
3558
3562
|
const dot = path.lastIndexOf(".");
|
|
3559
3563
|
if (dot < 0) return null;
|
|
@@ -3579,7 +3583,7 @@ async function readShots(paths, opts) {
|
|
|
3579
3583
|
}
|
|
3580
3584
|
let bytes;
|
|
3581
3585
|
try {
|
|
3582
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
3586
|
+
bytes = await read(isAbsolute3(path) ? path : join19(opts.worktree, path));
|
|
3583
3587
|
} catch (e) {
|
|
3584
3588
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
3585
3589
|
continue;
|
|
@@ -3656,12 +3660,12 @@ var init_shotUpload = __esm({
|
|
|
3656
3660
|
});
|
|
3657
3661
|
|
|
3658
3662
|
// src/oauth.ts
|
|
3659
|
-
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
3663
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
3660
3664
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
3661
|
-
import { join as
|
|
3665
|
+
import { join as join20 } from "path";
|
|
3662
3666
|
function makePkce() {
|
|
3663
3667
|
const verifier = randomBytes2(48).toString("base64url");
|
|
3664
|
-
const challenge =
|
|
3668
|
+
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
3665
3669
|
return { verifier, challenge };
|
|
3666
3670
|
}
|
|
3667
3671
|
async function registerClient(args, fetchFn = fetch) {
|
|
@@ -3793,15 +3797,15 @@ var init_oauth = __esm({
|
|
|
3793
3797
|
AUTHORIZE = "/api/oauth/authorize";
|
|
3794
3798
|
TOKEN = "/api/oauth/token";
|
|
3795
3799
|
SCOPE = "openid profile email taskless:full";
|
|
3796
|
-
STORE_PATH =
|
|
3797
|
-
CLIENT_PATH =
|
|
3800
|
+
STORE_PATH = join20(SWARM_HOME, "oauth.json");
|
|
3801
|
+
CLIENT_PATH = join20(SWARM_HOME, "oauth-client.json");
|
|
3798
3802
|
base = (api) => api.replace(/\/$/, "");
|
|
3799
3803
|
}
|
|
3800
3804
|
});
|
|
3801
3805
|
|
|
3802
3806
|
// src/agentToken.ts
|
|
3803
3807
|
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
3804
|
-
import { join as
|
|
3808
|
+
import { join as join21 } from "path";
|
|
3805
3809
|
function loadAgentToken() {
|
|
3806
3810
|
try {
|
|
3807
3811
|
const s = JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
|
|
@@ -3821,6 +3825,14 @@ async function probeAgentToken(args, fetchFn = fetch) {
|
|
|
3821
3825
|
return "unreachable";
|
|
3822
3826
|
}
|
|
3823
3827
|
}
|
|
3828
|
+
function isRevokedTokenError(e) {
|
|
3829
|
+
if (e && typeof e === "object" && e.code === 401) return true;
|
|
3830
|
+
const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
3831
|
+
return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
|
|
3832
|
+
}
|
|
3833
|
+
function revokedTokenLine() {
|
|
3834
|
+
return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
|
|
3835
|
+
}
|
|
3824
3836
|
function revokedTokenComplaint() {
|
|
3825
3837
|
return [
|
|
3826
3838
|
"",
|
|
@@ -3841,14 +3853,14 @@ var init_agentToken = __esm({
|
|
|
3841
3853
|
"src/agentToken.ts"() {
|
|
3842
3854
|
"use strict";
|
|
3843
3855
|
init_config();
|
|
3844
|
-
STORE_PATH2 =
|
|
3856
|
+
STORE_PATH2 = join21(SWARM_HOME, "agent.json");
|
|
3845
3857
|
base2 = (api) => api.replace(/\/$/, "");
|
|
3846
3858
|
}
|
|
3847
3859
|
});
|
|
3848
3860
|
|
|
3849
3861
|
// src/workspace.ts
|
|
3850
3862
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
|
|
3851
|
-
import { join as
|
|
3863
|
+
import { join as join22 } from "path";
|
|
3852
3864
|
function loadWorkspace() {
|
|
3853
3865
|
try {
|
|
3854
3866
|
const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
|
|
@@ -3865,7 +3877,7 @@ var init_workspace = __esm({
|
|
|
3865
3877
|
"src/workspace.ts"() {
|
|
3866
3878
|
"use strict";
|
|
3867
3879
|
init_config();
|
|
3868
|
-
WORKSPACE_PATH =
|
|
3880
|
+
WORKSPACE_PATH = join22(SWARM_HOME, "workspace.json");
|
|
3869
3881
|
}
|
|
3870
3882
|
});
|
|
3871
3883
|
|
|
@@ -4220,7 +4232,9 @@ async function connectTaskless(cfg) {
|
|
|
4220
4232
|
try {
|
|
4221
4233
|
await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
|
|
4222
4234
|
} catch (e) {
|
|
4223
|
-
console.warn(
|
|
4235
|
+
console.warn(
|
|
4236
|
+
isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 slug-only \u0440\u0435\u0436\u0438\u043C)` : `Taskless: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 slug-only. \u0412\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E.`
|
|
4237
|
+
);
|
|
4224
4238
|
return null;
|
|
4225
4239
|
}
|
|
4226
4240
|
const call = async (name, args) => await client.callTool({ name, arguments: args });
|
|
@@ -4244,7 +4258,9 @@ async function connectSwarmMcp(cfg) {
|
|
|
4244
4258
|
try {
|
|
4245
4259
|
await client.connect(transport);
|
|
4246
4260
|
} catch (e) {
|
|
4247
|
-
console.warn(
|
|
4261
|
+
console.warn(
|
|
4262
|
+
isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox)` : `Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`
|
|
4263
|
+
);
|
|
4248
4264
|
return null;
|
|
4249
4265
|
}
|
|
4250
4266
|
return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
|
|
@@ -5169,7 +5185,7 @@ var init_droneStore = __esm({
|
|
|
5169
5185
|
// src/overmind.ts
|
|
5170
5186
|
import { homedir as homedir5 } from "os";
|
|
5171
5187
|
import { existsSync as existsSync16, readFileSync as readFileSync20, readdirSync as readdirSync12, renameSync as renameSync10 } from "fs";
|
|
5172
|
-
import { basename as basename4, dirname as
|
|
5188
|
+
import { basename as basename4, dirname as dirname4, join as join29 } from "path";
|
|
5173
5189
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
5174
5190
|
import { spawn as spawn3 } from "child_process";
|
|
5175
5191
|
|
|
@@ -6694,7 +6710,16 @@ var SwarmClient = class _SwarmClient {
|
|
|
6694
6710
|
const data = res?.structuredContent ?? {};
|
|
6695
6711
|
return {
|
|
6696
6712
|
commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
|
|
6697
|
-
logRequests: parseLogRequests(data.logRequests)
|
|
6713
|
+
logRequests: parseLogRequests(data.logRequests),
|
|
6714
|
+
// Предел человека (TASWM-363). Различаем ТРИ случая, и разница не косметическая:
|
|
6715
|
+
// • ключа нет вовсе (старый прод, пульс без hostId) → undefined: прежнее число
|
|
6716
|
+
// не трогаем, иначе обновление прода молча снимало бы предел;
|
|
6717
|
+
// • число >0 → предел назван;
|
|
6718
|
+
// • всё прочее (null, ноль, мусор) → null: предела нет. Выдуманный потолок хуже
|
|
6719
|
+
// отсутствующего — под ним рой встанет молча.
|
|
6720
|
+
...data.maxDrones === void 0 ? {} : {
|
|
6721
|
+
maxDrones: typeof data.maxDrones === "number" && Number.isFinite(data.maxDrones) && data.maxDrones > 0 ? Math.trunc(data.maxDrones) : null
|
|
6722
|
+
}
|
|
6698
6723
|
};
|
|
6699
6724
|
} catch {
|
|
6700
6725
|
return EMPTY_HEARTBEAT_REPLY;
|
|
@@ -8231,19 +8256,89 @@ init_git();
|
|
|
8231
8256
|
|
|
8232
8257
|
// src/deps.ts
|
|
8233
8258
|
import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync12 } from "fs";
|
|
8234
|
-
import { join as
|
|
8259
|
+
import { join as join17 } from "path";
|
|
8235
8260
|
import { execa } from "execa";
|
|
8261
|
+
|
|
8262
|
+
// src/prepareLock.ts
|
|
8263
|
+
import { createHash } from "crypto";
|
|
8264
|
+
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync8, writeSync } from "fs";
|
|
8265
|
+
import { join as join16 } from "path";
|
|
8266
|
+
import { tmpdir } from "os";
|
|
8267
|
+
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
8268
|
+
var POLL_MS = 300;
|
|
8269
|
+
var STALE_MS = 20 * 6e4;
|
|
8270
|
+
function machineLockPath(key, dir = tmpdir()) {
|
|
8271
|
+
return join16(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
8272
|
+
}
|
|
8273
|
+
function defaultTryCreate(path, content) {
|
|
8274
|
+
try {
|
|
8275
|
+
const fd = openSync3(path, "wx");
|
|
8276
|
+
try {
|
|
8277
|
+
writeSync(fd, content);
|
|
8278
|
+
} finally {
|
|
8279
|
+
closeSync3(fd);
|
|
8280
|
+
}
|
|
8281
|
+
return true;
|
|
8282
|
+
} catch {
|
|
8283
|
+
return false;
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
function defaultAgeMs(path) {
|
|
8287
|
+
try {
|
|
8288
|
+
return Date.now() - statSync8(path).mtimeMs;
|
|
8289
|
+
} catch {
|
|
8290
|
+
return null;
|
|
8291
|
+
}
|
|
8292
|
+
}
|
|
8293
|
+
async function withMachineLock(key, fn, io = {}) {
|
|
8294
|
+
const tryCreate = io.tryCreate ?? defaultTryCreate;
|
|
8295
|
+
const remove = io.remove ?? ((p) => {
|
|
8296
|
+
try {
|
|
8297
|
+
rmSync2(p, { force: true });
|
|
8298
|
+
} catch {
|
|
8299
|
+
}
|
|
8300
|
+
});
|
|
8301
|
+
const ageMs2 = io.ageMs ?? defaultAgeMs;
|
|
8302
|
+
const wait = io.wait ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
8303
|
+
const now = io.now ?? Date.now;
|
|
8304
|
+
const path = machineLockPath(key, io.dir ?? tmpdir());
|
|
8305
|
+
const deadline = now() + WAIT_TIMEOUT_MS;
|
|
8306
|
+
let locked = false;
|
|
8307
|
+
for (; ; ) {
|
|
8308
|
+
if (tryCreate(path, `${process.pid}
|
|
8309
|
+
`)) {
|
|
8310
|
+
locked = true;
|
|
8311
|
+
break;
|
|
8312
|
+
}
|
|
8313
|
+
const age = ageMs2(path);
|
|
8314
|
+
if (age !== null && age > STALE_MS) remove(path);
|
|
8315
|
+
if (now() >= deadline) break;
|
|
8316
|
+
await wait(POLL_MS);
|
|
8317
|
+
}
|
|
8318
|
+
try {
|
|
8319
|
+
return await fn(locked);
|
|
8320
|
+
} finally {
|
|
8321
|
+
if (locked) remove(path);
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8324
|
+
|
|
8325
|
+
// src/deps.ts
|
|
8236
8326
|
var PREPARE_TIMEOUT_MS = 15 * 6e4;
|
|
8237
8327
|
var MAX_PLAN_COMMANDS = 12;
|
|
8238
8328
|
async function runCommand(cmd, worktree) {
|
|
8239
|
-
const
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8329
|
+
const exec = async () => {
|
|
8330
|
+
const res = await execa(cmd.run, {
|
|
8331
|
+
cwd: cmd.cwd ?? worktree,
|
|
8332
|
+
shell: true,
|
|
8333
|
+
// команда — строка целиком (у настройки/CI иначе никак), поэтому именно шелл
|
|
8334
|
+
reject: false,
|
|
8335
|
+
timeout: PREPARE_TIMEOUT_MS,
|
|
8336
|
+
...cmd.env ? { env: cmd.env } : {}
|
|
8337
|
+
// execa мержит с окружением процесса, не заменяет его
|
|
8338
|
+
});
|
|
8339
|
+
return res.exitCode === 0;
|
|
8340
|
+
};
|
|
8341
|
+
return cmd.lock ? withMachineLock(cmd.lock, exec) : exec();
|
|
8247
8342
|
}
|
|
8248
8343
|
function safeReadText(p) {
|
|
8249
8344
|
try {
|
|
@@ -8289,19 +8384,19 @@ function overridePlan(override) {
|
|
|
8289
8384
|
function detectPackageManager(worktree, io = {}) {
|
|
8290
8385
|
const exists = io.exists ?? existsSync10;
|
|
8291
8386
|
const readText = io.readText ?? safeReadText;
|
|
8292
|
-
const hasPkgJson = exists(
|
|
8387
|
+
const hasPkgJson = exists(join17(worktree, "package.json"));
|
|
8293
8388
|
if (!hasPkgJson) {
|
|
8294
|
-
if (exists(
|
|
8389
|
+
if (exists(join17(worktree, "deno.json")) || exists(join17(worktree, "deno.jsonc")) || exists(join17(worktree, "deno.lock"))) return "deno";
|
|
8295
8390
|
return null;
|
|
8296
8391
|
}
|
|
8297
|
-
const pkg = readJson(readText,
|
|
8392
|
+
const pkg = readJson(readText, join17(worktree, "package.json"));
|
|
8298
8393
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
8299
8394
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
8300
|
-
if (exists(
|
|
8301
|
-
if (exists(
|
|
8302
|
-
if (exists(
|
|
8303
|
-
if (exists(
|
|
8304
|
-
if (exists(
|
|
8395
|
+
if (exists(join17(worktree, "bun.lock")) || exists(join17(worktree, "bun.lockb"))) return "bun";
|
|
8396
|
+
if (exists(join17(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
8397
|
+
if (exists(join17(worktree, "yarn.lock"))) return "yarn";
|
|
8398
|
+
if (exists(join17(worktree, "package-lock.json"))) return "npm";
|
|
8399
|
+
if (exists(join17(worktree, "deno.lock"))) return "deno";
|
|
8305
8400
|
return "npm";
|
|
8306
8401
|
}
|
|
8307
8402
|
function jsInstallCommand(pm) {
|
|
@@ -8362,20 +8457,21 @@ function detectPlan(worktree, io) {
|
|
|
8362
8457
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
8363
8458
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
8364
8459
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
8365
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
8460
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join17(worktree, "node_modules"), source: "detect" }
|
|
8366
8461
|
];
|
|
8367
8462
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
8368
8463
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
8369
8464
|
}
|
|
8465
|
+
commands.push(...migrateCommands(worktree, pm, io));
|
|
8370
8466
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
8371
8467
|
}
|
|
8372
8468
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
8373
|
-
const hit = marker.files.some((f) => exists(
|
|
8469
|
+
const hit = marker.files.some((f) => exists(join17(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
8374
8470
|
if (!hit) continue;
|
|
8375
8471
|
let ecosystem = marker.ecosystem;
|
|
8376
8472
|
let command = marker.command;
|
|
8377
8473
|
for (const refine of marker.refine ?? []) {
|
|
8378
|
-
if (!exists(
|
|
8474
|
+
if (!exists(join17(worktree, refine.file))) continue;
|
|
8379
8475
|
ecosystem = refine.ecosystem;
|
|
8380
8476
|
command = refine.command;
|
|
8381
8477
|
break;
|
|
@@ -8432,12 +8528,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
8432
8528
|
function ciWorkflowPlan(worktree, io) {
|
|
8433
8529
|
const readText = io.readText ?? safeReadText;
|
|
8434
8530
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
8435
|
-
const dir =
|
|
8531
|
+
const dir = join17(worktree, ".github", "workflows");
|
|
8436
8532
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
8437
8533
|
const seen = /* @__PURE__ */ new Set();
|
|
8438
8534
|
const commands = [];
|
|
8439
8535
|
for (const name of files.sort()) {
|
|
8440
|
-
const yaml = readText(
|
|
8536
|
+
const yaml = readText(join17(dir, name));
|
|
8441
8537
|
if (yaml === null) continue;
|
|
8442
8538
|
for (const raw of workflowRunLines(yaml)) {
|
|
8443
8539
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -8452,7 +8548,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
8452
8548
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
8453
8549
|
}
|
|
8454
8550
|
function workspaceGlobs(worktree, readText) {
|
|
8455
|
-
const yaml = readText(
|
|
8551
|
+
const yaml = readText(join17(worktree, "pnpm-workspace.yaml"));
|
|
8456
8552
|
if (yaml !== null) {
|
|
8457
8553
|
const globs = [];
|
|
8458
8554
|
let inPackages = false;
|
|
@@ -8472,7 +8568,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
8472
8568
|
}
|
|
8473
8569
|
if (globs.length) return globs;
|
|
8474
8570
|
}
|
|
8475
|
-
const pkg = readJson(readText,
|
|
8571
|
+
const pkg = readJson(readText, join17(worktree, "package.json"));
|
|
8476
8572
|
const ws = pkg?.workspaces;
|
|
8477
8573
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
8478
8574
|
return list.filter((g) => typeof g === "string");
|
|
@@ -8483,14 +8579,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
8483
8579
|
if (glob.startsWith("!")) continue;
|
|
8484
8580
|
const star = glob.indexOf("*");
|
|
8485
8581
|
if (star === -1) {
|
|
8486
|
-
const dir =
|
|
8487
|
-
if (io.exists(
|
|
8582
|
+
const dir = join17(worktree, glob);
|
|
8583
|
+
if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
|
|
8488
8584
|
continue;
|
|
8489
8585
|
}
|
|
8490
|
-
const base3 =
|
|
8586
|
+
const base3 = join17(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
8491
8587
|
for (const name of io.listDirs(base3)) {
|
|
8492
|
-
const dir =
|
|
8493
|
-
if (io.exists(
|
|
8588
|
+
const dir = join17(base3, name);
|
|
8589
|
+
if (io.exists(join17(dir, "package.json"))) dirs.add(dir);
|
|
8494
8590
|
}
|
|
8495
8591
|
}
|
|
8496
8592
|
return [...dirs];
|
|
@@ -8518,11 +8614,11 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
8518
8614
|
if (!globs.length) return [];
|
|
8519
8615
|
const pkgs = [];
|
|
8520
8616
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
8521
|
-
const pkg = readJson(readText,
|
|
8617
|
+
const pkg = readJson(readText, join17(dir, "package.json"));
|
|
8522
8618
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
8523
8619
|
const scripts = pkg.scripts ?? {};
|
|
8524
8620
|
const entries = entryPaths(pkg);
|
|
8525
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
8621
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join17(dir, e)));
|
|
8526
8622
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
8527
8623
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
8528
8624
|
}
|
|
@@ -8544,6 +8640,59 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
8544
8640
|
for (const p of targets.values()) visit(p);
|
|
8545
8641
|
return ordered;
|
|
8546
8642
|
}
|
|
8643
|
+
var LOCAL_DB_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]", "host.docker.internal"]);
|
|
8644
|
+
function devDatabaseUrl(worktree, io = {}) {
|
|
8645
|
+
const readText = io.readText ?? safeReadText;
|
|
8646
|
+
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
8647
|
+
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
8648
|
+
for (const file of [".env", ".env.example"]) {
|
|
8649
|
+
const raw = readText(join17(worktree, file));
|
|
8650
|
+
if (raw === null) continue;
|
|
8651
|
+
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
8652
|
+
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
8653
|
+
if (value) return value;
|
|
8654
|
+
}
|
|
8655
|
+
return null;
|
|
8656
|
+
}
|
|
8657
|
+
function isLocalDatabase(url) {
|
|
8658
|
+
try {
|
|
8659
|
+
return LOCAL_DB_HOSTS.has(new URL(url).hostname);
|
|
8660
|
+
} catch {
|
|
8661
|
+
return false;
|
|
8662
|
+
}
|
|
8663
|
+
}
|
|
8664
|
+
function prismaPackages(worktree, io) {
|
|
8665
|
+
const exists = io.exists ?? existsSync10;
|
|
8666
|
+
const readText = io.readText ?? safeReadText;
|
|
8667
|
+
const listDirs = io.listDirs ?? safeListDirs;
|
|
8668
|
+
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
8669
|
+
return dirs.filter((dir) => exists(join17(dir, "prisma", "schema.prisma")) && listDirs(join17(dir, "prisma", "migrations")).length > 0);
|
|
8670
|
+
}
|
|
8671
|
+
function jsExecBin(pm, command) {
|
|
8672
|
+
switch (pm) {
|
|
8673
|
+
case "npm":
|
|
8674
|
+
return `npm exec -- ${command}`;
|
|
8675
|
+
case "bun":
|
|
8676
|
+
return `bun x ${command}`;
|
|
8677
|
+
default:
|
|
8678
|
+
return `${pm} exec ${command}`;
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
function migrateCommands(worktree, pm, io) {
|
|
8682
|
+
if (pm === "deno") return [];
|
|
8683
|
+
const url = devDatabaseUrl(worktree, io);
|
|
8684
|
+
if (!url || !isLocalDatabase(url)) return [];
|
|
8685
|
+
return prismaPackages(worktree, io).map((dir) => ({
|
|
8686
|
+
label: `\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0411\u0414${dir === worktree ? "" : ` (${dir.slice(worktree.length + 1)})`}`,
|
|
8687
|
+
run: jsExecBin(pm, "prisma migrate deploy"),
|
|
8688
|
+
cwd: dir,
|
|
8689
|
+
env: { DATABASE_URL: url },
|
|
8690
|
+
// Ключ мьютекса — сама база: дроны разных репозиториев с разными базами друг друга не ждут.
|
|
8691
|
+
lock: `migrate:${url}`,
|
|
8692
|
+
optional: true,
|
|
8693
|
+
source: "detect"
|
|
8694
|
+
}));
|
|
8695
|
+
}
|
|
8547
8696
|
function planPrepare(worktree, io = {}) {
|
|
8548
8697
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
8549
8698
|
if (!plan) return { source: "none", ecosystem: null, commands: [] };
|
|
@@ -8561,7 +8710,8 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
8561
8710
|
ran: [],
|
|
8562
8711
|
failed: [],
|
|
8563
8712
|
skipped: [],
|
|
8564
|
-
blocked: []
|
|
8713
|
+
blocked: [],
|
|
8714
|
+
warned: []
|
|
8565
8715
|
};
|
|
8566
8716
|
for (const cmd of plan.commands) {
|
|
8567
8717
|
if (cmd.skipIfExists && exists(cmd.skipIfExists)) {
|
|
@@ -8576,6 +8726,8 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
8576
8726
|
if (await exec(cmd)) {
|
|
8577
8727
|
report.ran.push(cmd.label);
|
|
8578
8728
|
report.outcome = "prepared";
|
|
8729
|
+
} else if (cmd.optional) {
|
|
8730
|
+
report.warned.push(cmd.label);
|
|
8579
8731
|
} else {
|
|
8580
8732
|
report.failed.push(cmd.label);
|
|
8581
8733
|
report.outcome = "failed";
|
|
@@ -8592,13 +8744,17 @@ function depsEvents(report) {
|
|
|
8592
8744
|
events.push(
|
|
8593
8745
|
`\u26A0 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 worktree${where} \u0423\u041F\u0410\u041B\u0410 \u043D\u0430 \xAB${report.failed.join(", ")}\xBB \u2014 \u0434\u0440\u043E\u043D \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0411\u0415\u0417 \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F: pre-commit \u0445\u0443\u043A \u043C\u043E\u0436\u0435\u0442 \u043C\u043E\u043B\u0447\u0430 \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C, typecheck/\u0442\u0435\u0441\u0442\u044B \u0431\u0443\u0434\u0443\u0442 \u043A\u0440\u0430\u0441\u043D\u044B\u043C\u0438 \u043D\u0435 \u043F\u043E \u0434\u0435\u043B\u0443 (TASWM-312)`
|
|
8594
8746
|
);
|
|
8747
|
+
if (report.warned.length)
|
|
8748
|
+
events.push(
|
|
8749
|
+
`\u26A0 \u0448\u0430\u0433 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \xAB${report.warned.join(", ")}\xBB \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B \u2014 worktree \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0411\u0410\u0417\u0410 \u041C\u041E\u0413\u041B\u0410 \u041E\u0421\u0422\u0410\u0422\u042C\u0421\u042F \u041D\u0410 \u0421\u0422\u0410\u0420\u041E\u0419 \u0421\u0425\u0415\u041C\u0415 (\u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0431\u0430\u0437\u0430 \u043F\u0440\u043E\u0441\u0442\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u0430) \u2014 TASWM-362`
|
|
8750
|
+
);
|
|
8595
8751
|
if (report.blocked.length)
|
|
8596
8752
|
events.push(`\u26A0 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \u043D\u0435 \u043F\u0443\u0449\u0435\u043D\u0430 \u0433\u0435\u0439\u0442\u043E\u043C \u0434\u043E\u0432\u0435\u0440\u0438\u044F: ${report.blocked.join(", ")} \u2014 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u0433\u043E\u0442\u043E\u0432\u0438\u043B\u0438 (TASWM-312)`);
|
|
8597
8753
|
if (report.outcome === "not-detected")
|
|
8598
8754
|
events.push(
|
|
8599
8755
|
`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u0430: \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043E\u043F\u043E\u0437\u043D\u0430\u043D (\u043D\u0435\u0442 \u0437\u043D\u0430\u043A\u043E\u043C\u044B\u0445 \u043C\u0430\u0440\u043A\u0435\u0440\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432, \u0432 .github/workflows \u043A\u043E\u043C\u0430\u043D\u0434 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u043D\u0435 \u043D\u0430\u0448\u043B\u0438) \u2014 \u0434\u0435\u043C\u043E\u043D \u041D\u0418\u0427\u0415\u0413\u041E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u043B; \u0437\u0430\u0434\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u043C\u043E\u0436\u043D\u043E \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (swarmConfig.prepare) \u2014 TASWM-312`
|
|
8600
8756
|
);
|
|
8601
|
-
if (report.outcome === "nothing-to-do" && !report.skipped.length) {
|
|
8757
|
+
if (report.outcome === "nothing-to-do" && !report.skipped.length && !report.warned.length) {
|
|
8602
8758
|
if (report.source === "project-config") events.push(`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (swarmConfig.prepare \u043F\u0443\u0441\u0442) \u2014 TASWM-312`);
|
|
8603
8759
|
else if (report.ecosystem) events.push(`\u043F\u0440\u043E\u0435\u043A\u0442${where}: \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0435\u0440\u0435\u0434 \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u043D\u0435\u0447\u0435\u0433\u043E, \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u2014 TASWM-312`);
|
|
8604
8760
|
}
|
|
@@ -8617,6 +8773,9 @@ function depsPromptNotice(report) {
|
|
|
8617
8773
|
if (problems.length) {
|
|
8618
8774
|
return `\u26A0 \u041E\u041A\u0420\u0423\u0416\u0415\u041D\u0418\u0415 WORKTREE \u041D\u0415 \u041F\u041E\u0414\u0413\u041E\u0422\u041E\u0412\u041B\u0415\u041D\u041E (TASWM-312): ${problems.join("; ")}. \u041F\u043E\u043A\u0430 \u044D\u0442\u043E \u0442\u0430\u043A, \u0413\u0415\u0419\u0422 \u041C\u041E\u0416\u0415\u0422 \u041D\u0415 \u0420\u0410\u0411\u041E\u0422\u0410\u0422\u042C: pre-commit \u0445\u0443\u043A \u043C\u043E\u043B\u0447\u0430 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0441\u044F (\u043F\u0435\u0440\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0443\u0439\u0434\u0451\u0442 \u0431\u0435\u0437 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A), \u0430 typecheck/\u0442\u0435\u0441\u0442\u044B \u0431\u0443\u0434\u0443\u0442 \u043A\u0440\u0430\u0441\u043D\u044B\u043C\u0438 \u043D\u0435 \u043F\u043E \u0434\u0435\u043B\u0443. \u041F\u0435\u0440\u0432\u044B\u043C \u0434\u0435\u043B\u043E\u043C \u043F\u043E\u0447\u0438\u043D\u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0441\u0430\u043C (\u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u0442\u0430\u043A, \u043A\u0430\u043A \u044D\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 README/CI \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0442\u043E\u043C \u0432\u0435\u0440\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A; \u043D\u0435 \u0441\u0434\u0430\u0432\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443, \u043E\u0431\u044A\u044F\u0441\u043D\u044F\u044F \u043F\u0430\u0434\u0435\u043D\u0438\u044F \xAB\u0441\u043B\u043E\u043C\u0430\u043D\u043D\u044B\u043C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435\u043C\xBB, \u043D\u0435 \u043F\u043E\u043F\u0440\u043E\u0431\u043E\u0432\u0430\u0432 \u044D\u0442\u043E.`;
|
|
8619
8775
|
}
|
|
8776
|
+
if (report.warned.length) {
|
|
8777
|
+
return `\u2139 \u0411\u0410\u0417\u0410 \u041C\u041E\u0413\u041B\u0410 \u041E\u0421\u0422\u0410\u0422\u042C\u0421\u042F \u041D\u0410 \u0421\u0422\u0410\u0420\u041E\u0419 \u0421\u0425\u0415\u041C\u0415 (TASWM-362): \u0448\u0430\u0433 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \xAB${report.warned.join(", ")}\xBB \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B \u2014 \u043E\u0431\u044B\u0447\u043D\u043E \u043F\u043E\u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0431\u0430\u0437\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u0430. \u0421\u0430\u043C worktree \u0433\u043E\u0442\u043E\u0432, typecheck \u0438 \u044E\u043D\u0438\u0442-\u0442\u0435\u0441\u0442\u044B \u0434\u043E\u0441\u0442\u043E\u0432\u0435\u0440\u043D\u044B. \u0415\u0441\u043B\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0448\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0438 \u0443\u0432\u0438\u0434\u0438\u0448\u044C \u043E\u0448\u0438\u0431\u043A\u0443 \u043F\u0440\u043E \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0443\u044E \u043A\u043E\u043B\u043E\u043D\u043A\u0443/\u0442\u0430\u0431\u043B\u0438\u0446\u0443 \u2014 \u044D\u0442\u043E \u043E\u043D\u0430: \u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0431\u0430\u0437\u0443 \u0438 \u043D\u0430\u043A\u0430\u0442\u0438 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0430\u043A, \u043A\u0430\u043A \u044D\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 dev-\u0441\u043A\u0440\u0438\u043F\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`;
|
|
8778
|
+
}
|
|
8620
8779
|
if (report.outcome === "not-detected") {
|
|
8621
8780
|
return `\u2139 \u0414\u0415\u041C\u041E\u041D \u041D\u0415 \u0413\u041E\u0422\u041E\u0412\u0418\u041B \u041E\u041A\u0420\u0423\u0416\u0415\u041D\u0418\u0415 (TASWM-312): \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043E\u043F\u043E\u0437\u043D\u0430\u043D \u043F\u043E \u043C\u0430\u0440\u043A\u0435\u0440\u043D\u044B\u043C \u0444\u0430\u0439\u043B\u0430\u043C, \u043A\u043E\u043C\u0430\u043D\u0434 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u0432 .github/workflows \u0442\u043E\u0436\u0435 \u043D\u0435\u0442. \u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043D\u0443\u0436\u043D\u044B, \u043D\u0435 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u044B \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 README/CI \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u044C \u0438\u0445 \u0441\u0430\u043C \u041F\u0415\u0420\u0415\u0414 \u0442\u0435\u043C, \u043A\u0430\u043A \u0432\u0435\u0440\u0438\u0442\u044C typecheck/\u0442\u0435\u0441\u0442\u0430\u043C.`;
|
|
8622
8781
|
}
|
|
@@ -8976,6 +9135,15 @@ function branchFromPrPatch(currentBranch, headBranch) {
|
|
|
8976
9135
|
event: currentBranch ? `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${currentBranch} \u2192 ${headBranch} (\u043F\u043E\u043B\u0435 \u0431\u044B\u043B\u043E \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0438\u0437 \u0441\u043B\u0430\u0433\u0430 \u2014 TASWM-356)` : `\u0432\u0435\u0442\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u0437\u044F\u0442\u0430 \u0443 PR: ${headBranch} (TASWM-356)`
|
|
8977
9136
|
};
|
|
8978
9137
|
}
|
|
9138
|
+
function prLiveness(st) {
|
|
9139
|
+
if (!st) return "unknown";
|
|
9140
|
+
if (st.merged) return "merged";
|
|
9141
|
+
const state = (st.state ?? "").toUpperCase();
|
|
9142
|
+
if (state === "MERGED") return "merged";
|
|
9143
|
+
if (state === "CLOSED") return "closed";
|
|
9144
|
+
if (state === "UNKNOWN") return "unknown";
|
|
9145
|
+
return "open";
|
|
9146
|
+
}
|
|
8979
9147
|
function applyReviewTransition(db, run, prUrl) {
|
|
8980
9148
|
const { patch, ...t } = reviewTransitionPatch(run, prUrl);
|
|
8981
9149
|
db.updateRun(run.id, patch);
|
|
@@ -9486,7 +9654,7 @@ async function runDroneServer(runId) {
|
|
|
9486
9654
|
|
|
9487
9655
|
// src/droneBridge.ts
|
|
9488
9656
|
init_src();
|
|
9489
|
-
import { createHash as
|
|
9657
|
+
import { createHash as createHash3 } from "crypto";
|
|
9490
9658
|
|
|
9491
9659
|
// src/anomalies.ts
|
|
9492
9660
|
init_src();
|
|
@@ -9881,7 +10049,7 @@ function questionKey(runId, text) {
|
|
|
9881
10049
|
return `${runId} ${text.trim().replace(/\s+/g, " ")}`;
|
|
9882
10050
|
}
|
|
9883
10051
|
function noteKey(runId, text) {
|
|
9884
|
-
return `note:${runId}:${
|
|
10052
|
+
return `note:${runId}:${createHash3("sha1").update(questionKey(runId, text)).digest("hex").slice(0, 16)}`;
|
|
9885
10053
|
}
|
|
9886
10054
|
async function syncDroneChannel(opts) {
|
|
9887
10055
|
const result = { imported: [], answered: [], notes: [] };
|
|
@@ -10162,20 +10330,20 @@ function makeExclusiveReader(opts) {
|
|
|
10162
10330
|
|
|
10163
10331
|
// src/envGate.ts
|
|
10164
10332
|
import { existsSync as existsSync13 } from "fs";
|
|
10165
|
-
import { join as
|
|
10333
|
+
import { join as join24 } from "path";
|
|
10166
10334
|
import { createConnection } from "net";
|
|
10167
10335
|
import { execa as execa2 } from "execa";
|
|
10168
10336
|
|
|
10169
10337
|
// src/platform.ts
|
|
10170
10338
|
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
|
|
10171
|
-
import { delimiter, join as
|
|
10339
|
+
import { delimiter, join as join23 } from "path";
|
|
10172
10340
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
10173
10341
|
if (name.includes("/") || name.includes("\\")) return existsSync12(name) ? name : null;
|
|
10174
10342
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
10175
10343
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
10176
10344
|
for (const dir of dirs) {
|
|
10177
10345
|
for (const ext of exts) {
|
|
10178
|
-
const candidate =
|
|
10346
|
+
const candidate = join23(dir, name + ext);
|
|
10179
10347
|
if (existsSync12(candidate)) return candidate;
|
|
10180
10348
|
}
|
|
10181
10349
|
}
|
|
@@ -10184,6 +10352,31 @@ function resolveCommandPath(name, env = process.env, platform = process.platform
|
|
|
10184
10352
|
function hasCommandInPath(name, env = process.env, platform = process.platform) {
|
|
10185
10353
|
return resolveCommandPath(name, env, platform) !== null;
|
|
10186
10354
|
}
|
|
10355
|
+
function detectGlobalPm(entry, io = {}) {
|
|
10356
|
+
const p = (entry ?? "").replace(/\\/g, "/");
|
|
10357
|
+
if (!p) return "unknown";
|
|
10358
|
+
if (p.endsWith(".ts") || /\/packages\/swarm-daemon\/(src|dist)\//.test(p)) return "sources";
|
|
10359
|
+
if (/\/\.bun\/install\/global\//.test(p) || /\/\.bun\/bin\//.test(p)) return "bun";
|
|
10360
|
+
if (/\/pnpm\//.test(p) || /\/\.pnpm-global\//.test(p) || /\/Library\/pnpm\//.test(p)) return "pnpm";
|
|
10361
|
+
if (/\/\.yarn\//.test(p) || /\/Yarn\/Data\/global\//.test(p) || /\/yarn\/global\//.test(p)) return "yarn";
|
|
10362
|
+
if (/\/node_modules\//.test(p)) return "npm";
|
|
10363
|
+
const env = io.env ?? process.env;
|
|
10364
|
+
const platform = io.platform ?? process.platform;
|
|
10365
|
+
const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
|
|
10366
|
+
return has("npm") ? "npm" : "unknown";
|
|
10367
|
+
}
|
|
10368
|
+
function globalInstallCommand(pm, spec) {
|
|
10369
|
+
switch (pm) {
|
|
10370
|
+
case "pnpm":
|
|
10371
|
+
return `pnpm add -g ${spec}`;
|
|
10372
|
+
case "bun":
|
|
10373
|
+
return `bun add -g ${spec}`;
|
|
10374
|
+
case "yarn":
|
|
10375
|
+
return `yarn global add ${spec}`;
|
|
10376
|
+
default:
|
|
10377
|
+
return `npm i -g ${spec}`;
|
|
10378
|
+
}
|
|
10379
|
+
}
|
|
10187
10380
|
|
|
10188
10381
|
// src/envGate.ts
|
|
10189
10382
|
var PROBE_TIMEOUT_MS = 2500;
|
|
@@ -10235,7 +10428,7 @@ var COMPOSE_MARKERS = [
|
|
|
10235
10428
|
];
|
|
10236
10429
|
function detectRequirements(repo, io = {}) {
|
|
10237
10430
|
const exists = io.exists ?? existsSync13;
|
|
10238
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
10431
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join24(repo, f)));
|
|
10239
10432
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
10240
10433
|
}
|
|
10241
10434
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -10371,8 +10564,8 @@ import { basename as basename3 } from "path";
|
|
|
10371
10564
|
import { serve as serve2 } from "@hono/node-server";
|
|
10372
10565
|
|
|
10373
10566
|
// src/diff.ts
|
|
10374
|
-
import { statSync as
|
|
10375
|
-
import { join as
|
|
10567
|
+
import { statSync as statSync9 } from "fs";
|
|
10568
|
+
import { join as join25 } from "path";
|
|
10376
10569
|
import { $ as $4 } from "execa";
|
|
10377
10570
|
var MAX_FILES = 200;
|
|
10378
10571
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -10515,7 +10708,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
10515
10708
|
}
|
|
10516
10709
|
function mtime(path) {
|
|
10517
10710
|
try {
|
|
10518
|
-
return
|
|
10711
|
+
return statSync9(path).mtimeMs;
|
|
10519
10712
|
} catch {
|
|
10520
10713
|
return 0;
|
|
10521
10714
|
}
|
|
@@ -10540,7 +10733,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
10540
10733
|
files.push(pf);
|
|
10541
10734
|
}
|
|
10542
10735
|
}
|
|
10543
|
-
files.sort((a, b) => mtime(
|
|
10736
|
+
files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
|
|
10544
10737
|
const truncatedFiles = files.length > MAX_FILES;
|
|
10545
10738
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
10546
10739
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -10573,7 +10766,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
10573
10766
|
// package.json
|
|
10574
10767
|
var package_default = {
|
|
10575
10768
|
name: "@taskless-app/swarm",
|
|
10576
|
-
version: "0.6.
|
|
10769
|
+
version: "0.6.2",
|
|
10577
10770
|
type: "module",
|
|
10578
10771
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
10579
10772
|
keywords: [
|
|
@@ -10622,7 +10815,8 @@ var package_default = {
|
|
|
10622
10815
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
10623
10816
|
execa: "^10.0.0",
|
|
10624
10817
|
zod: "3.25.76"
|
|
10625
|
-
}
|
|
10818
|
+
},
|
|
10819
|
+
gitHead: "e812f35a996b00111f3f535b6b0a45b379e7afb2"
|
|
10626
10820
|
};
|
|
10627
10821
|
|
|
10628
10822
|
// src/version.ts
|
|
@@ -12201,48 +12395,10 @@ function closeWebServer(server) {
|
|
|
12201
12395
|
});
|
|
12202
12396
|
}
|
|
12203
12397
|
|
|
12204
|
-
// src/daemonVersion.ts
|
|
12205
|
-
init_git();
|
|
12206
|
-
import { dirname as dirname4 } from "path";
|
|
12207
|
-
import { $ as $5 } from "execa";
|
|
12208
|
-
async function captureDaemonStart(entryFile, now = () => /* @__PURE__ */ new Date()) {
|
|
12209
|
-
try {
|
|
12210
|
-
const repo = await repoRoot(dirname4(entryFile));
|
|
12211
|
-
const sha = (await $5`git -C ${repo} rev-parse HEAD`).stdout.trim();
|
|
12212
|
-
if (!sha) return null;
|
|
12213
|
-
return { repo, startedAt: now().toISOString(), startCommit: sha };
|
|
12214
|
-
} catch {
|
|
12215
|
-
return null;
|
|
12216
|
-
}
|
|
12217
|
-
}
|
|
12218
|
-
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
12219
|
-
async function checkDaemonVersion(start, now = () => /* @__PURE__ */ new Date()) {
|
|
12220
|
-
const base3 = { startedAt: start.startedAt, startCommit: start.startCommit, checkedAt: now().toISOString() };
|
|
12221
|
-
try {
|
|
12222
|
-
await $5({ reject: false })`git -C ${start.repo} fetch --quiet origin`;
|
|
12223
|
-
const mainRef = await remoteMainRef(start.repo);
|
|
12224
|
-
const mainCommit = (await $5`git -C ${start.repo} rev-parse ${mainRef}`).stdout.trim();
|
|
12225
|
-
if (!mainCommit || mainCommit === start.startCommit) {
|
|
12226
|
-
return { ...base3, mainCommit: mainCommit || null, stale: false, staleFiles: [] };
|
|
12227
|
-
}
|
|
12228
|
-
const ancestor = await $5({ reject: false })`git -C ${start.repo} merge-base --is-ancestor ${start.startCommit} ${mainCommit}`;
|
|
12229
|
-
if (ancestor.exitCode !== 0) {
|
|
12230
|
-
return { ...base3, mainCommit, stale: false, staleFiles: [] };
|
|
12231
|
-
}
|
|
12232
|
-
const diff = await $5({ reject: false })(
|
|
12233
|
-
"git",
|
|
12234
|
-
["-C", start.repo, "diff", "--name-only", start.startCommit, mainCommit, "--", ...DAEMON_CODE_PATHS]
|
|
12235
|
-
);
|
|
12236
|
-
const staleFiles = diff.stdout.trim() ? diff.stdout.trim().split("\n") : [];
|
|
12237
|
-
return { ...base3, mainCommit, stale: staleFiles.length > 0, staleFiles: staleFiles.slice(0, 20) };
|
|
12238
|
-
} catch {
|
|
12239
|
-
return { ...base3, mainCommit: null, stale: false, staleFiles: [] };
|
|
12240
|
-
}
|
|
12241
|
-
}
|
|
12242
|
-
|
|
12243
12398
|
// src/release.ts
|
|
12399
|
+
init_src();
|
|
12244
12400
|
init_claudeBin();
|
|
12245
|
-
var SWARM_PACKAGE =
|
|
12401
|
+
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
12246
12402
|
var LATEST_TIMEOUT_MS = 4e3;
|
|
12247
12403
|
async function fetchLatestVersion(opts = {}) {
|
|
12248
12404
|
const fetchFn = opts.fetchFn ?? fetch;
|
|
@@ -12273,12 +12429,30 @@ function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
|
12273
12429
|
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
12274
12430
|
}
|
|
12275
12431
|
|
|
12432
|
+
// src/daemonVersion.ts
|
|
12433
|
+
function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
|
|
12434
|
+
return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
|
|
12435
|
+
}
|
|
12436
|
+
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
12437
|
+
async function checkDaemonVersion(start, opts = {}) {
|
|
12438
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
12439
|
+
const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
|
|
12440
|
+
const latestVersion = await fetchLatest().catch(() => null);
|
|
12441
|
+
return {
|
|
12442
|
+
startedAt: start.startedAt,
|
|
12443
|
+
version: start.version,
|
|
12444
|
+
latestVersion,
|
|
12445
|
+
stale: isOutdated(start.version, latestVersion),
|
|
12446
|
+
checkedAt: now().toISOString()
|
|
12447
|
+
};
|
|
12448
|
+
}
|
|
12449
|
+
|
|
12276
12450
|
// src/hostCommands.ts
|
|
12277
12451
|
init_src();
|
|
12278
12452
|
import { readFileSync as readFileSync18, renameSync as renameSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
12279
|
-
import { join as
|
|
12453
|
+
import { join as join26 } from "path";
|
|
12280
12454
|
function hostCommandStatePath(home) {
|
|
12281
|
-
return
|
|
12455
|
+
return join26(home, "host-command.json");
|
|
12282
12456
|
}
|
|
12283
12457
|
function readState(path) {
|
|
12284
12458
|
try {
|
|
@@ -12421,6 +12595,11 @@ async function executeOne(cmd, opts) {
|
|
|
12421
12595
|
effects.narrate(`\u26A0\uFE0F \u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0448\u0430\u0433\u0435 \xAB${UPDATE_STEP_LABELS[res.step]}\xBB: ${res.message}`);
|
|
12422
12596
|
return false;
|
|
12423
12597
|
}
|
|
12598
|
+
if (res.restart === false) {
|
|
12599
|
+
await ack(cmd.id, "done", { step: res.step, result: res.message });
|
|
12600
|
+
effects.narrate(`\u2705 ${res.message}`);
|
|
12601
|
+
return false;
|
|
12602
|
+
}
|
|
12424
12603
|
return await doRestart(cmd, opts, res.message);
|
|
12425
12604
|
}
|
|
12426
12605
|
}
|
|
@@ -12448,11 +12627,11 @@ async function doRestart(cmd, opts, note) {
|
|
|
12448
12627
|
// src/selfUpdate.ts
|
|
12449
12628
|
init_git();
|
|
12450
12629
|
import { existsSync as existsSync14 } from "fs";
|
|
12451
|
-
import { join as
|
|
12452
|
-
import { $ as $
|
|
12630
|
+
import { join as join27 } from "path";
|
|
12631
|
+
import { $ as $5 } from "execa";
|
|
12453
12632
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
12454
12633
|
var defaultRun = async (cmd, args, cwd) => {
|
|
12455
|
-
const res = await $
|
|
12634
|
+
const res = await $5({ reject: false, cwd })(cmd, args);
|
|
12456
12635
|
return { exitCode: res.exitCode ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
12457
12636
|
};
|
|
12458
12637
|
function tail(text, max = 400) {
|
|
@@ -12490,7 +12669,7 @@ async function runSelfUpdate(io) {
|
|
|
12490
12669
|
};
|
|
12491
12670
|
}
|
|
12492
12671
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
12493
|
-
if (!exists(
|
|
12672
|
+
if (!exists(join27(repo, "pnpm-lock.yaml"))) {
|
|
12494
12673
|
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" };
|
|
12495
12674
|
}
|
|
12496
12675
|
const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -12509,6 +12688,31 @@ async function runSelfUpdate(io) {
|
|
|
12509
12688
|
function defaultSelfUpdateIO(repo, onStep) {
|
|
12510
12689
|
return { repo, run: defaultRun, mainRef: remoteMainRef, onStep };
|
|
12511
12690
|
}
|
|
12691
|
+
async function runPackageSelfUpdate(io) {
|
|
12692
|
+
await io.onStep("fetch", "\u0441\u043C\u043E\u0442\u0440\u044E, \u0447\u0442\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043E");
|
|
12693
|
+
const latest = await io.latest();
|
|
12694
|
+
if (!latest) {
|
|
12695
|
+
return { ok: false, step: "fetch", message: "\u0434\u043E registry.npmjs.org \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u2014 \u043A\u0430\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430, \u0443\u0437\u043D\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C" };
|
|
12696
|
+
}
|
|
12697
|
+
if (!isOutdated(io.currentVersion, latest)) {
|
|
12698
|
+
return { ok: true, restart: false, step: "install", message: `\u0443\u0436\u0435 \u0441\u0432\u0435\u0436\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F: v${io.currentVersion} (\u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest})` };
|
|
12699
|
+
}
|
|
12700
|
+
await io.onStep("install", `\u0441\u0442\u0430\u0432\u043B\u044E v${latest}`);
|
|
12701
|
+
const command = io.installCommand(`${SWARM_PACKAGE}@${latest}`);
|
|
12702
|
+
const code = await io.exec(command);
|
|
12703
|
+
if (code !== 0) {
|
|
12704
|
+
return { ok: false, step: "install", message: `\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 (\u043A\u043E\u0434 ${code}): ${command} \u2014 \u0432\u044B\u043F\u043E\u043B\u043D\u0438 \u044D\u0442\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u043D\u0430 \u0447\u0451\u043C \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F` };
|
|
12705
|
+
}
|
|
12706
|
+
return { ok: true, step: "install", message: `\u043E\u0431\u043D\u043E\u0432\u0438\u043B\u0441\u044F \u0434\u043E v${latest} (\u0431\u044B\u043B\u043E v${io.currentVersion})` };
|
|
12707
|
+
}
|
|
12708
|
+
|
|
12709
|
+
// src/cliIo.ts
|
|
12710
|
+
import { createInterface } from "readline/promises";
|
|
12711
|
+
import { execa as execa3 } from "execa";
|
|
12712
|
+
async function execLoud(command) {
|
|
12713
|
+
const res = await execa3(command, { shell: true, reject: false, stdio: "inherit" });
|
|
12714
|
+
return res.exitCode ?? 1;
|
|
12715
|
+
}
|
|
12512
12716
|
|
|
12513
12717
|
// src/narration.ts
|
|
12514
12718
|
var RING_LIMIT = 20;
|
|
@@ -12584,14 +12788,14 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
12584
12788
|
|
|
12585
12789
|
// src/anomalyState.ts
|
|
12586
12790
|
import { existsSync as existsSync15, readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
12587
|
-
import { join as
|
|
12791
|
+
import { join as join28 } from "path";
|
|
12588
12792
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
12589
12793
|
var MAX_RESOLVED = 200;
|
|
12590
12794
|
function emptyState() {
|
|
12591
12795
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
12592
12796
|
}
|
|
12593
12797
|
function anomalyStatePath(home) {
|
|
12594
|
-
return
|
|
12798
|
+
return join28(home, "anomaly-state.json");
|
|
12595
12799
|
}
|
|
12596
12800
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
12597
12801
|
const exists = io.exists ?? existsSync15;
|
|
@@ -12871,7 +13075,7 @@ init_src();
|
|
|
12871
13075
|
init_src();
|
|
12872
13076
|
|
|
12873
13077
|
// src/gitHistory.ts
|
|
12874
|
-
import { $ as $
|
|
13078
|
+
import { $ as $6 } from "execa";
|
|
12875
13079
|
var REC = "";
|
|
12876
13080
|
var FIELD = "";
|
|
12877
13081
|
var LOG_FORMAT = `${REC}%H${FIELD}%s`;
|
|
@@ -12923,7 +13127,7 @@ function groupTaskHistory(commits, opts = {}) {
|
|
|
12923
13127
|
async function readCommitLog(repo, opts = {}) {
|
|
12924
13128
|
const max = opts.maxCommits ?? 1500;
|
|
12925
13129
|
try {
|
|
12926
|
-
const { stdout } = await $
|
|
13130
|
+
const { stdout } = await $6({ cwd: repo, reject: false })`git log --no-merges --name-only --pretty=format:${LOG_FORMAT} -n ${String(max)} ${opts.ref ?? "HEAD"}`;
|
|
12927
13131
|
return parseGitLog(stdout ?? "");
|
|
12928
13132
|
} catch {
|
|
12929
13133
|
return [];
|
|
@@ -12934,7 +13138,7 @@ async function readTaskHistory(repo, opts = {}) {
|
|
|
12934
13138
|
}
|
|
12935
13139
|
async function headSha(repo) {
|
|
12936
13140
|
try {
|
|
12937
|
-
const { stdout, exitCode } = await $
|
|
13141
|
+
const { stdout, exitCode } = await $6({ cwd: repo, reject: false })`git rev-parse HEAD`;
|
|
12938
13142
|
return exitCode === 0 ? stdout.trim() : null;
|
|
12939
13143
|
} catch {
|
|
12940
13144
|
return null;
|
|
@@ -13217,20 +13421,20 @@ function countActiveDrones(db) {
|
|
|
13217
13421
|
function countActiveReviewers(db) {
|
|
13218
13422
|
return db.listRuns().filter((r) => r.kind === "reviewer" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
|
|
13219
13423
|
}
|
|
13220
|
-
async function
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
deps.kill(rv.pid);
|
|
13226
|
-
} catch {
|
|
13227
|
-
}
|
|
13424
|
+
async function closeReviewerRun(deps, rv, reason) {
|
|
13425
|
+
if (rv.pid != null && deps.kill && deps.isAlive(rv.pid)) {
|
|
13426
|
+
try {
|
|
13427
|
+
deps.kill(rv.pid);
|
|
13428
|
+
} catch {
|
|
13228
13429
|
}
|
|
13229
|
-
if (rv.worktree_path) await deps.removeWorktree(rv.repo, rv.worktree_path).catch(() => {
|
|
13230
|
-
});
|
|
13231
|
-
deps.db.updateRun(rv.id, { status: "done" });
|
|
13232
|
-
deps.db.addEvent(rv.id, "done", "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E \u2014 \u0440\u0435\u0432\u044C\u044E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E");
|
|
13233
13430
|
}
|
|
13431
|
+
if (rv.worktree_path) await deps.removeWorktree(rv.repo, rv.worktree_path).catch(() => {
|
|
13432
|
+
});
|
|
13433
|
+
deps.db.updateRun(rv.id, { status: "done", stop_reason: null });
|
|
13434
|
+
deps.db.addEvent(rv.id, "done", reason);
|
|
13435
|
+
}
|
|
13436
|
+
async function closeReviewersOnDeadPr(deps, reviewRunId, reason) {
|
|
13437
|
+
for (const rv of deps.db.activeReviewersFor(reviewRunId)) await closeReviewerRun(deps, rv, reason);
|
|
13234
13438
|
}
|
|
13235
13439
|
function ciWaitStartedMs(db, runId) {
|
|
13236
13440
|
const evs = db.events(runId);
|
|
@@ -13304,7 +13508,39 @@ async function finalizeMergedReview(deps, run) {
|
|
|
13304
13508
|
deps.db.addEvent(run.id, "merged", run.pr_url ?? "");
|
|
13305
13509
|
deps.db.updateRun(run.id, { status: "done" });
|
|
13306
13510
|
deps.db.addEvent(run.id, "done");
|
|
13307
|
-
await
|
|
13511
|
+
await closeReviewersOnDeadPr(deps, run.id, "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E \u2014 \u0440\u0435\u0432\u044C\u044E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E");
|
|
13512
|
+
}
|
|
13513
|
+
async function finalizeClosedPrReview(deps, run, reason) {
|
|
13514
|
+
await closeReviewersOnDeadPr(deps, run.id, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E");
|
|
13515
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
13516
|
+
});
|
|
13517
|
+
deps.db.updateRun(run.id, { status: "done" });
|
|
13518
|
+
deps.db.addEvent(run.id, "done", reason);
|
|
13519
|
+
}
|
|
13520
|
+
var REVIEW_PR_UNKNOWN_MARK = "\u0441\u0442\u0430\u0442\u0443\u0441 PR \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D";
|
|
13521
|
+
async function prGateForReviewer(deps, run, prUrl, when) {
|
|
13522
|
+
const st = await deps.prStatus(prUrl).catch(() => null);
|
|
13523
|
+
switch (prLiveness(st)) {
|
|
13524
|
+
case "merged":
|
|
13525
|
+
await finalizeMergedReview(deps, run);
|
|
13526
|
+
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D, ${when} \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D, \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.`);
|
|
13527
|
+
return { kind: "finalized" };
|
|
13528
|
+
case "closed":
|
|
13529
|
+
await finalizeClosedPrReview(deps, run, `PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 (${when}) \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E`);
|
|
13530
|
+
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, ${when} \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
|
|
13531
|
+
return { kind: "finalized" };
|
|
13532
|
+
case "unknown":
|
|
13533
|
+
if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(REVIEW_PR_UNKNOWN_MARK))) {
|
|
13534
|
+
deps.db.addEvent(run.id, "step", `${REVIEW_PR_UNKNOWN_MARK} (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0438\u043B\u0438 \u0432 pr_url \u043D\u0435 PR) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C, \u0441\u043F\u0440\u043E\u0441\u0438\u043C \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435`);
|
|
13535
|
+
}
|
|
13536
|
+
return { kind: "wait" };
|
|
13537
|
+
default:
|
|
13538
|
+
if (st.conflicting || st.isDraft) {
|
|
13539
|
+
deps.db.addEvent(run.id, "step", `PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, ${when} \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430`);
|
|
13540
|
+
return { kind: "wait" };
|
|
13541
|
+
}
|
|
13542
|
+
return { kind: "spawn", st };
|
|
13543
|
+
}
|
|
13308
13544
|
}
|
|
13309
13545
|
function doneMergedPrForSlug(db, slug) {
|
|
13310
13546
|
const done = db.listRuns("done").find((r) => r.task_slug === slug && r.pr_url && r.kind !== "hero" && r.resolves_run_id == null && r.reviews_run_id == null);
|
|
@@ -13367,6 +13603,22 @@ async function closeMergedTaskRuns(deps) {
|
|
|
13367
13603
|
}
|
|
13368
13604
|
}
|
|
13369
13605
|
}
|
|
13606
|
+
async function closeOrphanedReviewers(deps) {
|
|
13607
|
+
for (const rv of deps.db.listRuns()) {
|
|
13608
|
+
if (rv.kind !== "reviewer" || rv.reviews_run_id == null) continue;
|
|
13609
|
+
if (!REVIEWER_ALIVE_STATUSES.has(rv.status)) continue;
|
|
13610
|
+
const parent = deps.db.getRun(rv.reviews_run_id);
|
|
13611
|
+
if (parent && !isTerminalStatus(parent.status)) continue;
|
|
13612
|
+
try {
|
|
13613
|
+
const why = parent ? `\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442 (${STATUS_META[parent.status]?.label ?? parent.status})` : "\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438\u0441\u0447\u0435\u0437";
|
|
13614
|
+
await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
|
|
13615
|
+
if (parent) deps.db.addEvent(parent.id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${rv.name} (#${rv.id}) \u0437\u0430\u043A\u0440\u044B\u0442: ${why}`);
|
|
13616
|
+
narrate(deps, `${droneLabel(rv)} \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u043D\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u2014 \u0433\u0430\u0448\u0443 \u0441\u0430\u043C, \u0441\u043B\u043E\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D.`);
|
|
13617
|
+
} catch (e) {
|
|
13618
|
+
console.error(`[overmind] \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u0435 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${rv.id} \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:`, e);
|
|
13619
|
+
}
|
|
13620
|
+
}
|
|
13621
|
+
}
|
|
13370
13622
|
var CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0446\u0435\u0441\u0441) \u2014 \u0442\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log` \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0432\u043E\u0438\u0445 \u0448\u0430\u0433\u043E\u0432: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438 \u0441 \u043C\u0435\u0441\u0442\u0430 \u043E\u0431\u0440\u044B\u0432\u0430. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u041A\u041E\u041C\u041C\u0418\u0422\u042C \u0418 \u041F\u0423\u0428\u042C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0448\u0430\u0433\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url). \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask.";
|
|
13371
13623
|
function buildCiWakeNudge(summary) {
|
|
13372
13624
|
return [
|
|
@@ -13472,7 +13724,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
13472
13724
|
}
|
|
13473
13725
|
return parts.join("\n");
|
|
13474
13726
|
}
|
|
13475
|
-
var HERO_CHARTER_PATH =
|
|
13727
|
+
var HERO_CHARTER_PATH = join29(dirname4(THIS_FILE), "..", "docs", "HERO.md");
|
|
13476
13728
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
13477
13729
|
const name = run.name || `#${run.id}`;
|
|
13478
13730
|
const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
|
|
@@ -13811,7 +14063,7 @@ function repoHasCi(deps, run) {
|
|
|
13811
14063
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
13812
14064
|
for (const base3 of [worktreePath, repo]) {
|
|
13813
14065
|
if (!base3) continue;
|
|
13814
|
-
const dir =
|
|
14066
|
+
const dir = join29(base3, ".github", "workflows");
|
|
13815
14067
|
try {
|
|
13816
14068
|
if (readdirSync12(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
13817
14069
|
} catch {
|
|
@@ -13974,10 +14226,9 @@ async function spawnFreshReviewerAfterStall(deps, run, why) {
|
|
|
13974
14226
|
if (run.review_cycle >= REVIEW_CYCLE_LIMIT || deps.db.reviewerAttemptCountFor(run.id) >= REVIEW_SPAWN_LIMIT) {
|
|
13975
14227
|
return;
|
|
13976
14228
|
}
|
|
13977
|
-
const
|
|
13978
|
-
if (
|
|
13979
|
-
|
|
13980
|
-
}
|
|
14229
|
+
const gate = await prGateForReviewer(deps, run, run.pr_url, "\u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B CI");
|
|
14230
|
+
if (gate.kind !== "spawn") return;
|
|
14231
|
+
const st = gate.st;
|
|
13981
14232
|
try {
|
|
13982
14233
|
await deps.spawnReviewer(run, st.files ?? []);
|
|
13983
14234
|
narrate(deps, `\u0420\u0435\u0432\u044C\u044E ${droneLabel(run)} \u043F\u043E\u0442\u0435\u0440\u044F\u043B\u043E \u0441\u0435\u0441\u0441\u0438\u044E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430, \u0436\u0434\u0430\u0432 CI (${why}) \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0436\u0434\u0443 \u043A\u0430\u0440\u0443\u0441\u0435\u043B\u044C.`);
|
|
@@ -14710,6 +14961,11 @@ async function tickOnce(deps) {
|
|
|
14710
14961
|
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${run.task_slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
14711
14962
|
continue;
|
|
14712
14963
|
}
|
|
14964
|
+
if (prLiveness(st) === "closed") {
|
|
14965
|
+
await finalizeClosedPrReview(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442");
|
|
14966
|
+
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 ${run.task_slug} \u0441\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0441 \u0440\u0435\u0432\u044C\u044E, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
14967
|
+
continue;
|
|
14968
|
+
}
|
|
14713
14969
|
if (touchesDaemonCode(st.files ?? [])) {
|
|
14714
14970
|
const already = deps.db.events(run.id).some((e) => (e.text ?? "").includes(SELF_REFERENCE_MARK));
|
|
14715
14971
|
if (!already) {
|
|
@@ -14803,20 +15059,9 @@ async function tickOnce(deps) {
|
|
|
14803
15059
|
} else {
|
|
14804
15060
|
const reviewersCapOk = deps.reviewCap === void 0 || countActiveReviewers(deps.db) < deps.reviewCap;
|
|
14805
15061
|
if ((!deps.resourcesOk || deps.resourcesOk()) && reviewersCapOk) {
|
|
14806
|
-
const
|
|
14807
|
-
if (
|
|
14808
|
-
|
|
14809
|
-
narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E, \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`);
|
|
14810
|
-
} else if (freshSt.state === "CLOSED") {
|
|
14811
|
-
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
14812
|
-
});
|
|
14813
|
-
deps.db.updateRun(run.id, { status: "done" });
|
|
14814
|
-
deps.db.addEvent(run.id, "done", "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E");
|
|
14815
|
-
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`);
|
|
14816
|
-
} else if (freshSt.conflicting || freshSt.isDraft) {
|
|
14817
|
-
deps.db.addEvent(run.id, "step", "PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, \u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430");
|
|
14818
|
-
} else {
|
|
14819
|
-
run = syncBranchFromPr(deps.db, run, freshSt.headBranch);
|
|
15062
|
+
const gate = await prGateForReviewer(deps, run, prUrl, "\u043F\u043E\u043A\u0430 \u0436\u0434\u0430\u043B \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430");
|
|
15063
|
+
if (gate.kind === "spawn") {
|
|
15064
|
+
run = syncBranchFromPr(deps.db, run, gate.st.headBranch);
|
|
14820
15065
|
try {
|
|
14821
15066
|
await deps.spawnReviewer(run, st.files ?? []);
|
|
14822
15067
|
narrate(deps, `PR ${droneLabel(run)} \u0433\u043E\u0442\u043E\u0432 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430.`);
|
|
@@ -14841,6 +15086,7 @@ async function tickOnce(deps) {
|
|
|
14841
15086
|
}
|
|
14842
15087
|
}
|
|
14843
15088
|
await closeMergedTaskRuns(deps);
|
|
15089
|
+
await closeOrphanedReviewers(deps);
|
|
14844
15090
|
refreshReviewPhases(deps);
|
|
14845
15091
|
let liveChildPpids;
|
|
14846
15092
|
try {
|
|
@@ -15218,7 +15464,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
15218
15464
|
workspace_id: reviewRun.workspace_id
|
|
15219
15465
|
// та же задача, тот же воркспейс (TASWM-288)
|
|
15220
15466
|
});
|
|
15221
|
-
const wt =
|
|
15467
|
+
const wt = join29(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
15222
15468
|
try {
|
|
15223
15469
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
15224
15470
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -15270,7 +15516,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
15270
15516
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
15271
15517
|
workspace_id: reviewRun.workspace_id
|
|
15272
15518
|
});
|
|
15273
|
-
const wt =
|
|
15519
|
+
const wt = join29(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
15274
15520
|
try {
|
|
15275
15521
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
15276
15522
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -15503,7 +15749,7 @@ async function reportWorkspaceAccess(tl, db) {
|
|
|
15503
15749
|
}
|
|
15504
15750
|
async function runDaemon(opts) {
|
|
15505
15751
|
installCrashGuards();
|
|
15506
|
-
const daemonStart =
|
|
15752
|
+
const daemonStart = captureDaemonStart();
|
|
15507
15753
|
const latestRelease = fetchLatestVersion();
|
|
15508
15754
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
15509
15755
|
if (!opts.repo) {
|
|
@@ -15550,8 +15796,10 @@ async function runDaemon(opts) {
|
|
|
15550
15796
|
}
|
|
15551
15797
|
} : void 0;
|
|
15552
15798
|
let lastDroneCap = null;
|
|
15799
|
+
let lastOwnCapacity = null;
|
|
15553
15800
|
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
15554
15801
|
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
15802
|
+
let operatorMaxDrones = null;
|
|
15555
15803
|
const launch = makeLauncher({
|
|
15556
15804
|
overmindEntry: THIS_FILE,
|
|
15557
15805
|
dbPath: DB_PATH,
|
|
@@ -15589,7 +15837,7 @@ async function runDaemon(opts) {
|
|
|
15589
15837
|
repoMap = { run: rebuildRepoMap, intervalMs: SWARM_REPO_MAP_MS, lastAt: Date.now() };
|
|
15590
15838
|
}
|
|
15591
15839
|
let daemonVersion;
|
|
15592
|
-
if (
|
|
15840
|
+
if (SWARM_VERSION_CHECK_MS > 0) {
|
|
15593
15841
|
let cached = null;
|
|
15594
15842
|
daemonVersion = {
|
|
15595
15843
|
run: async () => {
|
|
@@ -15606,7 +15854,8 @@ async function runDaemon(opts) {
|
|
|
15606
15854
|
const client = db;
|
|
15607
15855
|
heartbeat = {
|
|
15608
15856
|
push: async () => {
|
|
15609
|
-
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null,
|
|
15857
|
+
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null, lastOwnCapacity);
|
|
15858
|
+
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
15610
15859
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
15611
15860
|
if (reply.logRequests.length) {
|
|
15612
15861
|
await serveLogRequests(
|
|
@@ -15699,7 +15948,10 @@ async function runDaemon(opts) {
|
|
|
15699
15948
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
15700
15949
|
ttlMs: 1e4
|
|
15701
15950
|
});
|
|
15702
|
-
maxDrones = async () =>
|
|
15951
|
+
maxDrones = async () => {
|
|
15952
|
+
const own = lastOwnCapacity = hostMaxDrones ?? await readMaxDrones();
|
|
15953
|
+
return lastDroneCap = operatorMaxDrones != null ? Math.min(own, operatorMaxDrones) : own;
|
|
15954
|
+
};
|
|
15703
15955
|
}
|
|
15704
15956
|
let verifyModeReader;
|
|
15705
15957
|
let baseBranchReader;
|
|
@@ -16200,10 +16452,21 @@ async function runDaemon(opts) {
|
|
|
16200
16452
|
return wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate }, run.id);
|
|
16201
16453
|
},
|
|
16202
16454
|
update: async (onStep) => {
|
|
16203
|
-
|
|
16204
|
-
|
|
16455
|
+
const pm = detectGlobalPm(process.argv[1]);
|
|
16456
|
+
if (pm !== "sources") {
|
|
16457
|
+
return runPackageSelfUpdate({
|
|
16458
|
+
currentVersion: PACKAGE_VERSION,
|
|
16459
|
+
installCommand: (spec) => globalInstallCommand(pm, spec),
|
|
16460
|
+
latest: () => fetchLatestVersion(),
|
|
16461
|
+
exec: execLoud,
|
|
16462
|
+
onStep
|
|
16463
|
+
});
|
|
16464
|
+
}
|
|
16465
|
+
const repo = await repoRoot(dirname4(THIS_FILE)).catch(() => null);
|
|
16466
|
+
if (!repo) {
|
|
16467
|
+
return { ok: false, step: "fetch", message: "\u0431\u0435\u0433\u0443 \u0438\u0437 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432, \u043D\u043E git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0430\u0434 \u043D\u0438\u043C\u0438 \u043D\u0435\u0442 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" };
|
|
16205
16468
|
}
|
|
16206
|
-
return runSelfUpdate(defaultSelfUpdateIO(
|
|
16469
|
+
return runSelfUpdate(defaultSelfUpdateIO(repo, onStep));
|
|
16207
16470
|
},
|
|
16208
16471
|
narrate: narrator.narrate
|
|
16209
16472
|
};
|
|
@@ -16409,6 +16672,7 @@ export {
|
|
|
16409
16672
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
16410
16673
|
REVIEW_CYCLE_LIMIT,
|
|
16411
16674
|
REVIEW_ESCALATION_MARK,
|
|
16675
|
+
REVIEW_PR_UNKNOWN_MARK,
|
|
16412
16676
|
REVIEW_SPAWN_LIMIT,
|
|
16413
16677
|
REVIEW_UNRECOVERABLE_MARK,
|
|
16414
16678
|
SELF_REFERENCE_MARK,
|