opencode-swarm 6.36.0 → 6.37.0

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
@@ -36582,7 +36582,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
36582
36582
  return false;
36583
36583
  }
36584
36584
  function containsControlChars(str) {
36585
- return /[\0\r]/.test(str);
36585
+ return /[\0\t\r\n]/.test(str);
36586
36586
  }
36587
36587
  function validateDirectoryInput(dir) {
36588
36588
  if (!dir || dir.length === 0) {
@@ -50,6 +50,13 @@ export declare function createDelegationGateHook(config: PluginConfig, directory
50
50
  messagesTransform: (input: Record<string, never>, output: {
51
51
  messages?: MessageWithParts[];
52
52
  }) => Promise<void>;
53
+ toolBefore: (input: {
54
+ tool: string;
55
+ sessionID: string;
56
+ callID: string;
57
+ }, output: {
58
+ args: unknown;
59
+ }) => Promise<void>;
53
60
  toolAfter: (input: {
54
61
  tool: string;
55
62
  sessionID: string;
package/dist/index.js CHANGED
@@ -34195,7 +34195,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
34195
34195
  return false;
34196
34196
  }
34197
34197
  function containsControlChars(str) {
34198
- return /[\0\r]/.test(str);
34198
+ return /[\0\t\r\n]/.test(str);
34199
34199
  }
34200
34200
  function validateDirectoryInput(dir) {
34201
34201
  if (!dir || dir.length === 0) {
@@ -40386,7 +40386,7 @@ async function readPlanFromDisk(directory) {
40386
40386
  return null;
40387
40387
  }
40388
40388
  }
40389
- async function readEvidenceFromDisk(directory) {
40389
+ async function readGateEvidenceFromDisk(directory) {
40390
40390
  const evidenceMap = new Map;
40391
40391
  try {
40392
40392
  const evidenceDir = path3.join(directory, ".swarm", "evidence");
@@ -40421,7 +40421,7 @@ async function buildRehydrationCache(directory) {
40421
40421
  }
40422
40422
  }
40423
40423
  }
40424
- const evidenceMap = await readEvidenceFromDisk(directory);
40424
+ const evidenceMap = await readGateEvidenceFromDisk(directory);
40425
40425
  _rehydrationCache = { planTaskStates, evidenceMap };
40426
40426
  }
40427
40427
  function applyRehydrationCache(session) {
@@ -40530,7 +40530,9 @@ Do not re-trigger DISCOVER or CONSULT because you noticed a project phase bounda
40530
40530
  Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
40531
40531
 
40532
40532
  1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
40533
- YOUR TOOLS: Task (delegation), diff, syntax_check, placeholder_scan, imports, lint, secretscan, sast_scan, build_check, pre_check_batch, quality_budget, symbols, complexity_hotspots, schema_drift, todo_extract, evidence_check, sbom_generate, checkpoint, pkg_audit, test_runner.
40533
+ // IMPORTANT: This list MUST match AGENT_TOOL_MAP['architect'] in src/config/constants.ts
40534
+ // If you add a tool to the map, add it here. If you remove it from the map, remove it here.
40535
+ YOUR TOOLS: Task (delegation), checkpoint, check_gate_status, complexity_hotspots, declare_scope, detect_domains, diff, evidence_check, extract_code_blocks, gitingest, imports, knowledge_query, lint, pkg_audit, pre_check_batch, retrieve_summary, save_plan, schema_drift, secretscan, symbols, test_runner, todo_extract, update_task_status, write_retro.
40534
40536
  CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2014 any tool that modifies file contents.
40535
40537
  If a tool modifies a file, it is a CODER tool. Delegate.
40536
40538
  2. ONE agent per message. Send, STOP, wait for response.
@@ -51144,15 +51146,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51144
51146
  const errorContent = output.error ?? outputStr;
51145
51147
  if (typeof errorContent === "string" && TRANSIENT_MODEL_ERROR_PATTERN.test(errorContent) && !session.modelFallbackExhausted) {
51146
51148
  session.model_fallback_index++;
51147
- session.modelFallbackExhausted = true;
51148
51149
  const baseAgentName = session.agentName ? session.agentName.replace(/^[^_]+[_]/, "") : "";
51149
- const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, getSwarmAgents());
51150
+ const swarmAgents = getSwarmAgents();
51151
+ const fallbackModels = swarmAgents?.[baseAgentName]?.fallback_models;
51152
+ session.modelFallbackExhausted = !fallbackModels || session.model_fallback_index > fallbackModels.length;
51153
+ const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, swarmAgents);
51150
51154
  if (fallbackModel) {
51151
- const swarmAgents = getSwarmAgents();
51152
51155
  const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
51156
+ if (swarmAgents?.[baseAgentName]) {
51157
+ swarmAgents[baseAgentName].model = fallbackModel;
51158
+ }
51153
51159
  telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel, "transient_model_error");
51154
51160
  session.pendingAdvisoryMessages ??= [];
51155
- session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `Configured fallback model: "${fallbackModel}". ` + `Consider retrying with this model or using /swarm handoff to reset.`);
51161
+ session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
51156
51162
  } else {
51157
51163
  session.pendingAdvisoryMessages ??= [];
51158
51164
  session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `No fallback models configured for this agent. Add "fallback_models": ["model-a", "model-b"] ` + `to the agent's config in opencode-swarm.json.`);
@@ -51558,9 +51564,40 @@ function createDelegationGateHook(config3, directory) {
51558
51564
  if (!enabled) {
51559
51565
  return {
51560
51566
  messagesTransform: async (_input, _output) => {},
51567
+ toolBefore: async () => {},
51561
51568
  toolAfter: async () => {}
51562
51569
  };
51563
51570
  }
51571
+ const toolBefore = async (input, output) => {
51572
+ if (!input.sessionID)
51573
+ return;
51574
+ const normalized = input.tool.replace(/^[^:]+[:.]/, "");
51575
+ if (normalized !== "Task" && normalized !== "task")
51576
+ return;
51577
+ const args2 = output.args;
51578
+ if (!args2)
51579
+ return;
51580
+ const subagentType = args2.subagent_type;
51581
+ if (typeof subagentType !== "string")
51582
+ return;
51583
+ const targetAgent = stripKnownSwarmPrefix(subagentType);
51584
+ if (targetAgent !== "coder")
51585
+ return;
51586
+ const session = swarmState.agentSessions.get(input.sessionID);
51587
+ if (!session || !session.taskWorkflowStates)
51588
+ return;
51589
+ for (const [taskId, state2] of session.taskWorkflowStates) {
51590
+ if (state2 !== "coder_delegated")
51591
+ continue;
51592
+ const turbo = hasActiveTurboMode(input.sessionID);
51593
+ if (turbo) {
51594
+ const isTier3 = taskId.startsWith("3.");
51595
+ if (!isTier3)
51596
+ continue;
51597
+ }
51598
+ throw new Error(`REVIEWER_GATE_VIOLATION: Cannot re-delegate to coder without reviewer delegation. ` + `Task ${taskId} state: coder_delegated. Delegate to reviewer first.`);
51599
+ }
51600
+ };
51564
51601
  const toolAfter = async (input, _output) => {
51565
51602
  if (!input.sessionID)
51566
51603
  return;
@@ -51782,6 +51819,7 @@ function createDelegationGateHook(config3, directory) {
51782
51819
  }
51783
51820
  };
51784
51821
  return {
51822
+ toolBefore,
51785
51823
  messagesTransform: async (_input, output) => {
51786
51824
  const messages = output.messages;
51787
51825
  if (!messages || messages.length === 0)
@@ -66221,6 +66259,7 @@ var OpenCodeSwarm = async (ctx) => {
66221
66259
  }
66222
66260
  await guardrailsHooks.toolBefore(input, output);
66223
66261
  await scopeGuardHook.toolBefore(input, output);
66262
+ await delegationGateHooks.toolBefore(input, output);
66224
66263
  if (swarmState.lastBudgetPct >= 50) {
66225
66264
  const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
66226
66265
  if (!pressureSession.contextPressureWarningSent) {
@@ -2,6 +2,6 @@
2
2
  * Adversarial security tests for Task 2.15: Session restart rehydration
3
3
  *
4
4
  * ONLY attack vectors - malformed inputs, oversized payloads, injection attempts, boundary violations
5
- * Tests the security hardening of rehydrateSessionFromDisk, readPlanFromDisk, and readEvidenceFromDisk
5
+ * Tests the security hardening of rehydrateSessionFromDisk, readPlanFromDisk, and readGateEvidenceFromDisk
6
6
  */
7
7
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.36.0",
3
+ "version": "6.37.0",
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",