opencode-swarm 7.28.1 → 7.28.3

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/index.js CHANGED
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.28.1",
37
+ version: "7.28.3",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -308,6 +308,27 @@ function bunHash(input) {
308
308
  }
309
309
  return hash;
310
310
  }
311
+ function killProcessTreeImpl(pid, signal, directKill, wasDetached) {
312
+ if (typeof pid !== "number" || pid <= 0) {
313
+ directKill();
314
+ return;
315
+ }
316
+ if (process.platform === "win32") {
317
+ try {
318
+ nodeSpawnSync("taskkill", ["/PID", String(pid), "/T", "/F"]);
319
+ } catch {
320
+ directKill();
321
+ }
322
+ return;
323
+ }
324
+ if (wasDetached) {
325
+ try {
326
+ process.kill(-pid, signal ?? "SIGKILL");
327
+ return;
328
+ } catch {}
329
+ }
330
+ directKill();
331
+ }
311
332
  function streamFromNode(pipe) {
312
333
  const collected = new Promise((resolve) => {
313
334
  if (!pipe) {
@@ -430,20 +451,34 @@ function bunSpawn(cmd, options) {
430
451
  return proc2.exitCode;
431
452
  },
432
453
  kill(sig) {
433
- proc2.kill(sig);
454
+ if (options?.killProcessTree) {
455
+ killProcessTreeImpl(proc2.pid, sig, () => proc2.kill(sig), false);
456
+ } else {
457
+ proc2.kill(sig);
458
+ }
434
459
  }
435
460
  };
436
461
  }
437
462
  const [file, ...args] = cmd;
463
+ const detached = options?.killProcessTree === true;
438
464
  const proc = nodeSpawn(file, args, {
439
465
  cwd: options?.cwd,
440
466
  env: options?.env,
467
+ detached,
468
+ windowsHide: true,
441
469
  stdio: [
442
470
  mapStdio(options?.stdin),
443
471
  mapStdio(options?.stdout),
444
472
  mapStdio(options?.stderr)
445
473
  ]
446
474
  });
475
+ const killChild = (signal) => {
476
+ if (detached) {
477
+ killProcessTreeImpl(proc.pid, signal, () => proc.kill(signal), true);
478
+ } else {
479
+ proc.kill(signal);
480
+ }
481
+ };
447
482
  let timeoutHandle;
448
483
  const exited = new Promise((resolve) => {
449
484
  proc.on("exit", (code) => resolve(code ?? 0));
@@ -451,7 +486,7 @@ function bunSpawn(cmd, options) {
451
486
  if (options?.timeout && options.timeout > 0) {
452
487
  timeoutHandle = setTimeout(() => {
453
488
  try {
454
- proc.kill("SIGKILL");
489
+ killChild("SIGKILL");
455
490
  } catch {}
456
491
  }, options.timeout);
457
492
  if (typeof timeoutHandle.unref === "function") {
@@ -471,7 +506,7 @@ function bunSpawn(cmd, options) {
471
506
  },
472
507
  kill(signal) {
473
508
  try {
474
- proc.kill(signal);
509
+ killChild(signal);
475
510
  } catch {}
476
511
  }
477
512
  };
@@ -46255,7 +46290,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
46255
46290
  const coverage = opts.coverage ?? false;
46256
46291
  switch (framework) {
46257
46292
  case "bun": {
46258
- const args = ["bun", "test"];
46293
+ const args = ["bun", "--smol", "test"];
46259
46294
  if (coverage)
46260
46295
  args.push("--coverage");
46261
46296
  if (scope !== "all" && files.length > 0)
@@ -48688,7 +48723,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
48688
48723
  function buildTestCommand2(framework, scope, files, coverage, baseDir) {
48689
48724
  switch (framework) {
48690
48725
  case "bun": {
48691
- const args = ["bun", "test"];
48726
+ const args = ["bun", "--smol", "test"];
48692
48727
  if (coverage)
48693
48728
  args.push("--coverage");
48694
48729
  if (scope !== "all" && files.length > 0) {
@@ -49225,17 +49260,24 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
49225
49260
  const proc = bunSpawn(command, {
49226
49261
  stdout: "pipe",
49227
49262
  stderr: "pipe",
49228
- cwd
49263
+ stdin: "ignore",
49264
+ cwd,
49265
+ killProcessTree: true
49266
+ });
49267
+ let timeoutHandle;
49268
+ const timeoutPromise = new Promise((resolve14) => {
49269
+ timeoutHandle = setTimeout(() => {
49270
+ proc.kill();
49271
+ resolve14(-1);
49272
+ }, timeout_ms);
49229
49273
  });
49230
- const timeoutPromise = new Promise((resolve14) => setTimeout(() => {
49231
- proc.kill();
49232
- resolve14(-1);
49233
- }, timeout_ms));
49234
49274
  const [exitCode, stdoutResult, stderrResult] = await Promise.all([
49235
49275
  Promise.race([proc.exited, timeoutPromise]),
49236
49276
  readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
49237
49277
  readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
49238
49278
  ]);
49279
+ if (timeoutHandle !== undefined)
49280
+ clearTimeout(timeoutHandle);
49239
49281
  const duration_ms = Date.now() - startTime;
49240
49282
  let output = stdoutResult.text;
49241
49283
  if (stderrResult.text) {
@@ -49538,7 +49580,6 @@ var init_test_runner = __esm(() => {
49538
49580
  files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
49539
49581
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
49540
49582
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
49541
- allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
49542
49583
  working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
49543
49584
  },
49544
49585
  async execute(args, directory) {
@@ -49612,7 +49653,8 @@ var init_test_runner = __esm(() => {
49612
49653
  }
49613
49654
  const scope = args.scope || "all";
49614
49655
  if (scope === "all") {
49615
- if (!args.allow_full_suite) {
49656
+ const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
49657
+ if (!fullSuiteAllowed) {
49616
49658
  const errorResult = {
49617
49659
  success: false,
49618
49660
  framework: "none",
@@ -5,6 +5,7 @@
5
5
  * Uses experimental.chat.messages.transform to provide non-blocking guidance.
6
6
  */
7
7
  import type { PluginConfig } from '../config';
8
+ import type { AgentSessionState } from '../state';
8
9
  import type { DelegationEnvelope, EnvelopeValidationResult } from '../types/delegation.js';
9
10
  /**
10
11
  * v6.33.1 CRIT-1: Fallback map for declared coder scope by taskId.
@@ -53,6 +54,26 @@ interface MessageWithParts {
53
54
  info: MessageInfo;
54
55
  parts: MessagePart[];
55
56
  }
57
+ declare function resolveDelegatedPlanTaskId(args: Record<string, unknown>, knownPlanTaskIds?: ReadonlySet<string>): string | null;
58
+ /**
59
+ * Resolves the correct task ID for evidence recording by chaining:
60
+ * 1. Explicit task_id in direct args (structured field)
61
+ * 2. Prompt-text extraction via resolveDelegatedPlanTaskId (plan-aware)
62
+ * 3. Session-state fallback via getEvidenceTaskId
63
+ *
64
+ * This fixes parallel evidence recording where multiple reviewer/test_engineer
65
+ * agents are dispatched for different tasks from the same architect session.
66
+ * Issue #970.
67
+ */
68
+ declare function resolveEvidenceTaskId(args: Record<string, unknown> | undefined, session: AgentSessionState, directory: string): Promise<string | null>;
69
+ /**
70
+ * _internals export for testing — do not use in production code.
71
+ * Exposes resolveEvidenceTaskId and resolveDelegatedPlanTaskId for unit testing.
72
+ */
73
+ export declare const _internals: {
74
+ resolveEvidenceTaskId: typeof resolveEvidenceTaskId;
75
+ resolveDelegatedPlanTaskId: typeof resolveDelegatedPlanTaskId;
76
+ };
56
77
  /**
57
78
  * Creates the experimental.chat.messages.transform hook for delegation gating.
58
79
  * Inspects coder delegations and warns when tasks are oversized or batched.
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var package_default;
48
48
  var init_package = __esm(() => {
49
49
  package_default = {
50
50
  name: "opencode-swarm",
51
- version: "7.28.1",
51
+ version: "7.28.3",
52
52
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
53
53
  main: "dist/index.js",
54
54
  types: "dist/index.d.ts",
@@ -16518,6 +16518,27 @@ function bunHash(input) {
16518
16518
  }
16519
16519
  return hash2;
16520
16520
  }
16521
+ function killProcessTreeImpl(pid, signal, directKill, wasDetached) {
16522
+ if (typeof pid !== "number" || pid <= 0) {
16523
+ directKill();
16524
+ return;
16525
+ }
16526
+ if (process.platform === "win32") {
16527
+ try {
16528
+ nodeSpawnSync("taskkill", ["/PID", String(pid), "/T", "/F"]);
16529
+ } catch {
16530
+ directKill();
16531
+ }
16532
+ return;
16533
+ }
16534
+ if (wasDetached) {
16535
+ try {
16536
+ process.kill(-pid, signal ?? "SIGKILL");
16537
+ return;
16538
+ } catch {}
16539
+ }
16540
+ directKill();
16541
+ }
16521
16542
  function streamFromNode(pipe2) {
16522
16543
  const collected = new Promise((resolve) => {
16523
16544
  if (!pipe2) {
@@ -16640,20 +16661,34 @@ function bunSpawn(cmd, options) {
16640
16661
  return proc2.exitCode;
16641
16662
  },
16642
16663
  kill(sig) {
16643
- proc2.kill(sig);
16664
+ if (options?.killProcessTree) {
16665
+ killProcessTreeImpl(proc2.pid, sig, () => proc2.kill(sig), false);
16666
+ } else {
16667
+ proc2.kill(sig);
16668
+ }
16644
16669
  }
16645
16670
  };
16646
16671
  }
16647
16672
  const [file2, ...args2] = cmd;
16673
+ const detached = options?.killProcessTree === true;
16648
16674
  const proc = nodeSpawn(file2, args2, {
16649
16675
  cwd: options?.cwd,
16650
16676
  env: options?.env,
16677
+ detached,
16678
+ windowsHide: true,
16651
16679
  stdio: [
16652
16680
  mapStdio(options?.stdin),
16653
16681
  mapStdio(options?.stdout),
16654
16682
  mapStdio(options?.stderr)
16655
16683
  ]
16656
16684
  });
16685
+ const killChild = (signal) => {
16686
+ if (detached) {
16687
+ killProcessTreeImpl(proc.pid, signal, () => proc.kill(signal), true);
16688
+ } else {
16689
+ proc.kill(signal);
16690
+ }
16691
+ };
16657
16692
  let timeoutHandle;
16658
16693
  const exited = new Promise((resolve) => {
16659
16694
  proc.on("exit", (code) => resolve(code ?? 0));
@@ -16661,7 +16696,7 @@ function bunSpawn(cmd, options) {
16661
16696
  if (options?.timeout && options.timeout > 0) {
16662
16697
  timeoutHandle = setTimeout(() => {
16663
16698
  try {
16664
- proc.kill("SIGKILL");
16699
+ killChild("SIGKILL");
16665
16700
  } catch {}
16666
16701
  }, options.timeout);
16667
16702
  if (typeof timeoutHandle.unref === "function") {
@@ -16681,7 +16716,7 @@ function bunSpawn(cmd, options) {
16681
16716
  },
16682
16717
  kill(signal) {
16683
16718
  try {
16684
- proc.kill(signal);
16719
+ killChild(signal);
16685
16720
  } catch {}
16686
16721
  }
16687
16722
  };
@@ -39012,6 +39047,24 @@ async function getEvidenceTaskId(session, directory) {
39012
39047
  }
39013
39048
  return null;
39014
39049
  }
39050
+ async function resolveEvidenceTaskId(args2, session, directory) {
39051
+ const rawTaskId = args2?.task_id;
39052
+ if (typeof rawTaskId === "string" && rawTaskId.length <= 20 && isStrictTaskId(rawTaskId.trim())) {
39053
+ return rawTaskId.trim();
39054
+ }
39055
+ if (args2) {
39056
+ try {
39057
+ const plan = await loadPlanJsonOnly(directory);
39058
+ if (plan) {
39059
+ const planTaskIds = new Set(plan.phases.flatMap((p) => p.tasks.map((t) => t.id)));
39060
+ const promptTaskId = resolveDelegatedPlanTaskId(args2, planTaskIds);
39061
+ if (promptTaskId)
39062
+ return promptTaskId;
39063
+ }
39064
+ } catch {}
39065
+ }
39066
+ return getEvidenceTaskId(session, directory);
39067
+ }
39015
39068
  function createDelegationGateHook(config2, directory) {
39016
39069
  const enabled = config2.hooks?.delegation_gate !== false;
39017
39070
  const delegationMaxChars = config2.hooks?.delegation_max_chars ?? 4000;
@@ -39253,8 +39306,8 @@ function createDelegationGateHook(config2, directory) {
39253
39306
  }
39254
39307
  if (typeof subagentType === "string") {
39255
39308
  try {
39256
- const rawTaskId = directArgs?.task_id;
39257
- const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && isStrictTaskId(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
39309
+ const mergedArgs = { ...storedArgs ?? {}, ...directArgs };
39310
+ const evidenceTaskId = await resolveEvidenceTaskId(mergedArgs, session, directory);
39258
39311
  if (evidenceTaskId) {
39259
39312
  const turbo = hasActiveTurboMode(input.sessionID);
39260
39313
  const gateAgents = [
@@ -39376,8 +39429,7 @@ function createDelegationGateHook(config2, directory) {
39376
39429
  }
39377
39430
  }
39378
39431
  try {
39379
- const rawTaskId = directArgs?.task_id;
39380
- const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && isStrictTaskId(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
39432
+ const evidenceTaskId = await resolveEvidenceTaskId(directArgs, session, directory);
39381
39433
  if (evidenceTaskId) {
39382
39434
  const turbo = hasActiveTurboMode(input.sessionID);
39383
39435
  if (hasReviewer) {
@@ -67331,7 +67383,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
67331
67383
  const coverage = opts.coverage ?? false;
67332
67384
  switch (framework) {
67333
67385
  case "bun": {
67334
- const args2 = ["bun", "test"];
67386
+ const args2 = ["bun", "--smol", "test"];
67335
67387
  if (coverage)
67336
67388
  args2.push("--coverage");
67337
67389
  if (scope !== "all" && files.length > 0)
@@ -69764,7 +69816,7 @@ function getTargetedExecutionUnsupportedReason(framework) {
69764
69816
  function buildTestCommand2(framework, scope, files, coverage, baseDir) {
69765
69817
  switch (framework) {
69766
69818
  case "bun": {
69767
- const args2 = ["bun", "test"];
69819
+ const args2 = ["bun", "--smol", "test"];
69768
69820
  if (coverage)
69769
69821
  args2.push("--coverage");
69770
69822
  if (scope !== "all" && files.length > 0) {
@@ -70301,17 +70353,24 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
70301
70353
  const proc = bunSpawn(command, {
70302
70354
  stdout: "pipe",
70303
70355
  stderr: "pipe",
70304
- cwd
70356
+ stdin: "ignore",
70357
+ cwd,
70358
+ killProcessTree: true
70359
+ });
70360
+ let timeoutHandle;
70361
+ const timeoutPromise = new Promise((resolve16) => {
70362
+ timeoutHandle = setTimeout(() => {
70363
+ proc.kill();
70364
+ resolve16(-1);
70365
+ }, timeout_ms);
70305
70366
  });
70306
- const timeoutPromise = new Promise((resolve16) => setTimeout(() => {
70307
- proc.kill();
70308
- resolve16(-1);
70309
- }, timeout_ms));
70310
70367
  const [exitCode, stdoutResult, stderrResult] = await Promise.all([
70311
70368
  Promise.race([proc.exited, timeoutPromise]),
70312
70369
  readBoundedStream(proc.stdout, MAX_OUTPUT_BYTES3),
70313
70370
  readBoundedStream(proc.stderr, MAX_OUTPUT_BYTES3)
70314
70371
  ]);
70372
+ if (timeoutHandle !== undefined)
70373
+ clearTimeout(timeoutHandle);
70315
70374
  const duration_ms = Date.now() - startTime;
70316
70375
  let output = stdoutResult.text;
70317
70376
  if (stderrResult.text) {
@@ -70614,7 +70673,6 @@ var init_test_runner = __esm(() => {
70614
70673
  files: exports_external.array(exports_external.string()).optional().describe('Specific files to test. For "convention", pass source files or direct test files. For "graph" and "impact", pass source files only.'),
70615
70674
  coverage: exports_external.boolean().optional().describe("Enable coverage reporting if supported"),
70616
70675
  timeout_ms: exports_external.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)"),
70617
- allow_full_suite: exports_external.boolean().optional().describe('Explicit opt-in for scope "all". Required because full-suite output can destabilize SSE streaming.'),
70618
70676
  working_directory: exports_external.string().optional().describe("Explicit project root directory. When provided, tests run relative to this path instead of the plugin context directory. Use this when CWD differs from the actual project root.")
70619
70677
  },
70620
70678
  async execute(args2, directory) {
@@ -70688,7 +70746,8 @@ var init_test_runner = __esm(() => {
70688
70746
  }
70689
70747
  const scope = args2.scope || "all";
70690
70748
  if (scope === "all") {
70691
- if (!args2.allow_full_suite) {
70749
+ const fullSuiteAllowed = process.env.SWARM_ALLOW_FULL_SUITE === "1" || process.env.SWARM_ALLOW_FULL_SUITE === "true";
70750
+ if (!fullSuiteAllowed) {
70692
70751
  const errorResult = {
70693
70752
  success: false,
70694
70753
  framework: "none",
@@ -23,7 +23,6 @@ export interface TestRunnerArgs {
23
23
  files?: string[];
24
24
  coverage?: boolean;
25
25
  timeout_ms?: number;
26
- allow_full_suite?: boolean;
27
26
  }
28
27
  export type RegressionOutcome = 'pass' | 'skip' | 'regression' | 'scope_exceeded' | 'error';
29
28
  export interface TestTotals {
@@ -71,6 +71,16 @@ export interface BunCompatSpawnOptions {
71
71
  stdout?: 'inherit' | 'ignore' | 'pipe';
72
72
  stderr?: 'inherit' | 'ignore' | 'pipe';
73
73
  timeout?: number;
74
+ /**
75
+ * When true, spawn the child as its own process-group leader (Node path:
76
+ * `detached`) and kill the entire descendant tree on `kill()`/timeout
77
+ * rather than only the direct child. A test runner that forks worker
78
+ * processes (jest/vitest, or a runaway suite) can otherwise outlive a
79
+ * `proc.kill()` of the parent and keep consuming memory after the timeout.
80
+ * Opt-in because the default single-child kill is correct for the many
81
+ * short-lived `bunSpawn` callers (git, lint, version checks).
82
+ */
83
+ killProcessTree?: boolean;
74
84
  }
75
85
  export interface BunCompatStream {
76
86
  text(): Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.28.1",
3
+ "version": "7.28.3",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",