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.
- package/.opencode/skills/council/SKILL.md +5 -1
- package/.opencode/skills/deep-dive/SKILL.md +1 -1
- package/.opencode/skills/deep-research/SKILL.md +6 -1
- package/.opencode/skills/swarm-pr-review/SKILL.md +1 -1
- package/dist/cli/index.js +12 -4
- package/dist/index.js +76 -12
- package/dist/prm/index.d.ts +3 -3
- package/dist/sandbox/linux/bubblewrap-executor.d.ts +2 -1
- package/dist/sandbox/macos/sandbox-exec-executor.d.ts +7 -6
- package/dist/sandbox/win32/restricted-environment-executor.d.ts +11 -1
- package/dist/tools/dispatch-lanes.d.ts +46 -0
- package/package.json +1 -1
- package/dist/sandbox/linux/edge-cases.d.ts +0 -89
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
28630
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/dist/prm/index.d.ts
CHANGED
|
@@ -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
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
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
|
|
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
|
-
* -
|
|
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
|
|
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.
|
|
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;
|