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