opencode-swarm 7.81.2 → 7.81.4

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.
@@ -92,7 +92,11 @@ RESEARCH CONTEXT
92
92
  continue only non-dependent architect work: prepare the synthesis outline,
93
93
  normalize the RESEARCH CONTEXT citations, and draft disagreement categories.
94
94
  Do not call `convene_general_council` or present conclusions from running
95
- lanes. Each dispatch message must
95
+ lanes. Dispatch promptly do not accumulate extensive planning prose before the
96
+ call, or output truncation may swallow the tool call itself. Keep each lane `prompt`
97
+ compact: send shared context ONCE via the `common_prompt` field, or have lanes read
98
+ it from a file by absolute path, instead of inlining the same large blob into every
99
+ lane prompt. Each dispatch message must
96
100
  include:
97
101
  - The question
98
102
  - Round number: 1
@@ -71,7 +71,7 @@ Each explorer mission receives:
71
71
  - The scope map context from Step 2
72
72
  - Instruction: "You are performing a [LANE] audit. Report findings as candidate observations with severity (INFO/LOW/MEDIUM/HIGH/CRITICAL), location, and evidence."
73
73
 
74
- Explorer missions are dispatched in parallel waves. Launch the wave, record the returned `batch_id`, then continue deterministic architect work that does not depend on lane output: refine the scope map, build the candidate ledger shell, inspect local evidence with read-only tools, and prepare reviewer shard structure. Do not synthesize findings from running lanes.
74
+ Explorer missions are dispatched in parallel waves. Launch the wave promptly — do not accumulate extensive planning prose before the call, or output truncation may swallow the tool call itself. Launch the wave, record the returned `batch_id`, then continue deterministic architect work that does not depend on lane output: refine the scope map, build the candidate ledger shell, inspect local evidence with read-only tools, and prepare reviewer shard structure. Do not synthesize findings from running lanes. Keep each lane `prompt` compact: send shared context ONCE via the `common_prompt` field, or have lanes read it from a file by absolute path, instead of inlining the same large blob into every lane prompt — oversized inline prompts produce malformed or truncated tool-call JSON.
75
75
 
76
76
  At the Step 4 boundary, call `collect_lane_results` with `wait: true` for every open wave batch. Treat missing, stale, cancelled, or failed lanes as explicit coverage gaps; do not silently proceed past a required lane. If `dispatch_lanes_async` is unavailable, use blocking `dispatch_lanes` or parallel Task calls and record that async advisory lanes were unavailable.
77
77
 
@@ -102,7 +102,12 @@ Dispatch up to `max_researchers` `the active swarm's sme agent` calls with
102
102
  `batch_id`, then continue architect-owned retrieval quality work that does not
103
103
  depend on worker output: tighten the evidence ledger, check source authority,
104
104
  prepare reviewer shard structure, and identify unresolved gaps. Do not write final
105
- claims from running lanes. Each sme dispatch must
105
+ claims from running lanes. Dispatch promptly do not accumulate extensive planning
106
+ prose before the call, or output truncation may swallow the tool call itself. Keep each
107
+ lane `prompt` compact: send shared context ONCE via the `common_prompt` field, or have
108
+ lanes read it from a file by absolute path, instead of inlining the same large blob into
109
+ every lane prompt — oversized inline prompts produce malformed or truncated tool-call
110
+ JSON. Each sme dispatch must
106
111
  include:
107
112
  - `DOMAIN`: the subtopic
108
113
  - `TASK`: "Synthesize an evidence-grounded answer for this subtopic. Cite each
@@ -491,7 +491,7 @@ Tool candidate rules:
491
491
 
492
492
  ## Phase 3: Parallel Base Explorer Lanes
493
493
 
494
- Launch all base lanes with `dispatch_lanes_async` when available. Pass the six lane specs together, set `max_concurrent` to `6`, record the returned `batch_id`, and continue only non-dependent architect work: refine the obligation ledger, inspect PR metadata, prepare micro-lane trigger checks, and run deterministic read-only local tools. Do not synthesize findings from running lanes.
494
+ Launch all base lanes with `dispatch_lanes_async` when available. Pass the six lane specs together, set `max_concurrent` to `6`, record the returned `batch_id`, and continue only non-dependent architect work: refine the obligation ledger, inspect PR metadata, prepare micro-lane trigger checks, and run deterministic read-only local tools. Do not synthesize findings from running lanes. Keep each lane `prompt` compact: send the shared review context (PR diff, obligation ledger, scope) ONCE via the `common_prompt` field, or have lanes read it from a file by absolute path, instead of inlining the same large blob into all six prompts — oversized inline prompts produce malformed or truncated tool-call JSON and force clumsy file workarounds.
495
495
 
496
496
  Before Phase 4 or synthesis, call `collect_lane_results` with `wait: true` for the base-lane batch and treat the collected `lane_results` as the join barrier. Missing, stale, cancelled, or failed base lanes are explicit review coverage gaps. If `dispatch_lanes_async` is unavailable, use blocking `dispatch_lanes`; if that is also unavailable, simulate isolated passes. Do not let one lane's conclusions bias another lane, and record unavailable deterministic dispatch in the validation gate.
497
497
 
package/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "opencode-swarm",
55
- version: "7.81.2",
55
+ version: "7.81.4",
56
56
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
57
57
  main: "dist/index.js",
58
58
  types: "dist/index.d.ts",
@@ -17763,11 +17763,11 @@ var init_tool_metadata = __esm(() => {
17763
17763
  ]
17764
17764
  },
17765
17765
  dispatch_lanes: {
17766
- description: "dispatch multiple read-only exploration/review lanes concurrently and return a structured join result",
17766
+ description: "dispatch read-only exploration/review lanes concurrently and BLOCK until all finish; prefer dispatch_lanes_async for non-blocking dispatch, use this only when promptAsync is unavailable",
17767
17767
  agents: ["architect"]
17768
17768
  },
17769
17769
  dispatch_lanes_async: {
17770
- description: "launch multiple read-only advisory lanes asynchronously and return a batch id for later collection",
17770
+ description: "launch read-only advisory lanes non-blockingly and return a batch id immediately so you can keep working; join later with collect_lane_results",
17771
17771
  agents: ["architect"]
17772
17772
  },
17773
17773
  collect_lane_results: {
@@ -41475,6 +41475,14 @@ async function activateProposal(directory, slug, force = false, options = {}) {
41475
41475
  reason: `proposal not found or already activated: ${readErr instanceof Error ? readErr.message : String(readErr)}`
41476
41476
  };
41477
41477
  }
41478
+ if (await isRejectedSkillContent(directory, cleanSlug, proposalContent)) {
41479
+ return {
41480
+ activated: false,
41481
+ from,
41482
+ to,
41483
+ reason: "previously rejected equivalent content"
41484
+ };
41485
+ }
41478
41486
  const flipped = proposalContent.replace(/^status:\s*draft\s*$/m, "status: active");
41479
41487
  let evaluation;
41480
41488
  if (options.evaluate) {
@@ -41497,7 +41505,7 @@ async function activateProposal(directory, slug, force = false, options = {}) {
41497
41505
  await appendRejectedSkillEdit({
41498
41506
  directory,
41499
41507
  slug: cleanSlug,
41500
- candidateContent: flipped,
41508
+ candidateContent: proposalContent,
41501
41509
  incumbentContent,
41502
41510
  operation: options.operation ?? "skill_apply"
41503
41511
  }, evaluation);
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.81.2",
72
+ version: "7.81.4",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -680,11 +680,11 @@ var init_tool_metadata = __esm(() => {
680
680
  ]
681
681
  },
682
682
  dispatch_lanes: {
683
- description: "dispatch multiple read-only exploration/review lanes concurrently and return a structured join result",
683
+ description: "dispatch read-only exploration/review lanes concurrently and BLOCK until all finish; prefer dispatch_lanes_async for non-blocking dispatch, use this only when promptAsync is unavailable",
684
684
  agents: ["architect"]
685
685
  },
686
686
  dispatch_lanes_async: {
687
- description: "launch multiple read-only advisory lanes asynchronously and return a batch id for later collection",
687
+ description: "launch read-only advisory lanes non-blockingly and return a batch id immediately so you can keep working; join later with collect_lane_results",
688
688
  agents: ["architect"]
689
689
  },
690
690
  collect_lane_results: {
@@ -28232,6 +28232,8 @@ class BubblewrapSandboxExecutor {
28232
28232
  "--unshare-ipc",
28233
28233
  "--die-with-parent",
28234
28234
  "--new-session",
28235
+ "--cap-drop",
28236
+ "ALL",
28235
28237
  ...bindArgs,
28236
28238
  "--dev",
28237
28239
  "/dev",
@@ -28528,6 +28530,18 @@ function probeWindowsSandbox() {
28528
28530
  function psStringEscape(s) {
28529
28531
  return s.replace(/[`"$]/g, "`$&");
28530
28532
  }
28533
+ function getSafeWindowsPath() {
28534
+ const raw = process.env.SystemRoot;
28535
+ const isValid = typeof raw === "string" && raw.length > 0 && /^[A-Za-z]:[\\/](?:[^\\/;"'<>|*?\r\n]+[\\/]?)*$/.test(raw);
28536
+ const sysRoot = isValid ? raw : "C:\\Windows";
28537
+ return `${sysRoot}\\System32;${sysRoot}`;
28538
+ }
28539
+ function isPowerShellNativeCommand(command) {
28540
+ return /^(?:Remove-Item|rm|del|erase|Copy-Item|cp|copy|Move-Item|mv|move|Rename-Item|ren|New-Item|ni|Get-Item|gi|Get-ChildItem|ls|dir|gci|Get-Content|cat|type|gc|Set-Content|sc|Add-Content|ac|Clear-Content|clc|Test-Path|Resolve-Path|Split-Path|Join-Path|Out-File|Get-Date)\b/i.test(command.trimStart());
28541
+ }
28542
+ function isSafePsCommandBody(command) {
28543
+ return !/[;&|`$()\r\n]/.test(command);
28544
+ }
28531
28545
  function isPathInScopes(command, scopes) {
28532
28546
  if (scopes.length === 0)
28533
28547
  return true;
@@ -28597,9 +28611,13 @@ class WindowsSandboxExecutor {
28597
28611
  if (!isPathInScopes(command, _allScopes)) {
28598
28612
  throw new SandboxError("Command targets paths outside authorized scopes", "PATH_ESCAPE_SCOPE");
28599
28613
  }
28600
- const safePath = "C:\\Windows\\System32;C:\\Windows";
28614
+ if (isPowerShellNativeCommand(command) && !isSafePsCommandBody(command)) {
28615
+ throw new SandboxError("PowerShell-native command body contains unsafe characters", "UNSAFE_PS_COMMAND");
28616
+ }
28617
+ const safePath = getSafeWindowsPath();
28601
28618
  const escapedTemp = psStringEscape(temp);
28602
28619
  const escapedCommand = psStringEscape(command);
28620
+ const commandExec = isPowerShellNativeCommand(command) ? `Invoke-Expression "${escapedCommand}"` : `cmd /c "${escapedCommand}"`;
28603
28621
  const psScript = `
28604
28622
  $ErrorActionPreference = 'Stop';
28605
28623
  try {
@@ -28626,8 +28644,8 @@ try {
28626
28644
  }
28627
28645
  }
28628
28646
 
28629
- # Execute the command via cmd /c
28630
- cmd /c "${escapedCommand}";
28647
+ # Execute the command — PS-native cmdlets via Invoke-Expression, others via the standard command interpreter
28648
+ ${commandExec};
28631
28649
  } catch {
28632
28650
  Write-Error $_.Exception.Message;
28633
28651
  exit 1;
@@ -28636,7 +28654,7 @@ try {
28636
28654
  }
28637
28655
  getEnvOverrides() {
28638
28656
  return {
28639
- PATH: "C:\\Windows\\System32;C:\\Windows",
28657
+ PATH: getSafeWindowsPath(),
28640
28658
  TEMP: null,
28641
28659
  TMP: null,
28642
28660
  LD_PRELOAD: null,
@@ -64022,6 +64040,14 @@ async function activateProposal(directory, slug, force = false, options = {}) {
64022
64040
  reason: `proposal not found or already activated: ${readErr instanceof Error ? readErr.message : String(readErr)}`
64023
64041
  };
64024
64042
  }
64043
+ if (await isRejectedSkillContent(directory, cleanSlug, proposalContent)) {
64044
+ return {
64045
+ activated: false,
64046
+ from,
64047
+ to,
64048
+ reason: "previously rejected equivalent content"
64049
+ };
64050
+ }
64025
64051
  const flipped = proposalContent.replace(/^status:\s*draft\s*$/m, "status: active");
64026
64052
  let evaluation;
64027
64053
  if (options.evaluate) {
@@ -64044,7 +64070,7 @@ async function activateProposal(directory, slug, force = false, options = {}) {
64044
64070
  await appendRejectedSkillEdit({
64045
64071
  directory,
64046
64072
  slug: cleanSlug,
64047
- candidateContent: flipped,
64073
+ candidateContent: proposalContent,
64048
64074
  incumbentContent,
64049
64075
  operation: options.operation ?? "skill_apply"
64050
64076
  }, evaluation);
@@ -96229,6 +96255,7 @@ If a tool modifies a file, it is a CODER tool. Delegate.
96229
96255
  2. ONE agent per message. Send, STOP, wait for response.
96230
96256
  Exception: Stage B reviewer/test_engineer gate agents for the SAME completed coder task may be dispatched together before waiting when both gates are required. This exception NEVER applies to coder delegations. Preserve ONE task per coder call.
96231
96257
  Separate parallel-mode exception (distinct from the Stage B exception above, and the ONLY case where more than one coder may be dispatched before waiting): when an active \`[PARALLEL EXECUTION PROFILE]\` directive is present in your context (parallelization_enabled=true), you MAY dispatch multiple {{AGENT_PREFIX}}coder agents in a single message — up to the stated max_concurrent_tasks — but ONLY for distinct, dependency-ready tasks whose declared file scopes do NOT overlap. Each coder still requires its own \`declare_scope\` call and carries exactly ONE task (Rule 3 still holds: never batch multiple objectives into one coder). Parallel coders each run in an isolated git worktree, so their writes never collide and are merged back automatically. If no \`[PARALLEL EXECUTION PROFILE]\` directive is present, dispatch coders one at a time.
96258
+ Read-only advisory-lane exception (NON-BLOCKING; distinct from both exceptions above): the "Send, STOP, wait" rule governs MUTATION delegations (coder, and the test_engineer/reviewer Stage B completion gates). It does NOT govern read-only advisory exploration/review lanes. When you dispatch read-only advisory lanes — \`{{AGENT_PREFIX}}explorer\`, \`{{AGENT_PREFIX}}sme\`, \`{{AGENT_PREFIX}}researcher\`, the council members (\`council_generalist\`/\`council_skeptic\`/\`council_domain_expert\`), or an advisory \`{{AGENT_PREFIX}}critic\` lane — use the NON-BLOCKING path so you keep working while they run. Dispatch PROMPTLY: emit the \`dispatch_lanes_async\` call EARLY with compact lane prompts — do not accumulate long planning prose or build oversized inline prompts first, or the tool call can be truncated out of your message and the lanes never launch (a real failure mode on smaller models). The lane mechanism is a SINGLE \`dispatch_lanes_async\` call carrying all lane specs — NOT a per-agent Task/run-in-background pattern. Call \`dispatch_lanes_async\` with all lane specs in one call, record the returned \`batch_id\`, then IMMEDIATELY continue non-dependent architect work (refine the plan/obligation ledger, inspect metadata, prepare the synthesis/reviewer structure, run deterministic read-only tools). Do NOT sit idle waiting on running lanes, and do NOT synthesize findings from still-running lanes. Join later by calling \`collect_lane_results\` with \`wait: true\` as the explicit barrier immediately before you synthesize. Use blocking \`dispatch_lanes\` only when \`dispatch_lanes_async\`/promptAsync is unavailable. Keep each lane prompt compact: send large shared context (PR diff, ledger, scope) ONCE via the \`common_prompt\` field, or have lanes read it from a file by absolute path, instead of inlining the same blob into every lane prompt — inlining large context into many lanes is what produces malformed or truncated tool-call JSON and forces clumsy file workarounds. This non-blocking exception applies ONLY to read-only advisory lanes; it NEVER applies to coder delegations, to the test_engineer/reviewer Stage B completion gates, or to the critic PLAN-review gate, which all still follow "Send, STOP, wait" (or the Stage B parallel-dispatch exception above).
96232
96259
  3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
96233
96260
  3a. PRE-DELEGATION SCOPE CALL (required): BEFORE every {{AGENT_PREFIX}}coder delegation, you MUST call \`declare_scope\` with { taskId, files } listing the exact file(s) this task will modify (including generated/lockfile paths). No \`declare_scope\` call → no coder delegation. See Rule 1a.
96234
96261
  3b. PRE-DELEGATION SCOPE CALL (test_engineer): BEFORE any {{AGENT_PREFIX}}test_engineer delegation that will CREATE or MODIFY test files, you MUST call \`declare_scope\` with { taskId, files } listing the exact test file path(s) to write. Omitting this call leaves the write scope undeclared and will block the write. See Rule 1a.
@@ -126396,6 +126423,9 @@ init_state();
126396
126423
  init_create_tool();
126397
126424
  var MAX_LANES = 8;
126398
126425
  var MAX_PROMPT_CHARS = 80000;
126426
+ var COMMON_PROMPT_SEPARATOR = `
126427
+
126428
+ `;
126399
126429
  var DEFAULT_TIMEOUT_MS3 = 300000;
126400
126430
  var MAX_TIMEOUT_MS2 = 1800000;
126401
126431
  var MAX_LANE_OUTPUT_CHARS = 20000;
@@ -126455,6 +126485,7 @@ var LaneSchema = exports_external.object({
126455
126485
  });
126456
126486
  var DispatchLanesArgsSchema = exports_external.object({
126457
126487
  lanes: exports_external.array(LaneSchema).min(1).max(MAX_LANES).describe("Read-only lane specs to dispatch concurrently"),
126488
+ common_prompt: exports_external.string().min(1).regex(/\S/, "common_prompt must contain non-whitespace content").max(MAX_PROMPT_CHARS - COMMON_PROMPT_SEPARATOR.length - 1).optional().describe("Optional shared context prepended to every lane prompt. Send large shared context (PR diff, obligation ledger, scope) ONCE here instead of inlining the same blob into each lane prompt; this keeps the tool-call payload small and avoids malformed/truncated tool-call JSON. Combined common_prompt + per-lane prompt must not exceed the per-lane character limit."),
126458
126489
  max_concurrent: exports_external.number().int().min(1).max(MAX_LANES).optional().describe("Maximum lanes in flight at once; defaults to lane count"),
126459
126490
  timeout_ms: exports_external.number().int().min(10).max(MAX_TIMEOUT_MS2).optional().describe("Per-lane session create/prompt timeout in milliseconds")
126460
126491
  });
@@ -126502,7 +126533,15 @@ async function executeDispatchLanes(args2, directory, context = {}) {
126502
126533
  message: "OpenCode session client is not available"
126503
126534
  });
126504
126535
  }
126505
- const lanes = parsed.data.lanes;
126536
+ const common = applyCommonPrompt(parsed.data.lanes, parsed.data.common_prompt);
126537
+ if (!common.ok) {
126538
+ return failureResult({
126539
+ failure_class: "invalid_args",
126540
+ message: "Invalid dispatch_lanes arguments",
126541
+ errors: common.errors
126542
+ });
126543
+ }
126544
+ const lanes = common.lanes;
126506
126545
  const maxConcurrent = Math.min(parsed.data.max_concurrent ?? lanes.length, lanes.length, MAX_LANES);
126507
126546
  const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_TIMEOUT_MS3;
126508
126547
  const dispatcher = _internals82.createParallelDispatcher({
@@ -126542,7 +126581,15 @@ async function executeDispatchLanesAsync(args2, directory, context = {}) {
126542
126581
  message: "OpenCode session promptAsync client is not available"
126543
126582
  });
126544
126583
  }
126545
- const lanes = parsed.data.lanes;
126584
+ const common = applyCommonPrompt(parsed.data.lanes, parsed.data.common_prompt);
126585
+ if (!common.ok) {
126586
+ return asyncFailureResult({
126587
+ failure_class: "invalid_args",
126588
+ message: "Invalid dispatch_lanes_async arguments",
126589
+ errors: common.errors
126590
+ });
126591
+ }
126592
+ const lanes = common.lanes;
126546
126593
  const batchId = parsed.data.batch_id ?? makeBatchId();
126547
126594
  if (findByBatchId(directory, batchId).length > 0) {
126548
126595
  return asyncFailureResult({
@@ -127113,6 +127160,21 @@ function collectFailureResult(args2) {
127113
127160
  errors: args2.errors
127114
127161
  };
127115
127162
  }
127163
+ function applyCommonPrompt(lanes, commonPrompt) {
127164
+ if (!commonPrompt)
127165
+ return { ok: true, lanes: [...lanes] };
127166
+ const errors5 = [];
127167
+ const merged = lanes.map((lane) => {
127168
+ const prompt = `${commonPrompt}${COMMON_PROMPT_SEPARATOR}${lane.prompt}`;
127169
+ if (prompt.length > MAX_PROMPT_CHARS) {
127170
+ errors5.push(`Lane "${lane.id}" combined common_prompt + prompt is ${prompt.length} chars ` + `(common_prompt ${commonPrompt.length} + separator ${COMMON_PROMPT_SEPARATOR.length} + ` + `lane prompt ${lane.prompt.length}; max ${MAX_PROMPT_CHARS})`);
127171
+ }
127172
+ return { ...lane, prompt };
127173
+ });
127174
+ if (errors5.length > 0)
127175
+ return { ok: false, errors: errors5 };
127176
+ return { ok: true, lanes: merged };
127177
+ }
127116
127178
  function findDuplicateLaneIds(lanes) {
127117
127179
  const seen = new Set;
127118
127180
  const duplicates = new Set;
@@ -127197,9 +127259,10 @@ function sleep2(ms) {
127197
127259
  });
127198
127260
  }
127199
127261
  var dispatch_lanes = createSwarmTool({
127200
- description: "Dispatch multiple read-only exploration/review lanes concurrently through OpenCode sessions and return a structured join result.",
127262
+ description: "Dispatch multiple read-only exploration/review lanes concurrently and BLOCK until every lane finishes, returning a structured join result. This blocks the caller until completion; for non-blocking dispatch that lets you keep working while lanes run, prefer dispatch_lanes_async + collect_lane_results and use this blocking variant only when promptAsync is unavailable. Keep each lane prompt compact: send large shared context once via common_prompt (or have lanes read it from a file by absolute path) instead of inlining it into every lane prompt.",
127201
127263
  args: {
127202
127264
  lanes: DispatchLanesArgsSchema.shape.lanes,
127265
+ common_prompt: DispatchLanesArgsSchema.shape.common_prompt,
127203
127266
  max_concurrent: DispatchLanesArgsSchema.shape.max_concurrent,
127204
127267
  timeout_ms: DispatchLanesArgsSchema.shape.timeout_ms
127205
127268
  },
@@ -127211,9 +127274,10 @@ var dispatch_lanes = createSwarmTool({
127211
127274
  }
127212
127275
  });
127213
127276
  var dispatch_lanes_async = createSwarmTool({
127214
- description: "Launch multiple read-only advisory lanes with OpenCode promptAsync and return immediately with a batch id for collect_lane_results.",
127277
+ description: "Launch multiple read-only advisory lanes with OpenCode promptAsync and return IMMEDIATELY with a batch id (non-blocking). After launching, keep working on non-dependent tasks while the lanes run, then call collect_lane_results to join. Keep each lane prompt compact: send large shared context once via common_prompt (or have lanes read it from a file by absolute path) instead of inlining it into every lane prompt, which can produce oversized or malformed tool-call JSON.",
127215
127278
  args: {
127216
127279
  lanes: DispatchLanesAsyncArgsSchema.shape.lanes,
127280
+ common_prompt: DispatchLanesAsyncArgsSchema.shape.common_prompt,
127217
127281
  max_concurrent: DispatchLanesAsyncArgsSchema.shape.max_concurrent,
127218
127282
  timeout_ms: DispatchLanesAsyncArgsSchema.shape.timeout_ms,
127219
127283
  batch_id: DispatchLanesAsyncArgsSchema.shape.batch_id,
@@ -8,9 +8,9 @@
8
8
  * - Escalation tracking via escalation
9
9
  *
10
10
  * This module provides the createPrmHook factory that returns the toolAfter
11
- * handler used by the swarm hook system. PRM replaces loop-detector.ts for
12
- * repetition_loop detection, but loop-detector.ts is kept as a fast circuit
13
- * breaker for backward compatibility.
11
+ * handler used by the swarm hook system. PRM complements loop-detector.ts:
12
+ * loop-detector.ts remains the fast circuit breaker in guardrails/tool-before,
13
+ * while PRM provides deeper multi-pattern detection and escalation.
14
14
  */
15
15
  export { formatCourseCorrectionForInjection, generateCourseCorrection, } from './course-correction';
16
16
  export { createDefaultEscalationState, EscalationTracker } from './escalation';
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Wraps shell commands with bwrap (Bubblewrap) to restrict process capabilities.
5
5
  * Uses --bind to mount scope paths read-write, --tmpfs for /tmp, and --ro-bind
6
- * for essential read-only system paths.
6
+ * for essential read-only system paths. Drops all capabilities via --cap-drop ALL
7
+ * for defense-in-depth within the user namespace.
7
8
  */
8
9
  import { type SandboxExecutor } from '../executor';
9
10
  /**
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * macOS sandbox-exec sandbox executor.
3
3
  *
4
- * Wraps shell commands with sandbox-exec(8) to restrict process capabilities
5
- * using a profile-based deny-by-default policy.
4
+ * Wraps shell commands with sandbox-exec(8) to enforce file-write containment.
6
5
  *
7
- * Profile allows:
6
+ * Profile scope:
7
+ * - Non-file operations (network, IPC, process creation, sysctl reads) are
8
+ * ALLOWED via `(allow default)`. This executor enforces file-write
9
+ * containment only — it is not a full-process sandbox.
8
10
  * - Read-only access to essential system paths (/usr, /bin, /sbin, /lib)
9
- * - Read-write access to each scope path
10
- * - Read-write access to the temp directory (500MB bounded)
11
- * - Denies all other file writes
11
+ * - Read-write access to each scope path and the temp directory
12
+ * - All other file writes are denied
12
13
  */
13
14
  import { type SandboxExecutor } from '../executor';
14
15
  /**
@@ -73,13 +73,23 @@ export declare class WindowsSandboxExecutor implements SandboxExecutor {
73
73
  * - Sets scoped temp directory (%TEMP%, %TMP%)
74
74
  * - Restricts PATH to safe system paths only
75
75
  * - Removes dangerous environment variables that could be used to bypass restrictions
76
- * - Executes the command via cmd /c inside a PowerShell script
76
+ * - Executes PowerShell-native cmdlets (filesystem cmdlets only) via Invoke-Expression,
77
+ * and all other commands via cmd /c inside a PowerShell script
78
+ *
79
+ * Safety checks applied before wrapping:
80
+ * - PowerShell escape patterns are rejected via detectPowerShellEscape
81
+ * - PowerShell-native commands are restricted to a filesystem-only cmdlet whitelist
82
+ * - PowerShell-native command bodies must not contain statement separators (;),
83
+ * call operator (&), pipelines (|), backtick escapes (`), variable references ($),
84
+ * subexpressions/parentheses, or newlines
77
85
  *
78
86
  * @param command - Raw shell command to execute inside the sandbox
79
87
  * @param scopePaths - Additional scope paths to allow (merged with constructor scope)
80
88
  * @param tempDir - Optional temp directory override
81
89
  * @returns A PowerShell-wrapped command string ready for shell execution,
82
90
  * or the raw command string when the sandbox is unavailable (passthrough mode)
91
+ * @throws {SandboxError} UNSAFE_PS_COMMAND when a PowerShell-native command body
92
+ * contains characters that enable command injection via Invoke-Expression
83
93
  */
84
94
  wrapCommand(command: string, scopePaths: string[], tempDir?: string): string;
85
95
  /**
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { createParallelDispatcher } from '../parallel/dispatcher/parallel-dispatcher.js';
3
3
  import { createSwarmTool } from './create-tool.js';
4
+ export declare const MAX_PROMPT_CHARS = 80000;
4
5
  declare const LaneSchema: z.ZodObject<{
5
6
  id: z.ZodString;
6
7
  agent: z.ZodString;
@@ -12,6 +13,7 @@ declare const DispatchLanesArgsSchema: z.ZodObject<{
12
13
  agent: z.ZodString;
13
14
  prompt: z.ZodString;
14
15
  }, z.core.$strip>>;
16
+ common_prompt: z.ZodOptional<z.ZodString>;
15
17
  max_concurrent: z.ZodOptional<z.ZodNumber>;
16
18
  timeout_ms: z.ZodOptional<z.ZodNumber>;
17
19
  }, z.core.$strip>;
@@ -21,6 +23,7 @@ declare const DispatchLanesAsyncArgsSchema: z.ZodObject<{
21
23
  agent: z.ZodString;
22
24
  prompt: z.ZodString;
23
25
  }, z.core.$strip>>;
26
+ common_prompt: z.ZodOptional<z.ZodString>;
24
27
  max_concurrent: z.ZodOptional<z.ZodNumber>;
25
28
  timeout_ms: z.ZodOptional<z.ZodNumber>;
26
29
  batch_id: z.ZodOptional<z.ZodString>;
@@ -190,10 +193,35 @@ export declare const _internals: {
190
193
  sleep: (ms: number) => Promise<void>;
191
194
  };
192
195
  export declare const _test_exports: {
196
+ applyCommonPrompt: typeof applyCommonPrompt;
193
197
  extractLastAssistantText: typeof extractLastAssistantText;
194
198
  formatError: typeof formatError;
195
199
  nextCollectPollInterval: typeof nextCollectPollInterval;
196
200
  promptHash: typeof promptHash;
201
+ DispatchLanesArgsSchema: z.ZodObject<{
202
+ lanes: z.ZodArray<z.ZodObject<{
203
+ id: z.ZodString;
204
+ agent: z.ZodString;
205
+ prompt: z.ZodString;
206
+ }, z.core.$strip>>;
207
+ common_prompt: z.ZodOptional<z.ZodString>;
208
+ max_concurrent: z.ZodOptional<z.ZodNumber>;
209
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
210
+ }, z.core.$strip>;
211
+ DispatchLanesAsyncArgsSchema: z.ZodObject<{
212
+ lanes: z.ZodArray<z.ZodObject<{
213
+ id: z.ZodString;
214
+ agent: z.ZodString;
215
+ prompt: z.ZodString;
216
+ }, z.core.$strip>>;
217
+ common_prompt: z.ZodOptional<z.ZodString>;
218
+ max_concurrent: z.ZodOptional<z.ZodNumber>;
219
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
220
+ batch_id: z.ZodOptional<z.ZodString>;
221
+ mode: z.ZodOptional<z.ZodString>;
222
+ pr_head_sha: z.ZodOptional<z.ZodString>;
223
+ scope: z.ZodOptional<z.ZodString>;
224
+ }, z.core.$strip>;
197
225
  };
198
226
  type ReadOnlyToolPermissions = Record<string, false> & {
199
227
  write: false;
@@ -217,6 +245,24 @@ declare function extractLastAssistantText(messages: Array<{
217
245
  }>;
218
246
  }>): string;
219
247
  declare function nextCollectPollInterval(currentMs: number): number;
248
+ type ApplyCommonPromptResult = {
249
+ ok: true;
250
+ lanes: DispatchLaneSpec[];
251
+ } | {
252
+ ok: false;
253
+ errors: string[];
254
+ };
255
+ /**
256
+ * Prepend an optional shared `common_prompt` to every lane prompt so callers can
257
+ * send large shared context once instead of inlining it into each lane (which
258
+ * bloats the tool-call payload and triggers truncated/malformed tool-call JSON).
259
+ * Returns an error when any assembled prompt exceeds the per-lane character limit.
260
+ *
261
+ * Always returns a fresh array the caller owns: a shallow copy of the originals
262
+ * when no `commonPrompt` is provided, or shallow-copied lanes with rewritten
263
+ * prompts when it is. Callers may treat the returned array as their own.
264
+ */
265
+ declare function applyCommonPrompt(lanes: DispatchLaneSpec[], commonPrompt: string | undefined): ApplyCommonPromptResult;
220
266
  declare function formatError(error: unknown): string;
221
267
  declare function promptHash(lane: DispatchLaneSpec, directory: string, batchId: string): string;
222
268
  export declare const dispatch_lanes: ReturnType<typeof createSwarmTool>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.81.2",
3
+ "version": "7.81.4",
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",
@@ -1,89 +0,0 @@
1
- /**
2
- * Edge case handling utilities for Bubblewrap sandbox.
3
- *
4
- * This module provides functions to detect and prevent:
5
- * - Symlink escape attacks
6
- * - /proc/self/fd access
7
- * - io_uring bypass
8
- * - Namespace escape
9
- * - Hard-link creation
10
- * - Rename/move across scope boundary
11
- * - mmap interception
12
- */
13
- /**
14
- * Check whether a path is a symlink and resolves to a location outside
15
- * any of the configured scope paths.
16
- *
17
- * @param path - The path to check (may be a symlink)
18
- * @param scopePaths - Array of absolute scope paths
19
- * @returns true if the path is a symlink that escapes the sandbox
20
- */
21
- export declare function detectSymlinkEscape(path: string, scopePaths: string[]): boolean;
22
- /**
23
- * Check whether a path is under /proc/self/fd/, which provides
24
- * file descriptor access that can bypass normal path checks.
25
- *
26
- * @param path - The path to check
27
- * @returns true if the path is under /proc/self/fd/
28
- */
29
- export declare function detectProcFdAccess(path: string): boolean;
30
- /**
31
- * Detect whether io_uring is active on the system, which can be used
32
- * to perform I/O operations that bypass the seccomp filter.
33
- *
34
- * Note: This is a detection function only — it does not prevent io_uring usage.
35
- * Bubblewrap's --unshare-all combined with seccomp filtering can mitigate this.
36
- *
37
- * @returns true if io_uring appears to be active
38
- */
39
- export declare function detectIoUringBypass(): boolean;
40
- /**
41
- * Detect whether the current process is already running inside a user namespace.
42
- *
43
- * When a process is already inside a user namespace (rather than the initial
44
- * namespace), it may have different privileges and isolation properties than
45
- * expected. This can affect the security assumptions of a bubblewrap sandbox.
46
- *
47
- * @returns true if the current process is already inside a non-initial user namespace
48
- */
49
- export declare function detectNamespaceEscape(): boolean;
50
- /**
51
- * Check whether a path operation would create a hard link that escapes
52
- * the sandbox scope.
53
- *
54
- * Hard links can allow a file inside the sandbox to be linked to a location
55
- * outside the sandbox, potentially bypassing containment.
56
- *
57
- * @param path - The path being linked to
58
- * @param scopePaths - Array of absolute scope paths
59
- * @returns true if creating a hard link at path would escape the sandbox
60
- */
61
- export declare function detectHardLinkEscape(path: string, scopePaths: string[]): boolean;
62
- /**
63
- * Alias for detectHardLinkEscape for API compatibility.
64
- * @param path - The path being linked to
65
- * @param scopePaths - Array of absolute scope paths
66
- * @returns true if creating a hard link at path would escape the sandbox
67
- */
68
- export declare function detectHardLinkCreation(path: string, scopePaths: string[]): boolean;
69
- /**
70
- * Check whether a rename or move operation crosses a scope boundary.
71
- *
72
- * Moving a file from inside a scope path to outside violates containment.
73
- *
74
- * @param oldPath - The original path
75
- * @param newPath - The destination path after rename/move
76
- * @param scopePaths - Array of absolute scope paths
77
- * @returns true if the rename crosses a scope boundary
78
- */
79
- export declare function detectRenameAcrossBoundary(oldPath: string, newPath: string, scopePaths: string[]): boolean;
80
- /**
81
- * Check whether a path pattern suggests mmap interception attempts.
82
- *
83
- * mmap can be used to map device files or anonymous memory that bypasses
84
- * normal file-based access controls.
85
- *
86
- * @param path - The path being accessed
87
- * @returns true if the path suggests mmap interception
88
- */
89
- export declare function detectMmapInterception(path: string): boolean;