@taskless-app/swarm 0.6.4 → 0.6.5

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 CHANGED
@@ -1884,7 +1884,7 @@ var init_package = __esm({
1884
1884
  "package.json"() {
1885
1885
  package_default = {
1886
1886
  name: "@taskless-app/swarm",
1887
- version: "0.6.4",
1887
+ version: "0.6.5",
1888
1888
  type: "module",
1889
1889
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
1890
1890
  keywords: [
@@ -1934,7 +1934,7 @@ var init_package = __esm({
1934
1934
  execa: "^10.0.0",
1935
1935
  zod: "3.25.76"
1936
1936
  },
1937
- gitHead: "12ecebcf1e60baab07e68cae3e78a29d78cb7fde"
1937
+ gitHead: "76ff5edd816a2b9dd7f10db62c8fde89eec7771f"
1938
1938
  };
1939
1939
  }
1940
1940
  });
@@ -5291,6 +5291,19 @@ var SwarmClient = class _SwarmClient {
5291
5291
  * → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
5292
5292
  */
5293
5293
  async ownerMaxDrones() {
5294
+ return (await this.ownerSwarmSettings())?.maxDrones ?? null;
5295
+ }
5296
+ /**
5297
+ * TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
5298
+ * которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
5299
+ * двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
5300
+ * соседним полем того же ответа был бы платой ни за что.
5301
+ *
5302
+ * Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
5303
+ * (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
5304
+ * не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
5305
+ */
5306
+ async ownerSwarmSettings() {
5294
5307
  let res;
5295
5308
  try {
5296
5309
  res = await this.call("who_am_i", {});
@@ -5300,7 +5313,14 @@ var SwarmClient = class _SwarmClient {
5300
5313
  if (res?.isError) return null;
5301
5314
  const obj = jsonFromTextContent(res);
5302
5315
  const n = obj?.maxDrones;
5303
- return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
5316
+ if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
5317
+ const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
5318
+ return {
5319
+ maxDrones: Math.trunc(n),
5320
+ // Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
5321
+ // то есть прежнее поведение: env машины и дефолт харнесса.
5322
+ models: { drone: str(obj?.swarmSettings?.droneModel), reviewer: str(obj?.swarmSettings?.reviewerModel) }
5323
+ };
5304
5324
  }
5305
5325
  /**
5306
5326
  * Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
@@ -8709,6 +8729,16 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
8709
8729
  };
8710
8730
  }
8711
8731
 
8732
+ // src/swarmModels.ts
8733
+ function resolveRunModel(run, swarm, env = process.env) {
8734
+ const pick = (v) => {
8735
+ const s = typeof v === "string" ? v.trim() : "";
8736
+ return s.length > 0 ? s : null;
8737
+ };
8738
+ const fromSwarm = run.kind === "reviewer" ? pick(swarm?.reviewer) ?? pick(swarm?.drone) : pick(swarm?.drone);
8739
+ return pick(run.model) ?? fromSwarm ?? pick(env.SWARM_DRONE_MODEL) ?? null;
8740
+ }
8741
+
8712
8742
  // src/launcher.ts
8713
8743
  init_executors();
8714
8744
  var requireFromHere = createRequire(import.meta.url);
@@ -8846,7 +8876,7 @@ function makeLauncher(cfg) {
8846
8876
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
8847
8877
  }
8848
8878
  };
8849
- const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
8879
+ const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
8850
8880
  const realHome = cfg.droneIsolation?.realHome ?? homedir4();
8851
8881
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
8852
8882
  if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join12(cfg.droneIsolation.swarmHome, "token"))) {
package/dist/overmind.js CHANGED
@@ -4105,6 +4105,16 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4105
4105
  const text = res?.content?.find((c) => c.type === "text")?.text;
4106
4106
  return typeof text === "string" && text.trim() ? text : null;
4107
4107
  }
4108
+ async function reportAcceptanceCheck(input) {
4109
+ const res = await safeCall("report_acceptance_check", {
4110
+ id: input.id,
4111
+ state: input.state,
4112
+ ...input.evidence ? { evidence: input.evidence } : {}
4113
+ });
4114
+ if (!res) return null;
4115
+ const text = res.content?.find((c) => c.type === "text")?.text;
4116
+ return typeof text === "string" && text.trim() ? text : null;
4117
+ }
4108
4118
  async function resolveId(idOrSlug) {
4109
4119
  const cached = idCache.get(idOrSlug);
4110
4120
  if (cached) return cached;
@@ -4173,6 +4183,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4173
4183
  backlogStatusId,
4174
4184
  getDocument,
4175
4185
  getTaskContext,
4186
+ reportAcceptanceCheck,
4176
4187
  createTask: async (input) => {
4177
4188
  if (!input.projectId) {
4178
4189
  throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
@@ -4421,7 +4432,7 @@ var init_taskless = __esm({
4421
4432
  init_agentToken();
4422
4433
  init_workspace();
4423
4434
  SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
4424
- WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload"]);
4435
+ WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
4425
4436
  TaskBodyUnavailableError = class extends Error {
4426
4437
  constructor(slug, workspaceId) {
4427
4438
  super(
@@ -6686,6 +6697,19 @@ var SwarmClient = class _SwarmClient {
6686
6697
  * → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
6687
6698
  */
6688
6699
  async ownerMaxDrones() {
6700
+ return (await this.ownerSwarmSettings())?.maxDrones ?? null;
6701
+ }
6702
+ /**
6703
+ * TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
6704
+ * которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
6705
+ * двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
6706
+ * соседним полем того же ответа был бы платой ни за что.
6707
+ *
6708
+ * Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
6709
+ * (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
6710
+ * не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
6711
+ */
6712
+ async ownerSwarmSettings() {
6689
6713
  let res;
6690
6714
  try {
6691
6715
  res = await this.call("who_am_i", {});
@@ -6695,7 +6719,14 @@ var SwarmClient = class _SwarmClient {
6695
6719
  if (res?.isError) return null;
6696
6720
  const obj = jsonFromTextContent(res);
6697
6721
  const n = obj?.maxDrones;
6698
- return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
6722
+ if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
6723
+ const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
6724
+ return {
6725
+ maxDrones: Math.trunc(n),
6726
+ // Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
6727
+ // то есть прежнее поведение: env машины и дефолт харнесса.
6728
+ models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) }
6729
+ };
6699
6730
  }
6700
6731
  /**
6701
6732
  * Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
@@ -8186,6 +8217,16 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
8186
8217
  };
8187
8218
  }
8188
8219
 
8220
+ // src/swarmModels.ts
8221
+ function resolveRunModel(run, swarm, env = process.env) {
8222
+ const pick2 = (v) => {
8223
+ const s = typeof v === "string" ? v.trim() : "";
8224
+ return s.length > 0 ? s : null;
8225
+ };
8226
+ const fromSwarm = run.kind === "reviewer" ? pick2(swarm?.reviewer) ?? pick2(swarm?.drone) : pick2(swarm?.drone);
8227
+ return pick2(run.model) ?? fromSwarm ?? pick2(env.SWARM_DRONE_MODEL) ?? null;
8228
+ }
8229
+
8189
8230
  // src/launcher.ts
8190
8231
  init_executors();
8191
8232
  var requireFromHere = createRequire(import.meta.url);
@@ -8323,7 +8364,7 @@ function makeLauncher(cfg) {
8323
8364
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
8324
8365
  }
8325
8366
  };
8326
- const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
8367
+ const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
8327
8368
  const realHome = cfg.droneIsolation?.realHome ?? homedir4();
8328
8369
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
8329
8370
  if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
@@ -8391,7 +8432,7 @@ function accountRunCost(deps, run) {
8391
8432
  const dir = join13(deps.runLogsDir, String(run.id));
8392
8433
  const files = unprocessedLogFiles(dir);
8393
8434
  if (files.length === 0) return;
8394
- const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
8435
+ const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
8395
8436
  const executor = deps.executor ?? resolveExecutor();
8396
8437
  let added = 0;
8397
8438
  let tokensIn = 0;
@@ -9495,6 +9536,41 @@ ${notFoundText}` : notFoundText }],
9495
9536
  return { content: [{ type: "text", text: lines.join("\n") }] };
9496
9537
  }
9497
9538
  );
9539
+ server.tool(
9540
+ "report_acceptance_check",
9541
+ "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043E\u0434\u0438\u043D \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0442\u0432\u043E\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438: passed \u2014 \u043F\u0440\u043E\u0448\u043B\u0430, failed \u2014 \u0443\u043F\u0430\u043B\u0430, pending \u2014 \u0441\u043D\u043E\u0432\u0430 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C. id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0431\u0435\u0440\u0438 \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB (\u0441\u0442\u0440\u043E\u043A\u0430 \xAB(id: \u2026)\xBB) \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435 \u0438\u043B\u0438 \u0432 read_task. evidence \u2014 \u0414\u041E\u041A\u0410\u0417\u0410\u0422\u0415\u041B\u042C\u0421\u0422\u0412\u041E: \u0441\u0442\u0440\u043E\u043A\u0430 \u0432\u044B\u0432\u043E\u0434\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B. \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u0442\u0430\u0432\u044C \u043F\u043E \u0424\u0410\u041A\u0422\u0423 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0430 \u043D\u0435 \u043F\u043E \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044E: \xAB\u0443\u043F\u0430\u043B\u0430\xBB \u0438 \xAB\u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C\xBB \u2014 \u0440\u0430\u0437\u043D\u043E\u0435. \u041F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u0432\u0441\u0435\u043C \u043F\u0443\u043D\u043A\u0442\u0430\u043C \u041F\u0415\u0420\u0415\u0414 done(): \u043D\u0435\u043F\u0440\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043F\u043E \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0430 \u0440\u0430\u0431\u043E\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0441\u043D\u0438\u043C\u0430\u044E\u0442. \u0421\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0430\u043C\u0438 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043D\u0435\u0447\u0435\u043C: \u0438\u0445 \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0449\u0438\u043A.",
9542
+ {
9543
+ id: z.string().describe("id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB"),
9544
+ state: z.enum(["passed", "failed", "pending"]).describe("passed | failed | pending \u2014 \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430"),
9545
+ evidence: z.string().max(4e3).optional().describe("\u0447\u0435\u043C \u0434\u043E\u043A\u0430\u0437\u0430\u043D\u043E: \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435")
9546
+ },
9547
+ async ({ id, state, evidence }) => {
9548
+ if (!opts.taskless) {
9549
+ return {
9550
+ content: [
9551
+ {
9552
+ type: "text",
9553
+ text: "Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0433\u0430\u043B\u043E\u0447\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C. \u041E\u0442\u0447\u0438\u0442\u0430\u0439\u0441\u044F \u043F\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044E \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done()."
9554
+ }
9555
+ ],
9556
+ isError: true
9557
+ };
9558
+ }
9559
+ const text = await opts.taskless.reportAcceptanceCheck({ id, state, evidence });
9560
+ if (!text) {
9561
+ return {
9562
+ content: [
9563
+ {
9564
+ type: "text",
9565
+ text: `\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 ${id} \u043D\u0435 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u0430 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430): id \u043C\u043E\u0433 \u0431\u044B\u0442\u044C \u0447\u0443\u0436\u0438\u043C \u0438\u043B\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044C \u2014 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0439 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 read_task. \u041D\u0435 \u043C\u043E\u043B\u0447\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done().`
9566
+ }
9567
+ ],
9568
+ isError: true
9569
+ };
9570
+ }
9571
+ return { content: [{ type: "text", text }] };
9572
+ }
9573
+ );
9498
9574
  server.tool(
9499
9575
  "ask",
9500
9576
  "\u0417\u0430\u0434\u0430\u0442\u044C \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u043E\u0442\u0432\u0435\u0442\u0430. \u0415\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F \u2014 \u0434\u0430\u0439 options (2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430, \u0441\u0442\u0430\u043D\u0443\u0442 \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438); \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0432\u044B\u0431\u0435\u0440\u0435\u0442 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0438\u043B\u0438 \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u0441\u0432\u043E\u0451. \u041E\u043F\u0443\u0441\u0442\u0438 options \u0434\u043B\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043E\u0442\u0432\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u043E\u0439. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439.",
@@ -11037,7 +11113,7 @@ async function changedPaths(worktree, opts = {}) {
11037
11113
  // package.json
11038
11114
  var package_default = {
11039
11115
  name: "@taskless-app/swarm",
11040
- version: "0.6.4",
11116
+ version: "0.6.5",
11041
11117
  type: "module",
11042
11118
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
11043
11119
  keywords: [
@@ -11087,7 +11163,7 @@ var package_default = {
11087
11163
  execa: "^10.0.0",
11088
11164
  zod: "3.25.76"
11089
11165
  },
11090
- gitHead: "12ecebcf1e60baab07e68cae3e78a29d78cb7fde"
11166
+ gitHead: "76ff5edd816a2b9dd7f10db62c8fde89eec7771f"
11091
11167
  };
11092
11168
 
11093
11169
  // src/version.ts
@@ -13954,7 +14030,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
13954
14030
  // вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
13955
14031
  // ветку заводит spawnDrone из слага) — прежняя формулировка.
13956
14032
  `\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
13957
- "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
14033
+ "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
13958
14034
  "\u041F\u0440\u0430\u0432\u0438\u043B\u0430: \u0442\u0440\u043E\u0433\u0430\u0435\u0448\u044C UI \u2014 \u0421\u041D\u0410\u0427\u0410\u041B\u0410 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0441\u043A\u0438\u043B\u043B \u0441 UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u0433\u043E \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (\u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u0435 \u0445\u0430\u0440\u0434\u043A\u043E\u0434\u044C \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u043E\u0435); \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u0441\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438; \u043F\u043E\u0441\u043B\u0435 UI-\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 screenshots \u0443 done); \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0447\u0435\u0440\u0435\u0437 gh \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).",
13959
14035
  // TASWM-293: живой случай SCE-5 — ревьюер заблокировал PR за отсутствие CI (у проекта
13960
14036
  // его нет и не предполагается), автора разбудили «закрывать замечания», замечаний по коду
@@ -16117,6 +16193,7 @@ async function runDaemon(opts) {
16117
16193
  }
16118
16194
  } : void 0;
16119
16195
  let lastDroneCap = null;
16196
+ let lastSwarmModels = null;
16120
16197
  let lastOwnCapacity = null;
16121
16198
  const hostCommandState = hostCommandStatePath(SWARM_HOME);
16122
16199
  let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
@@ -16129,7 +16206,8 @@ async function runDaemon(opts) {
16129
16206
  executor: EXECUTOR,
16130
16207
  droneIsolation: DRONE_ISOLATION,
16131
16208
  channel: droneChannelLaunch,
16132
- droneCap: () => lastDroneCap
16209
+ droneCap: () => lastDroneCap,
16210
+ swarmModels: () => lastSwarmModels
16133
16211
  });
16134
16212
  let prepareReader;
16135
16213
  const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
@@ -16265,12 +16343,20 @@ async function runDaemon(opts) {
16265
16343
  if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
16266
16344
  const client = db;
16267
16345
  const readMaxDrones = makeMaxDronesReader({
16268
- fetch: () => client.ownerMaxDrones(),
16346
+ // TASWM-364: одним чтением берём и кап, и модели роя — это соседние поля ОДНОГО
16347
+ // ответа who_am_i. Модели складываем в снимок для спавна тем же побочным ходом,
16348
+ // что и кап ниже: второго сетевого канала за ними заводить незачем.
16349
+ fetch: async () => {
16350
+ const settings = await client.ownerSwarmSettings();
16351
+ if (settings) lastSwarmModels = settings.models;
16352
+ return settings?.maxDrones ?? null;
16353
+ },
16269
16354
  fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
16270
16355
  ttlMs: 1e4
16271
16356
  });
16272
16357
  maxDrones = async () => {
16273
- const own = lastOwnCapacity = hostMaxDrones ?? await readMaxDrones();
16358
+ const live = await readMaxDrones();
16359
+ const own = lastOwnCapacity = hostMaxDrones ?? live;
16274
16360
  return lastDroneCap = operatorMaxDrones != null ? Math.min(own, operatorMaxDrones) : own;
16275
16361
  };
16276
16362
  }
@@ -16467,7 +16553,9 @@ async function runDaemon(opts) {
16467
16553
  repoHasCi: (run) => repoHasCiWorkflows(run.worktree_path, run.repo || opts.repo),
16468
16554
  isAlive: pidAlive,
16469
16555
  // Учёт cost/usage (TASWM-188): реальный аккаунтант поверх RUN_LOGS_DIR + db.
16470
- accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR }),
16556
+ // TASWM-364: swarmModels — ТОТ ЖЕ снимок, что уехал в launcher: цена токенов считается
16557
+ // по модели, на которой прогон реально работал.
16558
+ accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels }),
16471
16559
  // Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
16472
16560
  // (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
16473
16561
  exitOutcome: makeExitClassifier(RUN_LOGS_DIR, EXECUTOR),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "execa": "^10.0.0",
39
39
  "zod": "3.25.76"
40
40
  },
41
- "gitHead": "12ecebcf1e60baab07e68cae3e78a29d78cb7fde",
41
+ "gitHead": "76ff5edd816a2b9dd7f10db62c8fde89eec7771f",
42
42
  "scripts": {
43
43
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
44
44
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",