@taskless-app/swarm 0.6.67 → 0.6.68
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 +202 -39
- package/dist/overmind.js +299 -42
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -3530,7 +3530,7 @@ var init_linkFailure = __esm({
|
|
|
3530
3530
|
"use strict";
|
|
3531
3531
|
init_agentToken();
|
|
3532
3532
|
UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
|
|
3533
|
-
OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
|
|
3533
|
+
OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout|request timed out|-32001\b/i;
|
|
3534
3534
|
MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
|
|
3535
3535
|
}
|
|
3536
3536
|
});
|
|
@@ -3880,7 +3880,7 @@ var init_package = __esm({
|
|
|
3880
3880
|
"package.json"() {
|
|
3881
3881
|
package_default = {
|
|
3882
3882
|
name: "@taskless-app/swarm",
|
|
3883
|
-
version: "0.6.
|
|
3883
|
+
version: "0.6.68",
|
|
3884
3884
|
type: "module",
|
|
3885
3885
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
3886
3886
|
keywords: [
|
|
@@ -3929,9 +3929,10 @@ var init_package = __esm({
|
|
|
3929
3929
|
"@hono/node-server": "^2.0.12",
|
|
3930
3930
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
3931
3931
|
execa: "^10.0.0",
|
|
3932
|
+
undici: "^7.25.0",
|
|
3932
3933
|
zod: "3.25.76"
|
|
3933
3934
|
},
|
|
3934
|
-
gitHead: "
|
|
3935
|
+
gitHead: "4bfd78926aa37ef7de8ea06a6763d7e84a2fded6"
|
|
3935
3936
|
};
|
|
3936
3937
|
}
|
|
3937
3938
|
});
|
|
@@ -4664,6 +4665,47 @@ var init_shotUpload = __esm({
|
|
|
4664
4665
|
}
|
|
4665
4666
|
});
|
|
4666
4667
|
|
|
4668
|
+
// src/prodHttp.ts
|
|
4669
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
4670
|
+
function prodDispatcher() {
|
|
4671
|
+
agent ??= new Agent({
|
|
4672
|
+
keepAliveTimeout: PROD_KEEPALIVE_MS,
|
|
4673
|
+
keepAliveMaxTimeout: PROD_KEEPALIVE_MS,
|
|
4674
|
+
headersTimeout: PROD_SOCKET_TIMEOUT_MS,
|
|
4675
|
+
bodyTimeout: PROD_SOCKET_TIMEOUT_MS,
|
|
4676
|
+
connect: { timeout: PROD_CONNECT_TIMEOUT_MS }
|
|
4677
|
+
});
|
|
4678
|
+
return agent;
|
|
4679
|
+
}
|
|
4680
|
+
function isProdTimeout(e) {
|
|
4681
|
+
for (let cur = e, depth = 0; cur != null && depth < 4; cur = cur.cause, depth++) {
|
|
4682
|
+
const code = cur.code;
|
|
4683
|
+
const name = cur.name;
|
|
4684
|
+
const msg = cur instanceof Error ? cur.message : String(cur);
|
|
4685
|
+
if (code === -32001 || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") return true;
|
|
4686
|
+
if (name === "TimeoutError" || /request timed out/i.test(msg)) return true;
|
|
4687
|
+
}
|
|
4688
|
+
return false;
|
|
4689
|
+
}
|
|
4690
|
+
var PROD_CALL_TIMEOUT_MS, FETCH_GRACE_MS, PROD_KEEPALIVE_MS, PROD_SOCKET_TIMEOUT_MS, PROD_CONNECT_TIMEOUT_MS, agent, prodFetch;
|
|
4691
|
+
var init_prodHttp = __esm({
|
|
4692
|
+
"src/prodHttp.ts"() {
|
|
4693
|
+
"use strict";
|
|
4694
|
+
PROD_CALL_TIMEOUT_MS = 15e3;
|
|
4695
|
+
FETCH_GRACE_MS = 5e3;
|
|
4696
|
+
PROD_KEEPALIVE_MS = 1e4;
|
|
4697
|
+
PROD_SOCKET_TIMEOUT_MS = 3e4;
|
|
4698
|
+
PROD_CONNECT_TIMEOUT_MS = 1e4;
|
|
4699
|
+
agent = null;
|
|
4700
|
+
prodFetch = (url, init) => {
|
|
4701
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
4702
|
+
const deadline = method === "GET" ? null : AbortSignal.timeout(PROD_CALL_TIMEOUT_MS + FETCH_GRACE_MS);
|
|
4703
|
+
const signal = deadline && init?.signal ? AbortSignal.any([init.signal, deadline]) : deadline ?? init?.signal ?? void 0;
|
|
4704
|
+
return undiciFetch(url, { ...init, ...signal ? { signal } : {}, dispatcher: prodDispatcher() });
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
});
|
|
4708
|
+
|
|
4667
4709
|
// src/workspace.ts
|
|
4668
4710
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
4669
4711
|
import { join as join19 } from "path";
|
|
@@ -4715,8 +4757,8 @@ function parseJsonContent(res) {
|
|
|
4715
4757
|
}
|
|
4716
4758
|
async function resolveApiToken(api, explicit) {
|
|
4717
4759
|
if (explicit) return explicit;
|
|
4718
|
-
const
|
|
4719
|
-
if (
|
|
4760
|
+
const agent2 = loadAgentToken();
|
|
4761
|
+
if (agent2?.token) return agent2.token;
|
|
4720
4762
|
const store = loadStore();
|
|
4721
4763
|
if (store) {
|
|
4722
4764
|
const r = await ensureAccess(store, { api });
|
|
@@ -4745,7 +4787,9 @@ async function connectSwarmMcp(cfg) {
|
|
|
4745
4787
|
}
|
|
4746
4788
|
const url = new URL(mcpEndpoint(api));
|
|
4747
4789
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
4748
|
-
requestInit: { headers: { Authorization: `Bearer ${token}` } }
|
|
4790
|
+
requestInit: { headers: { Authorization: `Bearer ${token}` } },
|
|
4791
|
+
fetch: prodFetch
|
|
4792
|
+
// TASWM-611
|
|
4749
4793
|
});
|
|
4750
4794
|
const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
|
|
4751
4795
|
try {
|
|
@@ -4764,12 +4808,31 @@ async function connectSwarmMcp(cfg) {
|
|
|
4764
4808
|
}
|
|
4765
4809
|
swarmConnectFailure = null;
|
|
4766
4810
|
noteTokenAccepted();
|
|
4767
|
-
|
|
4811
|
+
const timeout = cfg?.callTimeoutMs ?? PROD_CALL_TIMEOUT_MS;
|
|
4812
|
+
return async (name, args) => {
|
|
4813
|
+
try {
|
|
4814
|
+
return await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) }, void 0, { timeout });
|
|
4815
|
+
} catch (e) {
|
|
4816
|
+
if (isProdTimeout(e)) {
|
|
4817
|
+
console.warn(`${prodTimeoutLine("Swarm MCP", name, timeout)} \u2014 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u043E, \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0432\u044B\u0437\u043E\u0432 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u0441\u044F`);
|
|
4818
|
+
}
|
|
4819
|
+
void client.close().catch(() => {
|
|
4820
|
+
});
|
|
4821
|
+
throw e;
|
|
4822
|
+
}
|
|
4823
|
+
};
|
|
4824
|
+
}
|
|
4825
|
+
function prodTimeoutLine(scope, tool, timeoutMs) {
|
|
4826
|
+
return `[${scope}] \u23F1 ${tool}: \u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(timeoutMs / 100) / 10}\u0441, \u0432\u044B\u0437\u043E\u0432 \u043F\u0440\u0435\u0440\u0432\u0430\u043D`;
|
|
4768
4827
|
}
|
|
4769
4828
|
function lastSwarmConnectFailure() {
|
|
4770
4829
|
return swarmConnectFailure;
|
|
4771
4830
|
}
|
|
4772
4831
|
function swarmToolArgs(name, args, workspace) {
|
|
4832
|
+
if ("workspaceId" in args && args.workspaceId == null) {
|
|
4833
|
+
const { workspaceId: _drop, ...rest } = args;
|
|
4834
|
+
return rest;
|
|
4835
|
+
}
|
|
4773
4836
|
if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
|
|
4774
4837
|
return { workspaceId: workspace, ...args };
|
|
4775
4838
|
}
|
|
@@ -4784,6 +4847,7 @@ var init_taskless = __esm({
|
|
|
4784
4847
|
init_oauth();
|
|
4785
4848
|
init_agentToken();
|
|
4786
4849
|
init_linkFailure();
|
|
4850
|
+
init_prodHttp();
|
|
4787
4851
|
init_toolFailure();
|
|
4788
4852
|
init_authState();
|
|
4789
4853
|
init_workspace();
|
|
@@ -7697,7 +7761,7 @@ var init_updateCli = __esm({
|
|
|
7697
7761
|
// src/cli.ts
|
|
7698
7762
|
init_cliIo();
|
|
7699
7763
|
import { basename as basename6, dirname as dirname5, join as join27 } from "path";
|
|
7700
|
-
import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync12, statSync as statSync12, openSync as
|
|
7764
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync12, statSync as statSync12, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
|
|
7701
7765
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
7702
7766
|
|
|
7703
7767
|
// src/swarmClient.ts
|
|
@@ -10061,7 +10125,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
10061
10125
|
// 14.07/18.07, TASWM-201 — общий карантин; здесь — конкретный источник). Резолвим
|
|
10062
10126
|
// слаг→cuid через get_task (кэш — taskIdCache) ПЕРЕД тем как слать create.
|
|
10063
10127
|
taskIdCache = /* @__PURE__ */ new Map();
|
|
10064
|
-
// task_slug → cuid прода
|
|
10128
|
+
// task_slug → cuid прода и воркспейс задачи
|
|
10065
10129
|
// Create для этого runKey резолвился окончательно неудачно (задача не найдена/
|
|
10066
10130
|
// недоступна) — событие/вопросы того же прогона тоже никогда не получат prod-id,
|
|
10067
10131
|
// без карантина они бы бросали «нет prod-id» на каждом флаше и СНОВА вставали
|
|
@@ -10076,12 +10140,20 @@ var SwarmClient = class _SwarmClient {
|
|
|
10076
10140
|
const cacheKey = `${taskRef}\0${repoFullName ?? ""}\0${workspaceId ?? ""}`;
|
|
10077
10141
|
const cached = this.taskIdCache.get(cacheKey);
|
|
10078
10142
|
if (cached) return cached;
|
|
10143
|
+
const first = await this.readTaskRef(taskRef, repoFullName, workspaceId);
|
|
10144
|
+
const res = first === null && repoFullName && !workspaceId ? await this.readTaskRef(taskRef, repoFullName, null) : first;
|
|
10145
|
+
if (res && res !== "transient") this.taskIdCache.set(cacheKey, res);
|
|
10146
|
+
return res;
|
|
10147
|
+
}
|
|
10148
|
+
/** Один get_task: cuid задачи и её воркспейс; null — прод ответил «нет такой». */
|
|
10149
|
+
async readTaskRef(taskRef, repoFullName, workspaceId) {
|
|
10079
10150
|
let res;
|
|
10080
10151
|
try {
|
|
10081
10152
|
res = await this.call("get_task", {
|
|
10082
10153
|
id: taskRef,
|
|
10083
10154
|
...repoFullName ? { repoFullName } : {},
|
|
10084
|
-
|
|
10155
|
+
// null — «без пина демона» (см. swarmToolArgs), undefined — пин по умолчанию.
|
|
10156
|
+
...workspaceId ? { workspaceId } : workspaceId === null ? { workspaceId: null } : {}
|
|
10085
10157
|
});
|
|
10086
10158
|
} catch {
|
|
10087
10159
|
return "transient";
|
|
@@ -10091,10 +10163,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
10091
10163
|
const text = (res.content ?? []).map((c) => c.text ?? "").join(" ");
|
|
10092
10164
|
return /not_found/i.test(text) ? null : "transient";
|
|
10093
10165
|
}
|
|
10094
|
-
const
|
|
10095
|
-
if (!id) return null;
|
|
10096
|
-
|
|
10097
|
-
return id;
|
|
10166
|
+
const task = res.structuredContent?.task;
|
|
10167
|
+
if (!task?.id) return null;
|
|
10168
|
+
return { id: task.id, workspaceId: typeof task.workspaceId === "string" ? task.workspaceId : null };
|
|
10098
10169
|
}
|
|
10099
10170
|
// MCP-31: привести repo прогона к owner/repo-форме для подсказки резолверу
|
|
10100
10171
|
// задачи. Прод сматчит подсказку с ProjectRepo.repoFullName → воркспейс. repo
|
|
@@ -10133,18 +10204,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
10133
10204
|
this.poisonedRunKeys.add(e.runKey);
|
|
10134
10205
|
return "skip";
|
|
10135
10206
|
}
|
|
10136
|
-
const
|
|
10137
|
-
if (
|
|
10207
|
+
const task = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
|
|
10208
|
+
if (task === "transient") {
|
|
10138
10209
|
throw new Error(`slug\u2192cuid \u0434\u043B\u044F \xAB${e.taskRef}\xBB \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (\u043F\u0440\u043E\u0434 \u043C\u043E\u0440\u0433\u043D\u0443\u043B) \u2014 retry`);
|
|
10139
10210
|
}
|
|
10140
|
-
if (!
|
|
10211
|
+
if (!task) {
|
|
10141
10212
|
console.error(
|
|
10142
10213
|
`[swarm-client] create-\u0444\u043B\u0430\u0448 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: \u043F\u0440\u043E\u0434 \u043D\u0435 \u0437\u043D\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \xAB${e.taskRef}\xBB (runKey=${e.runKey}) \u2014 \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u044B\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u0438\u0437 outbox`
|
|
10143
10214
|
);
|
|
10144
10215
|
this.poisonedRunKeys.add(e.runKey);
|
|
10145
10216
|
return "skip";
|
|
10146
10217
|
}
|
|
10147
|
-
|
|
10218
|
+
const workspaceId = e.workspaceId || task.workspaceId;
|
|
10219
|
+
return { tool: "swarm_run_upsert", args: { taskId: task.id, ...workspaceId ? { workspaceId } : {}, ...fields } };
|
|
10148
10220
|
}
|
|
10149
10221
|
if (this.poisonedRunKeys.has(e.runKey)) {
|
|
10150
10222
|
console.error(`[swarm-client] ${e.t}-\u0444\u043B\u0430\u0448 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: create \u043F\u0440\u043E\u0433\u043E\u043D\u0430 runKey=${e.runKey} \u043D\u0435 \u0440\u0435\u0437\u043E\u043B\u0432\u0438\u043B\u0441\u044F \u0440\u0430\u043D\u0435\u0435 \u2014 \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u044B\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u0438\u0437 outbox`);
|
|
@@ -10196,6 +10268,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
10196
10268
|
} catch {
|
|
10197
10269
|
break;
|
|
10198
10270
|
}
|
|
10271
|
+
if (res?.isError && res.unavailable) break;
|
|
10199
10272
|
if (res?.isError) {
|
|
10200
10273
|
const reason = (res.content ?? []).map((c) => c.text).filter(Boolean).join(" ").trim() || "\u0431\u0435\u0437 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u044F";
|
|
10201
10274
|
const tries = (this.rejected.get(e.seq) ?? 0) + 1;
|
|
@@ -10448,7 +10521,7 @@ function tailFile(path, lines) {
|
|
|
10448
10521
|
}
|
|
10449
10522
|
|
|
10450
10523
|
// src/logRotate.ts
|
|
10451
|
-
import { copyFileSync as copyFileSync4, renameSync as renameSync3, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
|
|
10524
|
+
import { copyFileSync as copyFileSync4, renameSync as renameSync3, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8, openSync as openSync3, writeSync, closeSync as closeSync3 } from "fs";
|
|
10452
10525
|
var realIo = {
|
|
10453
10526
|
size: (path) => {
|
|
10454
10527
|
try {
|
|
@@ -10463,6 +10536,31 @@ var realIo = {
|
|
|
10463
10536
|
unlink: unlinkSync2,
|
|
10464
10537
|
exists: existsSync8
|
|
10465
10538
|
};
|
|
10539
|
+
var realOwnerIo = {
|
|
10540
|
+
statFd: (fd) => {
|
|
10541
|
+
try {
|
|
10542
|
+
const s = fstatSync(fd);
|
|
10543
|
+
return { dev: s.dev, ino: s.ino, isFile: s.isFile() };
|
|
10544
|
+
} catch {
|
|
10545
|
+
return null;
|
|
10546
|
+
}
|
|
10547
|
+
},
|
|
10548
|
+
statPath: (path) => {
|
|
10549
|
+
try {
|
|
10550
|
+
const s = statSync7(path);
|
|
10551
|
+
return { dev: s.dev, ino: s.ino };
|
|
10552
|
+
} catch {
|
|
10553
|
+
return null;
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
};
|
|
10557
|
+
function stdoutGoesTo(path, io = realOwnerIo) {
|
|
10558
|
+
if (teedTo === path) return true;
|
|
10559
|
+
const out = io.statFd(1);
|
|
10560
|
+
if (!out || !out.isFile) return false;
|
|
10561
|
+
const file = io.statPath(path);
|
|
10562
|
+
return file != null && file.dev === out.dev && file.ino === out.ino;
|
|
10563
|
+
}
|
|
10466
10564
|
var STRAY_GENERATIONS = 8;
|
|
10467
10565
|
function rotateDaemonLog(path, opts, io = realIo) {
|
|
10468
10566
|
const size = io.size(path);
|
|
@@ -10482,6 +10580,33 @@ function rotateDaemonLog(path, opts, io = realIo) {
|
|
|
10482
10580
|
io.truncate(path);
|
|
10483
10581
|
return "rotated";
|
|
10484
10582
|
}
|
|
10583
|
+
var teedTo = null;
|
|
10584
|
+
function teeStdioToFile(path) {
|
|
10585
|
+
if (teedTo === path || stdoutGoesTo(path)) return null;
|
|
10586
|
+
const fd = openSync3(path, "a");
|
|
10587
|
+
const restore = [];
|
|
10588
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
10589
|
+
const original = stream.write;
|
|
10590
|
+
restore.push(() => {
|
|
10591
|
+
stream.write = original;
|
|
10592
|
+
});
|
|
10593
|
+
const orig = original.bind(stream);
|
|
10594
|
+
stream.write = ((chunk, ...rest) => {
|
|
10595
|
+
try {
|
|
10596
|
+
if (typeof chunk === "string") writeSync(fd, chunk);
|
|
10597
|
+
else if (chunk instanceof Uint8Array) writeSync(fd, chunk);
|
|
10598
|
+
} catch {
|
|
10599
|
+
}
|
|
10600
|
+
return orig(chunk, ...rest);
|
|
10601
|
+
});
|
|
10602
|
+
}
|
|
10603
|
+
teedTo = path;
|
|
10604
|
+
return () => {
|
|
10605
|
+
for (const undo of restore) undo();
|
|
10606
|
+
teedTo = null;
|
|
10607
|
+
closeSync3(fd);
|
|
10608
|
+
};
|
|
10609
|
+
}
|
|
10485
10610
|
|
|
10486
10611
|
// src/cli.ts
|
|
10487
10612
|
init_procCleanup();
|
|
@@ -12807,7 +12932,7 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
12807
12932
|
|
|
12808
12933
|
// src/launcher.ts
|
|
12809
12934
|
import { spawn as spawn2 } from "child_process";
|
|
12810
|
-
import { closeSync as
|
|
12935
|
+
import { closeSync as closeSync4, existsSync as existsSync10, mkdirSync as mkdirSync10, openSync as openSync4, readdirSync as readdirSync9 } from "fs";
|
|
12811
12936
|
import { createRequire } from "module";
|
|
12812
12937
|
import { homedir as homedir5 } from "os";
|
|
12813
12938
|
import { dirname as dirname4, join as join15 } from "path";
|
|
@@ -13016,14 +13141,14 @@ function openNextRunLog(runLogsDir, runLogKeys) {
|
|
|
13016
13141
|
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
13017
13142
|
let out;
|
|
13018
13143
|
try {
|
|
13019
|
-
out =
|
|
13144
|
+
out = openSync4(path, "wx");
|
|
13020
13145
|
} catch (e) {
|
|
13021
13146
|
if (e.code !== "EEXIST") throw e;
|
|
13022
13147
|
continue;
|
|
13023
13148
|
}
|
|
13024
13149
|
let err = null;
|
|
13025
13150
|
try {
|
|
13026
|
-
err =
|
|
13151
|
+
err = openSync4(`${path.slice(0, -".json".length)}.err.log`, "w");
|
|
13027
13152
|
} catch {
|
|
13028
13153
|
err = null;
|
|
13029
13154
|
}
|
|
@@ -13153,8 +13278,8 @@ function makeLauncher(cfg) {
|
|
|
13153
13278
|
// они про его собственное состояние, и перебивать их ройным нечем.
|
|
13154
13279
|
env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
|
|
13155
13280
|
});
|
|
13156
|
-
if (outFd !== void 0)
|
|
13157
|
-
if (errFd !== void 0)
|
|
13281
|
+
if (outFd !== void 0) closeSync4(outFd);
|
|
13282
|
+
if (errFd !== void 0) closeSync4(errFd);
|
|
13158
13283
|
child.unref();
|
|
13159
13284
|
if (child.pid == null) throw new Error("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430 (\u043D\u0435\u0442 pid)");
|
|
13160
13285
|
return child.pid;
|
|
@@ -13171,11 +13296,17 @@ init_taskless();
|
|
|
13171
13296
|
// src/mcpResilient.ts
|
|
13172
13297
|
init_linkFailure();
|
|
13173
13298
|
init_taskless();
|
|
13299
|
+
init_prodHttp();
|
|
13174
13300
|
var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
|
|
13175
13301
|
var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
|
|
13176
13302
|
var DEFAULT_RETRY_DELAYS_MS = [200, 800];
|
|
13177
|
-
|
|
13178
|
-
|
|
13303
|
+
var PROD_PASS_BUDGET_MS = 2e4;
|
|
13304
|
+
function errorResult(reason, unavailable) {
|
|
13305
|
+
return {
|
|
13306
|
+
isError: true,
|
|
13307
|
+
...unavailable ? { unavailable: true } : {},
|
|
13308
|
+
content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }]
|
|
13309
|
+
};
|
|
13179
13310
|
}
|
|
13180
13311
|
function resilientSwarmMcp(opts = {}) {
|
|
13181
13312
|
const connect = opts.connect ?? (() => connectSwarmMcp());
|
|
@@ -13183,6 +13314,13 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13183
13314
|
const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
|
|
13184
13315
|
const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
13185
13316
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13317
|
+
const clock = opts.now ?? Date.now;
|
|
13318
|
+
const passBudgetMs = opts.passBudgetMs ?? PROD_PASS_BUDGET_MS;
|
|
13319
|
+
let passSpentMs = null;
|
|
13320
|
+
let budgetNoted = false;
|
|
13321
|
+
const spend = (since) => {
|
|
13322
|
+
if (passSpentMs !== null) passSpentMs += Math.max(0, clock() - since);
|
|
13323
|
+
};
|
|
13186
13324
|
let caller = null;
|
|
13187
13325
|
let lastTryAt = 0;
|
|
13188
13326
|
let failures = 0;
|
|
@@ -13205,6 +13343,7 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13205
13343
|
if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
13206
13344
|
if (!force) lastTryAt = now;
|
|
13207
13345
|
let fresh = null;
|
|
13346
|
+
const connectStart = clock();
|
|
13208
13347
|
try {
|
|
13209
13348
|
fresh = await connect();
|
|
13210
13349
|
if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
|
|
@@ -13221,17 +13360,30 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13221
13360
|
downReported = false;
|
|
13222
13361
|
return caller;
|
|
13223
13362
|
}
|
|
13363
|
+
spend(connectStart);
|
|
13224
13364
|
if (!force) failures += 1;
|
|
13225
13365
|
if (downSince === null) downSince = now;
|
|
13226
13366
|
return null;
|
|
13227
13367
|
}
|
|
13228
13368
|
const call = async (name, args) => {
|
|
13229
13369
|
for (let attempt = 0; ; attempt++) {
|
|
13370
|
+
if (passSpentMs !== null && passSpentMs >= passBudgetMs) {
|
|
13371
|
+
if (!budgetNoted) {
|
|
13372
|
+
budgetNoted = true;
|
|
13373
|
+
opts.onBudgetSpent?.({ spentMs: passSpentMs, budgetMs: passBudgetMs });
|
|
13374
|
+
}
|
|
13375
|
+
maybeReportDown();
|
|
13376
|
+
return errorResult(
|
|
13377
|
+
`\u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0445\u043E\u0434\u0430 \u043D\u0430 \u0441\u0435\u0442\u044C (${Math.round(passBudgetMs / 1e3)}\u0441) \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0434\u043E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0445\u043E\u0434\u0430 \u0431\u0435\u0437 \u0441\u0435\u0442\u0438${lastCallReason ?? lastConnectReason ? ` (\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043F\u0440\u0438\u0447\u0438\u043D\u0430: ${lastCallReason ?? lastConnectReason})` : ""}`,
|
|
13378
|
+
true
|
|
13379
|
+
);
|
|
13380
|
+
}
|
|
13230
13381
|
const active = await ensureCaller(attempt > 0);
|
|
13231
13382
|
if (!active) {
|
|
13232
13383
|
maybeReportDown();
|
|
13233
|
-
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
|
|
13384
|
+
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON, true);
|
|
13234
13385
|
}
|
|
13386
|
+
const callStart = clock();
|
|
13235
13387
|
try {
|
|
13236
13388
|
const res = await active(name, args);
|
|
13237
13389
|
lastOkAt = Date.now();
|
|
@@ -13239,13 +13391,14 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13239
13391
|
return res;
|
|
13240
13392
|
} catch (e) {
|
|
13241
13393
|
caller = null;
|
|
13394
|
+
spend(callStart);
|
|
13242
13395
|
if (downSince === null) downSince = Date.now();
|
|
13243
13396
|
const { kind, detail: reason } = explainLinkFailure(e);
|
|
13244
13397
|
lastCallReason = reason;
|
|
13245
13398
|
const delay = retryDelays[attempt];
|
|
13246
|
-
if (delay === void 0 || kind !== "offline") {
|
|
13399
|
+
if (delay === void 0 || kind !== "offline" || isProdTimeout(e)) {
|
|
13247
13400
|
maybeReportDown();
|
|
13248
|
-
return errorResult(reason);
|
|
13401
|
+
return errorResult(reason, kind === "offline" || kind === "unknown" || kind === "unauthorized" || isProdTimeout(e));
|
|
13249
13402
|
}
|
|
13250
13403
|
opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
|
|
13251
13404
|
await sleep3(delay);
|
|
@@ -13253,6 +13406,10 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13253
13406
|
}
|
|
13254
13407
|
};
|
|
13255
13408
|
return Object.assign(call, {
|
|
13409
|
+
beginPass: () => {
|
|
13410
|
+
passSpentMs = 0;
|
|
13411
|
+
budgetNoted = false;
|
|
13412
|
+
},
|
|
13256
13413
|
// Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
|
|
13257
13414
|
// подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
|
|
13258
13415
|
link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
|
|
@@ -13811,13 +13968,13 @@ async function stopDaemonForLogin() {
|
|
|
13811
13968
|
async function runLoginFlow(db2, opts) {
|
|
13812
13969
|
const web = startWeb({ db: db2 });
|
|
13813
13970
|
try {
|
|
13814
|
-
let
|
|
13815
|
-
if (
|
|
13971
|
+
let agent2 = loadAgentToken();
|
|
13972
|
+
if (agent2 && opts.force) {
|
|
13816
13973
|
console.log("--force: \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E, \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D.");
|
|
13817
|
-
|
|
13974
|
+
agent2 = null;
|
|
13818
13975
|
}
|
|
13819
|
-
if (
|
|
13820
|
-
const probe = await probeAgentToken({ api: TASKLESS_API, token:
|
|
13976
|
+
if (agent2) {
|
|
13977
|
+
const probe = await probeAgentToken({ api: TASKLESS_API, token: agent2.token });
|
|
13821
13978
|
if (probe === "ok") {
|
|
13822
13979
|
console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u044E \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430.");
|
|
13823
13980
|
} else {
|
|
@@ -13829,10 +13986,10 @@ async function runLoginFlow(db2, opts) {
|
|
|
13829
13986
|
"\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434/\u043E\u0442\u043A\u0430\u0437) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E: \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435\u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0437\u0430 \xAB\u0432\u0441\u0451 \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435\xBB \u043D\u0435\u043B\u044C\u0437\u044F."
|
|
13830
13987
|
);
|
|
13831
13988
|
}
|
|
13832
|
-
|
|
13989
|
+
agent2 = null;
|
|
13833
13990
|
}
|
|
13834
13991
|
}
|
|
13835
|
-
if (!
|
|
13992
|
+
if (!agent2) {
|
|
13836
13993
|
let store = loadStore();
|
|
13837
13994
|
if (!store) {
|
|
13838
13995
|
const loginUrl = `http://127.0.0.1:${web.port}/login`;
|
|
@@ -14021,14 +14178,14 @@ async function runDaemonLogs(argv) {
|
|
|
14021
14178
|
}
|
|
14022
14179
|
if (size < pos) pos = 0;
|
|
14023
14180
|
if (size === pos) continue;
|
|
14024
|
-
const fd =
|
|
14181
|
+
const fd = openSync5(DAEMON_LOG_PATH, "r");
|
|
14025
14182
|
try {
|
|
14026
14183
|
const buf = Buffer.alloc(size - pos);
|
|
14027
14184
|
const read = readSync2(fd, buf, 0, buf.length, pos);
|
|
14028
14185
|
process.stdout.write(buf.subarray(0, read).toString("utf8"));
|
|
14029
14186
|
pos += read;
|
|
14030
14187
|
} finally {
|
|
14031
|
-
|
|
14188
|
+
closeSync5(fd);
|
|
14032
14189
|
}
|
|
14033
14190
|
}
|
|
14034
14191
|
}
|
|
@@ -14306,7 +14463,7 @@ async function makeCliStore(repo) {
|
|
|
14306
14463
|
onDown: (ms) => console.warn(`Swarm MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u043C \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C.`),
|
|
14307
14464
|
onUp: () => console.warn("Swarm MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
|
|
14308
14465
|
});
|
|
14309
|
-
const repoRegistry = await buildRepoRegistry(SWARM_REPO_PATHS);
|
|
14466
|
+
const repoRegistry = await buildRepoRegistry([...new Set([repo, ...SWARM_REPO_PATHS].filter(Boolean))]);
|
|
14310
14467
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry });
|
|
14311
14468
|
await client.hydrate();
|
|
14312
14469
|
client.reconcileRuntime({ worktreesDir: effectiveWorktreesDir(), listProcesses: listDroneProcesses });
|
|
@@ -14563,6 +14720,12 @@ if (cmd === "start") {
|
|
|
14563
14720
|
if (!foreground) {
|
|
14564
14721
|
process.exit(await runStartDetached(startArgs));
|
|
14565
14722
|
}
|
|
14723
|
+
try {
|
|
14724
|
+
rotateDaemonLog(DAEMON_LOG_PATH, { maxBytes: DAEMON_LOG_MAX_BYTES, keep: DAEMON_LOG_KEEP });
|
|
14725
|
+
if (teeStdioToFile(DAEMON_LOG_PATH)) console.log(`\u043B\u043E\u0433 \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u0438 \u0432 ${DAEMON_LOG_PATH}`);
|
|
14726
|
+
} catch (e) {
|
|
14727
|
+
console.warn(`\u043B\u043E\u0433 \u0432 \u0444\u0430\u0439\u043B \u043D\u0435 \u043F\u0438\u0448\u0435\u0442\u0441\u044F (${e instanceof Error ? e.message : e}) \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B`);
|
|
14728
|
+
}
|
|
14566
14729
|
try {
|
|
14567
14730
|
await runDaemon({ repo, repoFlag });
|
|
14568
14731
|
} catch (e) {
|