@taskless-app/swarm 0.6.66 → 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 +211 -43
- package/dist/overmind.js +550 -63
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -254,7 +254,8 @@ var init_reviewPhase = __esm({
|
|
|
254
254
|
verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
255
255
|
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
256
256
|
ready_awaiting_ci: "\u0413\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443 \u2014 \u0436\u0434\u0451\u0442 CI",
|
|
257
|
-
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
257
|
+
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
258
|
+
cycles_exhausted: "\u041B\u0438\u043C\u0438\u0442 \u043A\u0440\u0443\u0433\u043E\u0432 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
258
259
|
};
|
|
259
260
|
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
260
261
|
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
@@ -1789,7 +1790,7 @@ var init_effort = __esm({
|
|
|
1789
1790
|
});
|
|
1790
1791
|
|
|
1791
1792
|
// ../swarm-core/src/queuePlan.ts
|
|
1792
|
-
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS;
|
|
1793
|
+
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS, SERVICE_DONE_KEEP_MS, PR_HEAD_LAG_MS, SERVICE_DONE_MARKER_MS;
|
|
1793
1794
|
var init_queuePlan = __esm({
|
|
1794
1795
|
"../swarm-core/src/queuePlan.ts"() {
|
|
1795
1796
|
"use strict";
|
|
@@ -1802,6 +1803,9 @@ var init_queuePlan = __esm({
|
|
|
1802
1803
|
MIN_MS = 6e4;
|
|
1803
1804
|
QUEUE_NOMINAL_RUN_MS = 25 * MIN_MS;
|
|
1804
1805
|
QUEUE_MIN_TAIL_MS = 3 * MIN_MS;
|
|
1806
|
+
SERVICE_DONE_KEEP_MS = 2 * 60 * MIN_MS;
|
|
1807
|
+
PR_HEAD_LAG_MS = 5 * MIN_MS;
|
|
1808
|
+
SERVICE_DONE_MARKER_MS = 8 * MIN_MS;
|
|
1805
1809
|
}
|
|
1806
1810
|
});
|
|
1807
1811
|
|
|
@@ -3526,7 +3530,7 @@ var init_linkFailure = __esm({
|
|
|
3526
3530
|
"use strict";
|
|
3527
3531
|
init_agentToken();
|
|
3528
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";
|
|
3529
|
-
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;
|
|
3530
3534
|
MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
|
|
3531
3535
|
}
|
|
3532
3536
|
});
|
|
@@ -3876,7 +3880,7 @@ var init_package = __esm({
|
|
|
3876
3880
|
"package.json"() {
|
|
3877
3881
|
package_default = {
|
|
3878
3882
|
name: "@taskless-app/swarm",
|
|
3879
|
-
version: "0.6.
|
|
3883
|
+
version: "0.6.68",
|
|
3880
3884
|
type: "module",
|
|
3881
3885
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
3882
3886
|
keywords: [
|
|
@@ -3925,9 +3929,10 @@ var init_package = __esm({
|
|
|
3925
3929
|
"@hono/node-server": "^2.0.12",
|
|
3926
3930
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
3927
3931
|
execa: "^10.0.0",
|
|
3932
|
+
undici: "^7.25.0",
|
|
3928
3933
|
zod: "3.25.76"
|
|
3929
3934
|
},
|
|
3930
|
-
gitHead: "
|
|
3935
|
+
gitHead: "4bfd78926aa37ef7de8ea06a6763d7e84a2fded6"
|
|
3931
3936
|
};
|
|
3932
3937
|
}
|
|
3933
3938
|
});
|
|
@@ -4660,6 +4665,47 @@ var init_shotUpload = __esm({
|
|
|
4660
4665
|
}
|
|
4661
4666
|
});
|
|
4662
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
|
+
|
|
4663
4709
|
// src/workspace.ts
|
|
4664
4710
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
|
|
4665
4711
|
import { join as join19 } from "path";
|
|
@@ -4711,8 +4757,8 @@ function parseJsonContent(res) {
|
|
|
4711
4757
|
}
|
|
4712
4758
|
async function resolveApiToken(api, explicit) {
|
|
4713
4759
|
if (explicit) return explicit;
|
|
4714
|
-
const
|
|
4715
|
-
if (
|
|
4760
|
+
const agent2 = loadAgentToken();
|
|
4761
|
+
if (agent2?.token) return agent2.token;
|
|
4716
4762
|
const store = loadStore();
|
|
4717
4763
|
if (store) {
|
|
4718
4764
|
const r = await ensureAccess(store, { api });
|
|
@@ -4741,7 +4787,9 @@ async function connectSwarmMcp(cfg) {
|
|
|
4741
4787
|
}
|
|
4742
4788
|
const url = new URL(mcpEndpoint(api));
|
|
4743
4789
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
4744
|
-
requestInit: { headers: { Authorization: `Bearer ${token}` } }
|
|
4790
|
+
requestInit: { headers: { Authorization: `Bearer ${token}` } },
|
|
4791
|
+
fetch: prodFetch
|
|
4792
|
+
// TASWM-611
|
|
4745
4793
|
});
|
|
4746
4794
|
const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
|
|
4747
4795
|
try {
|
|
@@ -4760,12 +4808,31 @@ async function connectSwarmMcp(cfg) {
|
|
|
4760
4808
|
}
|
|
4761
4809
|
swarmConnectFailure = null;
|
|
4762
4810
|
noteTokenAccepted();
|
|
4763
|
-
|
|
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`;
|
|
4764
4827
|
}
|
|
4765
4828
|
function lastSwarmConnectFailure() {
|
|
4766
4829
|
return swarmConnectFailure;
|
|
4767
4830
|
}
|
|
4768
4831
|
function swarmToolArgs(name, args, workspace) {
|
|
4832
|
+
if ("workspaceId" in args && args.workspaceId == null) {
|
|
4833
|
+
const { workspaceId: _drop, ...rest } = args;
|
|
4834
|
+
return rest;
|
|
4835
|
+
}
|
|
4769
4836
|
if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
|
|
4770
4837
|
return { workspaceId: workspace, ...args };
|
|
4771
4838
|
}
|
|
@@ -4780,6 +4847,7 @@ var init_taskless = __esm({
|
|
|
4780
4847
|
init_oauth();
|
|
4781
4848
|
init_agentToken();
|
|
4782
4849
|
init_linkFailure();
|
|
4850
|
+
init_prodHttp();
|
|
4783
4851
|
init_toolFailure();
|
|
4784
4852
|
init_authState();
|
|
4785
4853
|
init_workspace();
|
|
@@ -7693,7 +7761,7 @@ var init_updateCli = __esm({
|
|
|
7693
7761
|
// src/cli.ts
|
|
7694
7762
|
init_cliIo();
|
|
7695
7763
|
import { basename as basename6, dirname as dirname5, join as join27 } from "path";
|
|
7696
|
-
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";
|
|
7697
7765
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
7698
7766
|
|
|
7699
7767
|
// src/swarmClient.ts
|
|
@@ -10057,7 +10125,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
10057
10125
|
// 14.07/18.07, TASWM-201 — общий карантин; здесь — конкретный источник). Резолвим
|
|
10058
10126
|
// слаг→cuid через get_task (кэш — taskIdCache) ПЕРЕД тем как слать create.
|
|
10059
10127
|
taskIdCache = /* @__PURE__ */ new Map();
|
|
10060
|
-
// task_slug → cuid прода
|
|
10128
|
+
// task_slug → cuid прода и воркспейс задачи
|
|
10061
10129
|
// Create для этого runKey резолвился окончательно неудачно (задача не найдена/
|
|
10062
10130
|
// недоступна) — событие/вопросы того же прогона тоже никогда не получат prod-id,
|
|
10063
10131
|
// без карантина они бы бросали «нет prod-id» на каждом флаше и СНОВА вставали
|
|
@@ -10072,12 +10140,20 @@ var SwarmClient = class _SwarmClient {
|
|
|
10072
10140
|
const cacheKey = `${taskRef}\0${repoFullName ?? ""}\0${workspaceId ?? ""}`;
|
|
10073
10141
|
const cached = this.taskIdCache.get(cacheKey);
|
|
10074
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) {
|
|
10075
10150
|
let res;
|
|
10076
10151
|
try {
|
|
10077
10152
|
res = await this.call("get_task", {
|
|
10078
10153
|
id: taskRef,
|
|
10079
10154
|
...repoFullName ? { repoFullName } : {},
|
|
10080
|
-
|
|
10155
|
+
// null — «без пина демона» (см. swarmToolArgs), undefined — пин по умолчанию.
|
|
10156
|
+
...workspaceId ? { workspaceId } : workspaceId === null ? { workspaceId: null } : {}
|
|
10081
10157
|
});
|
|
10082
10158
|
} catch {
|
|
10083
10159
|
return "transient";
|
|
@@ -10087,10 +10163,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
10087
10163
|
const text = (res.content ?? []).map((c) => c.text ?? "").join(" ");
|
|
10088
10164
|
return /not_found/i.test(text) ? null : "transient";
|
|
10089
10165
|
}
|
|
10090
|
-
const
|
|
10091
|
-
if (!id) return null;
|
|
10092
|
-
|
|
10093
|
-
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 };
|
|
10094
10169
|
}
|
|
10095
10170
|
// MCP-31: привести repo прогона к owner/repo-форме для подсказки резолверу
|
|
10096
10171
|
// задачи. Прод сматчит подсказку с ProjectRepo.repoFullName → воркспейс. repo
|
|
@@ -10129,18 +10204,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
10129
10204
|
this.poisonedRunKeys.add(e.runKey);
|
|
10130
10205
|
return "skip";
|
|
10131
10206
|
}
|
|
10132
|
-
const
|
|
10133
|
-
if (
|
|
10207
|
+
const task = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
|
|
10208
|
+
if (task === "transient") {
|
|
10134
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`);
|
|
10135
10210
|
}
|
|
10136
|
-
if (!
|
|
10211
|
+
if (!task) {
|
|
10137
10212
|
console.error(
|
|
10138
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`
|
|
10139
10214
|
);
|
|
10140
10215
|
this.poisonedRunKeys.add(e.runKey);
|
|
10141
10216
|
return "skip";
|
|
10142
10217
|
}
|
|
10143
|
-
|
|
10218
|
+
const workspaceId = e.workspaceId || task.workspaceId;
|
|
10219
|
+
return { tool: "swarm_run_upsert", args: { taskId: task.id, ...workspaceId ? { workspaceId } : {}, ...fields } };
|
|
10144
10220
|
}
|
|
10145
10221
|
if (this.poisonedRunKeys.has(e.runKey)) {
|
|
10146
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`);
|
|
@@ -10192,6 +10268,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
10192
10268
|
} catch {
|
|
10193
10269
|
break;
|
|
10194
10270
|
}
|
|
10271
|
+
if (res?.isError && res.unavailable) break;
|
|
10195
10272
|
if (res?.isError) {
|
|
10196
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";
|
|
10197
10274
|
const tries = (this.rejected.get(e.seq) ?? 0) + 1;
|
|
@@ -10444,7 +10521,7 @@ function tailFile(path, lines) {
|
|
|
10444
10521
|
}
|
|
10445
10522
|
|
|
10446
10523
|
// src/logRotate.ts
|
|
10447
|
-
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";
|
|
10448
10525
|
var realIo = {
|
|
10449
10526
|
size: (path) => {
|
|
10450
10527
|
try {
|
|
@@ -10459,6 +10536,31 @@ var realIo = {
|
|
|
10459
10536
|
unlink: unlinkSync2,
|
|
10460
10537
|
exists: existsSync8
|
|
10461
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
|
+
}
|
|
10462
10564
|
var STRAY_GENERATIONS = 8;
|
|
10463
10565
|
function rotateDaemonLog(path, opts, io = realIo) {
|
|
10464
10566
|
const size = io.size(path);
|
|
@@ -10478,6 +10580,33 @@ function rotateDaemonLog(path, opts, io = realIo) {
|
|
|
10478
10580
|
io.truncate(path);
|
|
10479
10581
|
return "rotated";
|
|
10480
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
|
+
}
|
|
10481
10610
|
|
|
10482
10611
|
// src/cli.ts
|
|
10483
10612
|
init_procCleanup();
|
|
@@ -12803,7 +12932,7 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
12803
12932
|
|
|
12804
12933
|
// src/launcher.ts
|
|
12805
12934
|
import { spawn as spawn2 } from "child_process";
|
|
12806
|
-
import { closeSync as
|
|
12935
|
+
import { closeSync as closeSync4, existsSync as existsSync10, mkdirSync as mkdirSync10, openSync as openSync4, readdirSync as readdirSync9 } from "fs";
|
|
12807
12936
|
import { createRequire } from "module";
|
|
12808
12937
|
import { homedir as homedir5 } from "os";
|
|
12809
12938
|
import { dirname as dirname4, join as join15 } from "path";
|
|
@@ -13012,14 +13141,14 @@ function openNextRunLog(runLogsDir, runLogKeys) {
|
|
|
13012
13141
|
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
13013
13142
|
let out;
|
|
13014
13143
|
try {
|
|
13015
|
-
out =
|
|
13144
|
+
out = openSync4(path, "wx");
|
|
13016
13145
|
} catch (e) {
|
|
13017
13146
|
if (e.code !== "EEXIST") throw e;
|
|
13018
13147
|
continue;
|
|
13019
13148
|
}
|
|
13020
13149
|
let err = null;
|
|
13021
13150
|
try {
|
|
13022
|
-
err =
|
|
13151
|
+
err = openSync4(`${path.slice(0, -".json".length)}.err.log`, "w");
|
|
13023
13152
|
} catch {
|
|
13024
13153
|
err = null;
|
|
13025
13154
|
}
|
|
@@ -13149,8 +13278,8 @@ function makeLauncher(cfg) {
|
|
|
13149
13278
|
// они про его собственное состояние, и перебивать их ройным нечем.
|
|
13150
13279
|
env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
|
|
13151
13280
|
});
|
|
13152
|
-
if (outFd !== void 0)
|
|
13153
|
-
if (errFd !== void 0)
|
|
13281
|
+
if (outFd !== void 0) closeSync4(outFd);
|
|
13282
|
+
if (errFd !== void 0) closeSync4(errFd);
|
|
13154
13283
|
child.unref();
|
|
13155
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)");
|
|
13156
13285
|
return child.pid;
|
|
@@ -13167,11 +13296,17 @@ init_taskless();
|
|
|
13167
13296
|
// src/mcpResilient.ts
|
|
13168
13297
|
init_linkFailure();
|
|
13169
13298
|
init_taskless();
|
|
13299
|
+
init_prodHttp();
|
|
13170
13300
|
var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
|
|
13171
13301
|
var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
|
|
13172
13302
|
var DEFAULT_RETRY_DELAYS_MS = [200, 800];
|
|
13173
|
-
|
|
13174
|
-
|
|
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
|
+
};
|
|
13175
13310
|
}
|
|
13176
13311
|
function resilientSwarmMcp(opts = {}) {
|
|
13177
13312
|
const connect = opts.connect ?? (() => connectSwarmMcp());
|
|
@@ -13179,6 +13314,13 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13179
13314
|
const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
|
|
13180
13315
|
const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
13181
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
|
+
};
|
|
13182
13324
|
let caller = null;
|
|
13183
13325
|
let lastTryAt = 0;
|
|
13184
13326
|
let failures = 0;
|
|
@@ -13201,6 +13343,7 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13201
13343
|
if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
13202
13344
|
if (!force) lastTryAt = now;
|
|
13203
13345
|
let fresh = null;
|
|
13346
|
+
const connectStart = clock();
|
|
13204
13347
|
try {
|
|
13205
13348
|
fresh = await connect();
|
|
13206
13349
|
if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
|
|
@@ -13217,17 +13360,30 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13217
13360
|
downReported = false;
|
|
13218
13361
|
return caller;
|
|
13219
13362
|
}
|
|
13363
|
+
spend(connectStart);
|
|
13220
13364
|
if (!force) failures += 1;
|
|
13221
13365
|
if (downSince === null) downSince = now;
|
|
13222
13366
|
return null;
|
|
13223
13367
|
}
|
|
13224
13368
|
const call = async (name, args) => {
|
|
13225
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
|
+
}
|
|
13226
13381
|
const active = await ensureCaller(attempt > 0);
|
|
13227
13382
|
if (!active) {
|
|
13228
13383
|
maybeReportDown();
|
|
13229
|
-
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
|
|
13384
|
+
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON, true);
|
|
13230
13385
|
}
|
|
13386
|
+
const callStart = clock();
|
|
13231
13387
|
try {
|
|
13232
13388
|
const res = await active(name, args);
|
|
13233
13389
|
lastOkAt = Date.now();
|
|
@@ -13235,13 +13391,14 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13235
13391
|
return res;
|
|
13236
13392
|
} catch (e) {
|
|
13237
13393
|
caller = null;
|
|
13394
|
+
spend(callStart);
|
|
13238
13395
|
if (downSince === null) downSince = Date.now();
|
|
13239
13396
|
const { kind, detail: reason } = explainLinkFailure(e);
|
|
13240
13397
|
lastCallReason = reason;
|
|
13241
13398
|
const delay = retryDelays[attempt];
|
|
13242
|
-
if (delay === void 0 || kind !== "offline") {
|
|
13399
|
+
if (delay === void 0 || kind !== "offline" || isProdTimeout(e)) {
|
|
13243
13400
|
maybeReportDown();
|
|
13244
|
-
return errorResult(reason);
|
|
13401
|
+
return errorResult(reason, kind === "offline" || kind === "unknown" || kind === "unauthorized" || isProdTimeout(e));
|
|
13245
13402
|
}
|
|
13246
13403
|
opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
|
|
13247
13404
|
await sleep3(delay);
|
|
@@ -13249,6 +13406,10 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
13249
13406
|
}
|
|
13250
13407
|
};
|
|
13251
13408
|
return Object.assign(call, {
|
|
13409
|
+
beginPass: () => {
|
|
13410
|
+
passSpentMs = 0;
|
|
13411
|
+
budgetNoted = false;
|
|
13412
|
+
},
|
|
13252
13413
|
// Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
|
|
13253
13414
|
// подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
|
|
13254
13415
|
link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
|
|
@@ -13807,13 +13968,13 @@ async function stopDaemonForLogin() {
|
|
|
13807
13968
|
async function runLoginFlow(db2, opts) {
|
|
13808
13969
|
const web = startWeb({ db: db2 });
|
|
13809
13970
|
try {
|
|
13810
|
-
let
|
|
13811
|
-
if (
|
|
13971
|
+
let agent2 = loadAgentToken();
|
|
13972
|
+
if (agent2 && opts.force) {
|
|
13812
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.");
|
|
13813
|
-
|
|
13974
|
+
agent2 = null;
|
|
13814
13975
|
}
|
|
13815
|
-
if (
|
|
13816
|
-
const probe = await probeAgentToken({ api: TASKLESS_API, token:
|
|
13976
|
+
if (agent2) {
|
|
13977
|
+
const probe = await probeAgentToken({ api: TASKLESS_API, token: agent2.token });
|
|
13817
13978
|
if (probe === "ok") {
|
|
13818
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.");
|
|
13819
13980
|
} else {
|
|
@@ -13825,10 +13986,10 @@ async function runLoginFlow(db2, opts) {
|
|
|
13825
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."
|
|
13826
13987
|
);
|
|
13827
13988
|
}
|
|
13828
|
-
|
|
13989
|
+
agent2 = null;
|
|
13829
13990
|
}
|
|
13830
13991
|
}
|
|
13831
|
-
if (!
|
|
13992
|
+
if (!agent2) {
|
|
13832
13993
|
let store = loadStore();
|
|
13833
13994
|
if (!store) {
|
|
13834
13995
|
const loginUrl = `http://127.0.0.1:${web.port}/login`;
|
|
@@ -14017,14 +14178,14 @@ async function runDaemonLogs(argv) {
|
|
|
14017
14178
|
}
|
|
14018
14179
|
if (size < pos) pos = 0;
|
|
14019
14180
|
if (size === pos) continue;
|
|
14020
|
-
const fd =
|
|
14181
|
+
const fd = openSync5(DAEMON_LOG_PATH, "r");
|
|
14021
14182
|
try {
|
|
14022
14183
|
const buf = Buffer.alloc(size - pos);
|
|
14023
14184
|
const read = readSync2(fd, buf, 0, buf.length, pos);
|
|
14024
14185
|
process.stdout.write(buf.subarray(0, read).toString("utf8"));
|
|
14025
14186
|
pos += read;
|
|
14026
14187
|
} finally {
|
|
14027
|
-
|
|
14188
|
+
closeSync5(fd);
|
|
14028
14189
|
}
|
|
14029
14190
|
}
|
|
14030
14191
|
}
|
|
@@ -14302,7 +14463,7 @@ async function makeCliStore(repo) {
|
|
|
14302
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.`),
|
|
14303
14464
|
onUp: () => console.warn("Swarm MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
|
|
14304
14465
|
});
|
|
14305
|
-
const repoRegistry = await buildRepoRegistry(SWARM_REPO_PATHS);
|
|
14466
|
+
const repoRegistry = await buildRepoRegistry([...new Set([repo, ...SWARM_REPO_PATHS].filter(Boolean))]);
|
|
14306
14467
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry });
|
|
14307
14468
|
await client.hydrate();
|
|
14308
14469
|
client.reconcileRuntime({ worktreesDir: effectiveWorktreesDir(), listProcesses: listDroneProcesses });
|
|
@@ -14559,6 +14720,12 @@ if (cmd === "start") {
|
|
|
14559
14720
|
if (!foreground) {
|
|
14560
14721
|
process.exit(await runStartDetached(startArgs));
|
|
14561
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
|
+
}
|
|
14562
14729
|
try {
|
|
14563
14730
|
await runDaemon({ repo, repoFlag });
|
|
14564
14731
|
} catch (e) {
|
|
@@ -14854,10 +15021,11 @@ switch (cmd) {
|
|
|
14854
15021
|
// TASWM-592 (ревью): сессию ищет харнесс ПРОГОНА, а не глобальный исполнитель —
|
|
14855
15022
|
// иначе побудка codex-прогона при мёртвом демоне теряла продолжаемую сессию.
|
|
14856
15023
|
hasTranscript: (run2) => !!run2.worktree_path && executorForRun(run2, null).session.exists(run2.worktree_path),
|
|
14857
|
-
respawn: (r, mode) => respawnRun(
|
|
15024
|
+
respawn: (r, mode, nudge) => respawnRun(
|
|
14858
15025
|
{ db, launch, repoFallback: SWARM_REPO, worktreesDir: effectiveWorktreesDir(), getTask: () => Promise.resolve(null) },
|
|
14859
15026
|
r,
|
|
14860
|
-
mode
|
|
15027
|
+
mode,
|
|
15028
|
+
nudge
|
|
14861
15029
|
)
|
|
14862
15030
|
},
|
|
14863
15031
|
runId
|