@taskless-app/swarm 0.6.43 → 0.6.44

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
@@ -3390,7 +3390,7 @@ var init_package = __esm({
3390
3390
  "package.json"() {
3391
3391
  package_default = {
3392
3392
  name: "@taskless-app/swarm",
3393
- version: "0.6.43",
3393
+ version: "0.6.44",
3394
3394
  type: "module",
3395
3395
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3396
3396
  keywords: [
@@ -3441,7 +3441,7 @@ var init_package = __esm({
3441
3441
  execa: "^10.0.0",
3442
3442
  zod: "3.25.76"
3443
3443
  },
3444
- gitHead: "609513603dc6deac786b27462c041e8544589bcf"
3444
+ gitHead: "21322aad3f4de6898a9aa9cc2eb884cfdd2ebb5e"
3445
3445
  };
3446
3446
  }
3447
3447
  });
@@ -4778,6 +4778,9 @@ function rowToRun(row) {
4778
4778
  // старые прогоны без колонки → 0
4779
4779
  task_title: row.task_title ?? null,
4780
4780
  task_desc: row.task_desc ?? null,
4781
+ // TASWM-556: когда заведена сама задача — по ней проверка дубля отличает свой
4782
+ // смерженный PR от чужого на переиспользованном слаге.
4783
+ task_created_at: row.task_created_at ?? null,
4781
4784
  task_url: row.task_url ?? null,
4782
4785
  priority: row.priority ?? 0,
4783
4786
  // старые прогоны без колонки → 0 (обычный приоритет)
@@ -4849,6 +4852,7 @@ CREATE TABLE IF NOT EXISTS runs (
4849
4852
  review_cycle INTEGER NOT NULL DEFAULT 0,
4850
4853
  task_title TEXT,
4851
4854
  task_desc TEXT,
4855
+ task_created_at TEXT,
4852
4856
  task_url TEXT,
4853
4857
  priority INTEGER NOT NULL DEFAULT 0,
4854
4858
  base_branch TEXT,
@@ -4921,6 +4925,8 @@ CREATE TABLE IF NOT EXISTS imports (
4921
4925
  "ALTER TABLE runs ADD COLUMN depends_on TEXT",
4922
4926
  "ALTER TABLE runs ADD COLUMN task_title TEXT",
4923
4927
  "ALTER TABLE runs ADD COLUMN task_desc TEXT",
4928
+ // TASWM-556: время создания задачи (снимок из прода) — признак «этот смерженный PR мой».
4929
+ "ALTER TABLE runs ADD COLUMN task_created_at TEXT",
4924
4930
  "ALTER TABLE runs ADD COLUMN task_url TEXT",
4925
4931
  // Иерархия роя (TASWM-78): роль прогона (drone|hero) и ссылка на прогон-героя.
4926
4932
  "ALTER TABLE runs ADD COLUMN kind TEXT NOT NULL DEFAULT 'drone'",
@@ -9276,6 +9282,8 @@ var SwarmClient = class _SwarmClient {
9276
9282
  task_title: null,
9277
9283
  task_desc: null,
9278
9284
  task_url: null,
9285
+ // TASWM-556: когда заведена задача — прод-поле снимка, заполняется ниже из ответа.
9286
+ task_created_at: null,
9279
9287
  // Машина прогона (TASWM-281) и её закрепление (TASWM-291) — прод-поля,
9280
9288
  // заполняются ниже из ответа.
9281
9289
  host_id: null,
@@ -9299,6 +9307,7 @@ var SwarmClient = class _SwarmClient {
9299
9307
  else if (!isTaskSlug(local.task_slug)) local.task_slug = pr.taskId || local.task_slug;
9300
9308
  local.task_title = pr.taskTitle ?? local.task_title;
9301
9309
  local.task_desc = pr.taskDesc ?? local.task_desc;
9310
+ local.task_created_at = pr.taskCreatedAt ?? local.task_created_at;
9302
9311
  local.name = pr.droneName ?? local.name;
9303
9312
  local.kind = pr.role ?? local.kind;
9304
9313
  const heroProd = pr.heroId ?? null;
package/dist/overmind.js CHANGED
@@ -1896,6 +1896,17 @@ function prBelongsToTask(slug, pr) {
1896
1896
  const after = title[at + s.length] ?? "";
1897
1897
  return !/[a-z0-9]/.test(before) && !/[a-z0-9-]/.test(after);
1898
1898
  }
1899
+ function mergedPrIsTaskWork(slug, pr, taskCreatedAt) {
1900
+ if (!prBelongsToTask(slug, pr)) return false;
1901
+ const merged = Date.parse((pr.mergedAt ?? "").trim());
1902
+ const created = Date.parse((taskCreatedAt ?? "").trim());
1903
+ if (!Number.isFinite(merged) || !Number.isFinite(created)) return true;
1904
+ return merged >= created;
1905
+ }
1906
+ function pickTaskMergedPr(slug, prs, taskCreatedAt) {
1907
+ const own = prs.filter((p) => typeof p.url === "string" && p.url.trim() && mergedPrIsTaskWork(slug, p, taskCreatedAt)).sort((a, b) => (Date.parse((b.mergedAt ?? "").trim()) || 0) - (Date.parse((a.mergedAt ?? "").trim()) || 0));
1908
+ return own[0]?.url?.trim() || null;
1909
+ }
1899
1910
  function decideTaskWork(facts) {
1900
1911
  const candidate = (facts.mergedCandidate ?? "").trim() || null;
1901
1912
  const open = [];
@@ -6203,6 +6214,9 @@ function rowToRun(row) {
6203
6214
  // старые прогоны без колонки → 0
6204
6215
  task_title: row.task_title ?? null,
6205
6216
  task_desc: row.task_desc ?? null,
6217
+ // TASWM-556: когда заведена сама задача — по ней проверка дубля отличает свой
6218
+ // смерженный PR от чужого на переиспользованном слаге.
6219
+ task_created_at: row.task_created_at ?? null,
6206
6220
  task_url: row.task_url ?? null,
6207
6221
  priority: row.priority ?? 0,
6208
6222
  // старые прогоны без колонки → 0 (обычный приоритет)
@@ -6274,6 +6288,7 @@ CREATE TABLE IF NOT EXISTS runs (
6274
6288
  review_cycle INTEGER NOT NULL DEFAULT 0,
6275
6289
  task_title TEXT,
6276
6290
  task_desc TEXT,
6291
+ task_created_at TEXT,
6277
6292
  task_url TEXT,
6278
6293
  priority INTEGER NOT NULL DEFAULT 0,
6279
6294
  base_branch TEXT,
@@ -6346,6 +6361,8 @@ CREATE TABLE IF NOT EXISTS imports (
6346
6361
  "ALTER TABLE runs ADD COLUMN depends_on TEXT",
6347
6362
  "ALTER TABLE runs ADD COLUMN task_title TEXT",
6348
6363
  "ALTER TABLE runs ADD COLUMN task_desc TEXT",
6364
+ // TASWM-556: время создания задачи (снимок из прода) — признак «этот смерженный PR мой».
6365
+ "ALTER TABLE runs ADD COLUMN task_created_at TEXT",
6349
6366
  "ALTER TABLE runs ADD COLUMN task_url TEXT",
6350
6367
  // Иерархия роя (TASWM-78): роль прогона (drone|hero) и ссылка на прогон-героя.
6351
6368
  "ALTER TABLE runs ADD COLUMN kind TEXT NOT NULL DEFAULT 'drone'",
@@ -9503,6 +9520,8 @@ var SwarmClient = class _SwarmClient {
9503
9520
  task_title: null,
9504
9521
  task_desc: null,
9505
9522
  task_url: null,
9523
+ // TASWM-556: когда заведена задача — прод-поле снимка, заполняется ниже из ответа.
9524
+ task_created_at: null,
9506
9525
  // Машина прогона (TASWM-281) и её закрепление (TASWM-291) — прод-поля,
9507
9526
  // заполняются ниже из ответа.
9508
9527
  host_id: null,
@@ -9526,6 +9545,7 @@ var SwarmClient = class _SwarmClient {
9526
9545
  else if (!isTaskSlug(local.task_slug)) local.task_slug = pr.taskId || local.task_slug;
9527
9546
  local.task_title = pr.taskTitle ?? local.task_title;
9528
9547
  local.task_desc = pr.taskDesc ?? local.task_desc;
9548
+ local.task_created_at = pr.taskCreatedAt ?? local.task_created_at;
9529
9549
  local.name = pr.droneName ?? local.name;
9530
9550
  local.kind = pr.role ?? local.kind;
9531
9551
  const heroProd = pr.heroId ?? null;
@@ -12294,7 +12314,7 @@ function parkReasonUnretriable(reason) {
12294
12314
  // package.json
12295
12315
  var package_default = {
12296
12316
  name: "@taskless-app/swarm",
12297
- version: "0.6.43",
12317
+ version: "0.6.44",
12298
12318
  type: "module",
12299
12319
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12300
12320
  keywords: [
@@ -12345,7 +12365,7 @@ var package_default = {
12345
12365
  execa: "^10.0.0",
12346
12366
  zod: "3.25.76"
12347
12367
  },
12348
- gitHead: "609513603dc6deac786b27462c041e8544589bcf"
12368
+ gitHead: "21322aad3f4de6898a9aa9cc2eb884cfdd2ebb5e"
12349
12369
  };
12350
12370
 
12351
12371
  // src/version.ts
@@ -12689,13 +12709,17 @@ async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
12689
12709
  return [];
12690
12710
  }
12691
12711
  }
12692
- async function mergedPrForBranch(branch, repoDir, run = realRepoRunner) {
12712
+ async function mergedPrForBranch(branch, repoDir, run = realRepoRunner, taskCreatedAt) {
12693
12713
  if (!branch || !repoDir) return null;
12694
12714
  try {
12695
- const out = await run(["pr", "list", "--head", branch, "--state", "merged", "--json", "url", "--limit", "1"], repoDir);
12715
+ const out = await run(
12716
+ ["pr", "list", "--head", branch, "--state", "merged", "--json", "url,headRefName,title,mergedAt", "--limit", "20"],
12717
+ repoDir
12718
+ );
12696
12719
  const arr = JSON.parse(out);
12697
- const url = Array.isArray(arr) ? arr[0]?.url : void 0;
12698
- return typeof url === "string" ? url : null;
12720
+ if (!Array.isArray(arr)) return null;
12721
+ const prs = arr.map((p) => ({ ...p, headRefName: p.headRefName ?? branch }));
12722
+ return pickTaskMergedPr(branch, prs, taskCreatedAt);
12699
12723
  } catch {
12700
12724
  return null;
12701
12725
  }
@@ -18333,6 +18357,20 @@ async function taskWorkState(deps, slug, repoDir, candidate) {
18333
18357
  const openPrs = deps.openPrsForTask ? await deps.openPrsForTask(slug, repoDir).catch(() => null) : null;
18334
18358
  return decideTaskWork({ mergedCandidate: candidate, openPrs });
18335
18359
  }
18360
+ function taskCreatedAtFor(runs) {
18361
+ let best = null;
18362
+ for (const r of runs) {
18363
+ const at = (r.task_created_at ?? "").trim();
18364
+ if (!at) continue;
18365
+ if (!best || r.id > best.id) best = { id: r.id, at };
18366
+ }
18367
+ return best?.at ?? null;
18368
+ }
18369
+ function taskCreatedAtForRun(deps, run) {
18370
+ const own = (run.task_created_at ?? "").trim();
18371
+ if (own) return own;
18372
+ return taskCreatedAtFor(deps.db.listRuns().filter((r) => r.task_slug === run.task_slug));
18373
+ }
18336
18374
  function noteOpenPrHold(deps, run, v) {
18337
18375
  if (deps.db.events(run.id).some((e) => (e.text ?? "").includes(OPEN_PR_HOLD_MARK))) return;
18338
18376
  deps.db.addEvent(run.id, "step", formatOpenPrHold(v));
@@ -18376,7 +18414,7 @@ async function closeMergedTaskRuns(deps) {
18376
18414
  }
18377
18415
  }
18378
18416
  if (!mergedUrl && deps.mergedPrForBranch) {
18379
- mergedUrl = await deps.mergedPrForBranch(slug, group[0].repo || "").catch(() => null);
18417
+ mergedUrl = await deps.mergedPrForBranch(slug, group[0].repo || "", taskCreatedAtFor(group)).catch(() => null);
18380
18418
  }
18381
18419
  if (!mergedUrl) continue;
18382
18420
  const work = await taskWorkState(deps, slug, group[0].repo || "", mergedUrl);
@@ -19678,7 +19716,7 @@ async function trySpawnQueuedRun(deps, run) {
19678
19716
  if (d.log) console.error(`[overmind] ${run.task_slug}: ${why}${d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`}`);
19679
19717
  return "closed";
19680
19718
  }
19681
- const mergedCandidate = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
19719
+ const mergedCandidate = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "", taskCreatedAtForRun(deps, run)).catch(() => null) : null);
19682
19720
  const work = mergedCandidate ? await taskWorkState(deps, run.task_slug, run.repo || "", mergedCandidate) : null;
19683
19721
  if (work?.kind === "open") noteOpenPrHold(deps, run, work);
19684
19722
  const mergedUrl = work?.kind === "merged" ? work.pr : null;
@@ -22268,7 +22306,7 @@ async function runDaemon(opts) {
22268
22306
  commitsSince: prHasCommitsSince,
22269
22307
  // Смерженный PR ветки-слага (TASWM-206) — тот же фолбэк на дефолт-репо роя, что и
22270
22308
  // openPrsForBranch выше; реальный gh, логика закрытия дублей — в tickOnce (тестируется).
22271
- mergedPrForBranch: (branch, repoDir) => mergedPrForBranch(branch, repoDir || opts.repo),
22309
+ mergedPrForBranch: (branch, repoDir, taskCreatedAt) => mergedPrForBranch(branch, repoDir || opts.repo, void 0, taskCreatedAt),
22272
22310
  // ВСЕ открытые PR задачи (TASWM-515) — тот же фолбэк на дефолт-репо роя. Реальный gh;
22273
22311
  // и отбор «этот PR — задачи», и решение «закончена ли работа» чистые (swarm-core).
22274
22312
  openPrsForTask: (slug, repoDir) => openPrsForTask(slug, repoDir || opts.repo),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.43",
3
+ "version": "0.6.44",
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": [
@@ -39,7 +39,7 @@
39
39
  "execa": "^10.0.0",
40
40
  "zod": "3.25.76"
41
41
  },
42
- "gitHead": "609513603dc6deac786b27462c041e8544589bcf",
42
+ "gitHead": "21322aad3f4de6898a9aa9cc2eb884cfdd2ebb5e",
43
43
  "scripts": {
44
44
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
45
45
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",