opencode-swarm 6.59.0 → 6.60.1

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/README.md CHANGED
@@ -759,14 +759,17 @@ Use glob patterns for complex path matching:
759
759
  - `?` — Match single character: `test?.js` matches `test1.js`, `testa.js`
760
760
  - Uses [picomatch](https://github.com/micromatch/picomatch) for cross-platform compatibility
761
761
 
762
+ **Path Normalization and Symlinks:**
763
+ Paths are resolved via `realpathSync` before matching, which resolves symlinks and prevents path-traversal escapes. However, if a symlink's target does not exist, `realpathSync` throws and the fallback returns the symlink's own path (unresolved). A dangling symlink inside an `allowedPrefix` directory will therefore pass prefix-based checks even if its intended target is outside the project. Use `blockedExact` or `blockedGlobs` to deny known dangling-symlink paths explicitly.
764
+
762
765
  **Evaluation Order:**
763
766
  1. `readOnly` check (if true, deny all writes)
764
767
  2. `blockedExact` (exact path matches, highest priority)
765
768
  3. `blockedGlobs` (glob pattern matches)
766
769
  4. `allowedExact` (exact path matches, overrides prefix/glob restrictions)
767
770
  5. `allowedGlobs` (glob pattern matches)
768
- 6. `allowedPrefix` (prefix matches)
769
- 7. `blockedPrefix` (prefix matches)
771
+ 6. `blockedPrefix` (prefix matches)
772
+ 7. `allowedPrefix` (prefix matches)
770
773
  8. `blockedZones` (zone classification)
771
774
 
772
775
  </details>
package/dist/cli/index.js CHANGED
@@ -18296,7 +18296,7 @@ ${warnings.map((w) => ` - ${w}`).join(`
18296
18296
  `)}` : "";
18297
18297
  const cautionMessage = `
18298
18298
 
18299
- \u26A0\uFE0F Caution: Spec drift was acknowledged \u2014 verify that the implementation still matches the spec before proceeding.`;
18299
+ \u26A0\uFE0F Warning: Spec drift was acknowledged \u2014 verify that the implementation still matches the spec before proceeding.`;
18300
18300
  return baseMessage + warningMessage + cautionMessage;
18301
18301
  }
18302
18302
 
@@ -18564,7 +18564,8 @@ var AGENT_TOOL_MAP = {
18564
18564
  "imports",
18565
18565
  "retrieve_summary",
18566
18566
  "symbols",
18567
- "knowledge_recall"
18567
+ "knowledge_recall",
18568
+ "req_coverage"
18568
18569
  ],
18569
18570
  critic_drift_verifier: [
18570
18571
  "complexity_hotspots",
@@ -18968,7 +18969,8 @@ var GuardrailsConfigSchema = exports_external.object({
18968
18969
  ]),
18969
18970
  require_reviewer_test_engineer: exports_external.boolean().default(true)
18970
18971
  }).optional(),
18971
- profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
18972
+ profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional(),
18973
+ block_destructive_commands: exports_external.boolean().default(true)
18972
18974
  });
18973
18975
  var WatchdogConfigSchema = exports_external.object({
18974
18976
  scope_guard: exports_external.boolean().default(true),
@@ -32067,7 +32069,7 @@ var DeltaSpecSchema = exports_external.union([
32067
32069
  ]);
32068
32070
  // src/tools/create-tool.ts
32069
32071
  function classifyToolError(error93) {
32070
- const msg = (error93 instanceof Error ? error93.message : String(error93)).toLowerCase();
32072
+ const msg = (error93 instanceof Error ? error93.message ?? "" : String(error93)).toLowerCase();
32071
32073
  if (msg.includes("not registered") || msg.includes("unknown tool"))
32072
32074
  return "not_registered";
32073
32075
  if (msg.includes("not whitelisted") || msg.includes("not allowed"))
@@ -36802,6 +36804,10 @@ async function discoverBuildCommands(workingDir, options) {
36802
36804
  const skipped = [...profileSkipped];
36803
36805
  for (const ecosystem of ECOSYSTEMS) {
36804
36806
  if (coveredEcosystems.has(ecosystem.ecosystem)) {
36807
+ skipped.push({
36808
+ ecosystem: ecosystem.ecosystem,
36809
+ reason: `Covered by profile detection`
36810
+ });
36805
36811
  continue;
36806
36812
  }
36807
36813
  if (!checkToolchain(ecosystem.toolchainCommands)) {
@@ -330,6 +330,7 @@ export declare const GuardrailsConfigSchema: z.ZodObject<{
330
330
  warning_threshold: z.ZodOptional<z.ZodNumber>;
331
331
  idle_timeout_minutes: z.ZodOptional<z.ZodNumber>;
332
332
  }, z.core.$strip>>>;
333
+ block_destructive_commands: z.ZodDefault<z.ZodBoolean>;
333
334
  }, z.core.$strip>;
334
335
  export type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;
335
336
  export declare const WatchdogConfigSchema: z.ZodObject<{
@@ -655,6 +656,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
655
656
  warning_threshold: z.ZodOptional<z.ZodNumber>;
656
657
  idle_timeout_minutes: z.ZodOptional<z.ZodNumber>;
657
658
  }, z.core.$strip>>>;
659
+ block_destructive_commands: z.ZodDefault<z.ZodBoolean>;
658
660
  }, z.core.$strip>>;
659
661
  watchdog: z.ZodOptional<z.ZodObject<{
660
662
  scope_guard: z.ZodDefault<z.ZodBoolean>;
@@ -103,6 +103,11 @@ export declare function validateAndRecordAttestation(dir: string, findingId: str
103
103
  valid: false;
104
104
  reason: string;
105
105
  }>;
106
+ /**
107
+ * Clears all guardrails caches.
108
+ * Use this for test isolation or when guardrails config reloads at runtime.
109
+ */
110
+ export declare function clearGuardrailsCaches(): void;
106
111
  type AgentRule = {
107
112
  readOnly?: boolean;
108
113
  blockedExact?: string[];
package/dist/index.js CHANGED
@@ -297,7 +297,8 @@ var init_constants = __esm(() => {
297
297
  "imports",
298
298
  "retrieve_summary",
299
299
  "symbols",
300
- "knowledge_recall"
300
+ "knowledge_recall",
301
+ "req_coverage"
301
302
  ],
302
303
  critic_drift_verifier: [
303
304
  "complexity_hotspots",
@@ -14990,7 +14991,8 @@ var init_schema = __esm(() => {
14990
14991
  ]),
14991
14992
  require_reviewer_test_engineer: exports_external.boolean().default(true)
14992
14993
  }).optional(),
14993
- profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
14994
+ profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional(),
14995
+ block_destructive_commands: exports_external.boolean().default(true)
14994
14996
  });
14995
14997
  WatchdogConfigSchema = exports_external.object({
14996
14998
  scope_guard: exports_external.boolean().default(true),
@@ -30654,7 +30656,7 @@ var init_dist = __esm(() => {
30654
30656
 
30655
30657
  // src/tools/create-tool.ts
30656
30658
  function classifyToolError(error93) {
30657
- const msg = (error93 instanceof Error ? error93.message : String(error93)).toLowerCase();
30659
+ const msg = (error93 instanceof Error ? error93.message ?? "" : String(error93)).toLowerCase();
30658
30660
  if (msg.includes("not registered") || msg.includes("unknown tool"))
30659
30661
  return "not_registered";
30660
30662
  if (msg.includes("not whitelisted") || msg.includes("not allowed"))
@@ -34920,6 +34922,10 @@ async function discoverBuildCommands(workingDir, options) {
34920
34922
  const skipped = [...profileSkipped];
34921
34923
  for (const ecosystem of ECOSYSTEMS) {
34922
34924
  if (coveredEcosystems.has(ecosystem.ecosystem)) {
34925
+ skipped.push({
34926
+ ecosystem: ecosystem.ecosystem,
34927
+ reason: `Covered by profile detection`
34928
+ });
34923
34929
  continue;
34924
34930
  }
34925
34931
  if (!checkToolchain(ecosystem.toolchainCommands)) {
@@ -41619,6 +41625,98 @@ var init_doc_scan = __esm(() => {
41619
41625
  });
41620
41626
  });
41621
41627
 
41628
+ // src/tools/knowledge-recall.ts
41629
+ var exports_knowledge_recall = {};
41630
+ __export(exports_knowledge_recall, {
41631
+ knowledge_recall: () => knowledge_recall
41632
+ });
41633
+ var knowledge_recall;
41634
+ var init_knowledge_recall = __esm(() => {
41635
+ init_dist();
41636
+ init_knowledge_store();
41637
+ init_create_tool();
41638
+ knowledge_recall = createSwarmTool({
41639
+ description: "Search the knowledge base for relevant past decisions, patterns, and lessons learned. Returns ranked results by semantic similarity.",
41640
+ args: {
41641
+ query: tool.schema.string().min(3).describe("Natural language search query"),
41642
+ top_n: tool.schema.number().int().min(1).max(20).optional().describe("Maximum results to return (default: 5)"),
41643
+ tier: tool.schema.enum(["all", "swarm", "hive"]).optional().describe("Knowledge tier to search (default: 'all')")
41644
+ },
41645
+ execute: async (args2, directory) => {
41646
+ let queryInput;
41647
+ let topNInput;
41648
+ let tierInput;
41649
+ try {
41650
+ if (args2 && typeof args2 === "object") {
41651
+ const obj = args2;
41652
+ queryInput = obj.query;
41653
+ topNInput = obj.top_n;
41654
+ tierInput = obj.tier;
41655
+ }
41656
+ } catch {}
41657
+ if (typeof queryInput !== "string" || queryInput.length < 3) {
41658
+ return JSON.stringify({
41659
+ results: [],
41660
+ total: 0,
41661
+ error: "query must be a string with at least 3 characters"
41662
+ });
41663
+ }
41664
+ let topN = 5;
41665
+ if (topNInput !== undefined) {
41666
+ if (typeof topNInput === "number" && Number.isInteger(topNInput)) {
41667
+ topN = Math.max(1, Math.min(20, topNInput));
41668
+ }
41669
+ }
41670
+ let tier = "all";
41671
+ if (tierInput !== undefined && typeof tierInput === "string") {
41672
+ if (tierInput === "swarm" || tierInput === "hive") {
41673
+ tier = tierInput;
41674
+ }
41675
+ }
41676
+ const swarmPath = resolveSwarmKnowledgePath(directory);
41677
+ const hivePath = resolveHiveKnowledgePath();
41678
+ const [swarmEntries, hiveEntries] = await Promise.all([
41679
+ readKnowledge(swarmPath),
41680
+ readKnowledge(hivePath)
41681
+ ]);
41682
+ let entries = [];
41683
+ if (tier === "all" || tier === "swarm") {
41684
+ entries = entries.concat(swarmEntries);
41685
+ }
41686
+ if (tier === "all" || tier === "hive") {
41687
+ entries = entries.concat(hiveEntries);
41688
+ }
41689
+ if (entries.length === 0) {
41690
+ const result2 = { results: [], total: 0 };
41691
+ return JSON.stringify(result2);
41692
+ }
41693
+ const normalizedQuery = normalize2(queryInput);
41694
+ const queryBigrams = wordBigrams(normalizedQuery);
41695
+ const scoredEntries = entries.map((entry) => {
41696
+ const entryText = `${entry.lesson} ${entry.tags.join(" ")} ${entry.category}`;
41697
+ const entryBigrams = wordBigrams(entryText);
41698
+ const textScore = jaccardBigram(queryBigrams, entryBigrams);
41699
+ const boost = entry.status === "established" ? 0.1 : entry.status === "promoted" ? 0.05 : 0;
41700
+ const finalScore = textScore + boost;
41701
+ return {
41702
+ id: entry.id,
41703
+ confidence: entry.confidence,
41704
+ category: entry.category,
41705
+ lesson: entry.lesson,
41706
+ score: finalScore
41707
+ };
41708
+ });
41709
+ scoredEntries.sort((a, b) => b.score - a.score);
41710
+ const topResults = scoredEntries.slice(0, topN);
41711
+ const result = {
41712
+ results: topResults,
41713
+ total: topResults.length
41714
+ };
41715
+ return JSON.stringify(result);
41716
+ }
41717
+ });
41718
+ });
41719
+
41622
41720
  // src/environment/prompt-renderer.ts
41623
41721
  var exports_prompt_renderer = {};
41624
41722
  __export(exports_prompt_renderer, {
@@ -45263,7 +45361,7 @@ ${warnings.map((w) => ` - ${w}`).join(`
45263
45361
  `)}` : "";
45264
45362
  const cautionMessage = `
45265
45363
 
45266
- \u26A0\uFE0F Caution: Spec drift was acknowledged \u2014 verify that the implementation still matches the spec before proceeding.`;
45364
+ \u26A0\uFE0F Warning: Spec drift was acknowledged \u2014 verify that the implementation still matches the spec before proceeding.`;
45267
45365
  return baseMessage + warningMessage + cautionMessage;
45268
45366
  }
45269
45367
 
@@ -48051,42 +48149,73 @@ init_utils2();
48051
48149
  var DEFAULT_CURATOR_LLM_TIMEOUT_MS = 300000;
48052
48150
  function parseKnowledgeRecommendations(llmOutput) {
48053
48151
  const recommendations = [];
48054
- const section = llmOutput.match(/OBSERVATIONS:\s*\n([\s\S]*?)(?:\n\n|\n[A-Z_]+:|$)/);
48055
- if (!section)
48056
- return recommendations;
48057
- const lines = section[1].split(`
48152
+ const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
48153
+ const obsSection = llmOutput.match(/OBSERVATIONS:\s*\n([\s\S]*?)(?:\n\n|\n[A-Z_]+:|$)/);
48154
+ if (obsSection) {
48155
+ const lines = obsSection[1].split(`
48058
48156
  `);
48059
- for (const line of lines) {
48060
- const trimmed = line.trim();
48061
- if (!trimmed.startsWith("-"))
48062
- continue;
48063
- const match = trimmed.match(/^-\s+entry\s+(\S+)\s+\(([^)]+)\):\s+(.+)$/i);
48064
- if (!match)
48065
- continue;
48066
- const uuid8 = match[1];
48067
- const parenthetical = match[2];
48068
- const text = match[3].trim().replace(/\s+\([^)]+\)$/, "");
48069
- const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
48070
- const entryId = uuid8 === "new" || !UUID_V4.test(uuid8) ? undefined : uuid8;
48071
- let action = "rewrite";
48072
- const lowerParenthetical = parenthetical.toLowerCase();
48073
- if (lowerParenthetical.includes("suggests boost confidence") || lowerParenthetical.includes("mark hive_eligible") || lowerParenthetical.includes("appears high-confidence")) {
48074
- action = "promote";
48075
- } else if (lowerParenthetical.includes("suggests archive") || lowerParenthetical.includes("appears stale")) {
48076
- action = "archive";
48077
- } else if (lowerParenthetical.includes("contradicts project state")) {
48078
- action = "flag_contradiction";
48079
- } else if (lowerParenthetical.includes("suggests rewrite") || lowerParenthetical.includes("could be tighter")) {
48080
- action = "rewrite";
48081
- } else if (lowerParenthetical.includes("new candidate")) {
48082
- action = "promote";
48083
- }
48084
- recommendations.push({
48085
- action,
48086
- entry_id: entryId,
48087
- lesson: text,
48088
- reason: text
48089
- });
48157
+ for (const line of lines) {
48158
+ const trimmed = line.trim();
48159
+ if (!trimmed.startsWith("-"))
48160
+ continue;
48161
+ const match = trimmed.match(/^-\s+entry\s+(\S+)\s+\(([^)]+)\):\s+(.+)$/i);
48162
+ if (!match)
48163
+ continue;
48164
+ const uuid8 = match[1];
48165
+ const parenthetical = match[2];
48166
+ const text = match[3].trim().replace(/\s+\([^)]+\)$/, "");
48167
+ const entryId = uuid8 === "new" || !UUID_V4.test(uuid8) ? undefined : uuid8;
48168
+ let action = "rewrite";
48169
+ const lowerParenthetical = parenthetical.toLowerCase();
48170
+ if (lowerParenthetical.includes("suggests boost confidence") || lowerParenthetical.includes("mark hive_eligible") || lowerParenthetical.includes("appears high-confidence")) {
48171
+ action = "promote";
48172
+ } else if (lowerParenthetical.includes("suggests archive") || lowerParenthetical.includes("appears stale")) {
48173
+ action = "archive";
48174
+ } else if (lowerParenthetical.includes("contradicts project state")) {
48175
+ action = "flag_contradiction";
48176
+ } else if (lowerParenthetical.includes("suggests rewrite") || lowerParenthetical.includes("could be tighter")) {
48177
+ action = "rewrite";
48178
+ } else if (lowerParenthetical.includes("new candidate")) {
48179
+ action = "promote";
48180
+ }
48181
+ recommendations.push({
48182
+ action,
48183
+ entry_id: entryId,
48184
+ lesson: text,
48185
+ reason: text
48186
+ });
48187
+ }
48188
+ }
48189
+ const updatesSection = llmOutput.match(/KNOWLEDGE_UPDATES:\s*\n([\s\S]*?)(?:\n\n|\n[A-Z_]+:|$)/);
48190
+ if (updatesSection) {
48191
+ const validActions = new Set([
48192
+ "promote",
48193
+ "archive",
48194
+ "rewrite",
48195
+ "flag_contradiction"
48196
+ ]);
48197
+ const lines = updatesSection[1].split(`
48198
+ `);
48199
+ for (const line of lines) {
48200
+ const trimmed = line.trim();
48201
+ if (!trimmed.startsWith("-"))
48202
+ continue;
48203
+ const match = trimmed.match(/^-\s+(\S+)\s+(\S+):\s+(.+)$/);
48204
+ if (!match)
48205
+ continue;
48206
+ const action = match[1].toLowerCase();
48207
+ if (!validActions.has(action))
48208
+ continue;
48209
+ const id = match[2];
48210
+ const text = match[3].trim();
48211
+ const entryId = UUID_V4.test(id) ? id : undefined;
48212
+ recommendations.push({
48213
+ action,
48214
+ entry_id: entryId,
48215
+ lesson: text,
48216
+ reason: text
48217
+ });
48218
+ }
48090
48219
  }
48091
48220
  return recommendations;
48092
48221
  }
@@ -56198,7 +56327,7 @@ class PlanSyncWorker {
56198
56327
  const planJsonPath = path36.join(swarmDir, "plan.json");
56199
56328
  const markerPath = path36.join(swarmDir, ".plan-write-marker");
56200
56329
  const planStats = fs24.statSync(planJsonPath);
56201
- const planMtimeMs = planStats.mtimeMs;
56330
+ const planMtimeMs = Math.floor(planStats.mtimeMs);
56202
56331
  const markerContent = fs24.readFileSync(markerPath, "utf8");
56203
56332
  const marker = JSON.parse(markerContent);
56204
56333
  const markerTimestampMs = new Date(marker.timestamp).getTime();
@@ -57676,6 +57805,54 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityC
57676
57805
  "pre_check_batch"
57677
57806
  ];
57678
57807
  const requireReviewerAndTestEngineer = cfg.qa_gates?.require_reviewer_test_engineer ?? true;
57808
+ function checkDestructiveCommand(tool3, args2) {
57809
+ if (tool3 !== "bash" && tool3 !== "shell")
57810
+ return;
57811
+ if (cfg.block_destructive_commands === false)
57812
+ return;
57813
+ const toolArgs = args2;
57814
+ const command = typeof toolArgs?.command === "string" ? toolArgs.command.trim() : "";
57815
+ if (!command)
57816
+ return;
57817
+ if (/:\s*\(\s*\)\s*\{[^}]*\|[^}]*:/.test(command)) {
57818
+ throw new Error(`BLOCKED: Potentially destructive shell command detected: fork bomb pattern`);
57819
+ }
57820
+ const rmFlagPattern = /^rm\s+(-r\s+-f|-f\s+-r|-rf|-fr)\s+(.+)$/;
57821
+ const rmMatch = rmFlagPattern.exec(command);
57822
+ if (rmMatch) {
57823
+ const targetPart = rmMatch[2].trim();
57824
+ const targets = targetPart.split(/\s+/);
57825
+ const safeTargets = /^(node_modules|\.git)$/;
57826
+ const allSafe = targets.every((t) => safeTargets.test(t));
57827
+ if (!allSafe) {
57828
+ throw new Error(`BLOCKED: Potentially destructive shell command: rm -rf on unsafe path(s): ${targetPart}`);
57829
+ }
57830
+ }
57831
+ if (/^git\s+push\b.*?(--force|-f)\b/.test(command)) {
57832
+ throw new Error(`BLOCKED: Force push detected \u2014 git push --force is not allowed`);
57833
+ }
57834
+ if (/^git\s+reset\s+--hard/.test(command)) {
57835
+ throw new Error(`BLOCKED: "git reset --hard" detected \u2014 use --soft or --mixed with caution`);
57836
+ }
57837
+ if (/^git\s+reset\s+--mixed\s+\S+/.test(command)) {
57838
+ throw new Error(`BLOCKED: "git reset --mixed" with a target branch/commit is not allowed`);
57839
+ }
57840
+ if (/^kubectl\s+delete\b/.test(command)) {
57841
+ throw new Error(`BLOCKED: "kubectl delete" detected \u2014 destructive cluster operation`);
57842
+ }
57843
+ if (/^docker\s+system\s+prune\b/.test(command)) {
57844
+ throw new Error(`BLOCKED: "docker system prune" detected \u2014 destructive container operation`);
57845
+ }
57846
+ if (/^\s*DROP\s+(TABLE|DATABASE|SCHEMA)\b/i.test(command)) {
57847
+ throw new Error(`BLOCKED: SQL DROP command detected \u2014 destructive database operation`);
57848
+ }
57849
+ if (/^\s*TRUNCATE\s+TABLE\b/i.test(command)) {
57850
+ throw new Error(`BLOCKED: SQL TRUNCATE command detected \u2014 destructive database operation`);
57851
+ }
57852
+ if (/^mkfs[./]/.test(command)) {
57853
+ throw new Error(`BLOCKED: Disk format command (mkfs) detected \u2014 disk formatting operation`);
57854
+ }
57855
+ }
57679
57856
  async function checkGateLimits(params) {
57680
57857
  const { sessionID, window: window2, agentConfig, elapsedMinutes, repetitionCount } = params;
57681
57858
  if (agentConfig.max_tool_calls > 0 && window2.toolCalls >= agentConfig.max_tool_calls) {
@@ -58025,6 +58202,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityC
58025
58202
  handleDelegatedWriteTracking(input.sessionID, input.tool, output.args);
58026
58203
  handleLoopDetection(input.sessionID, input.tool, output.args);
58027
58204
  handleTestSuiteBlocking(input.tool, output.args);
58205
+ checkDestructiveCommand(input.tool, output.args);
58028
58206
  if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
58029
58207
  handlePlanAndScopeProtection(input.sessionID, input.tool, output.args);
58030
58208
  const toolArgs = output.args;
@@ -58205,18 +58383,18 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityC
58205
58383
  const fallbackModels = swarmAgents?.[baseAgentName]?.fallback_models;
58206
58384
  session.modelFallbackExhausted = !fallbackModels || session.model_fallback_index > fallbackModels.length;
58207
58385
  const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, swarmAgents);
58386
+ const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
58208
58387
  if (fallbackModel) {
58209
- const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
58210
58388
  if (swarmAgents?.[baseAgentName]) {
58211
58389
  swarmAgents[baseAgentName].model = fallbackModel;
58212
58390
  }
58213
- telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel, "transient_model_error");
58214
58391
  session.pendingAdvisoryMessages ??= [];
58215
58392
  session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
58216
58393
  } else {
58217
58394
  session.pendingAdvisoryMessages ??= [];
58218
58395
  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.`);
58219
58396
  }
58397
+ telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel ?? "none", "transient_model_error");
58220
58398
  swarmState.pendingEvents++;
58221
58399
  }
58222
58400
  }
@@ -58589,17 +58767,22 @@ function normalizePathWithCache(filePath, cwd) {
58589
58767
  return fallback;
58590
58768
  }
58591
58769
  }
58592
- function getGlobMatcher(pattern) {
58770
+ function getGlobMatcher(pattern, caseInsensitive = process.platform === "win32" || process.platform === "darwin") {
58593
58771
  const cached3 = globMatcherCache.get(pattern);
58594
58772
  if (cached3 !== undefined) {
58595
58773
  return cached3;
58596
58774
  }
58597
- const matcher = import_picomatch.default(pattern, {
58598
- dot: true,
58599
- nocase: process.platform === "win32"
58600
- });
58601
- globMatcherCache.set(pattern, matcher);
58602
- return matcher;
58775
+ try {
58776
+ const matcher = import_picomatch.default(pattern, {
58777
+ dot: true,
58778
+ nocase: caseInsensitive
58779
+ });
58780
+ globMatcherCache.set(pattern, matcher);
58781
+ return matcher;
58782
+ } catch (err2) {
58783
+ warn(`picomatch error for pattern "${pattern}": ${err2}`);
58784
+ return () => false;
58785
+ }
58603
58786
  }
58604
58787
  var DEFAULT_AGENT_AUTHORITY_RULES = {
58605
58788
  architect: {
@@ -58677,7 +58860,7 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules) {
58677
58860
  const dir = cwd || process.cwd();
58678
58861
  let normalizedPath;
58679
58862
  try {
58680
- const normalizedWithSymlinks = normalizePathWithCache(filePath, cwd);
58863
+ const normalizedWithSymlinks = normalizePathWithCache(filePath, dir);
58681
58864
  const resolved = path38.resolve(dir, normalizedWithSymlinks);
58682
58865
  normalizedPath = path38.relative(dir, resolved).replace(/\\/g, "/");
58683
58866
  } catch {
@@ -58730,6 +58913,16 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules) {
58730
58913
  return { allowed: true };
58731
58914
  }
58732
58915
  }
58916
+ if (rules.blockedPrefix && rules.blockedPrefix.length > 0) {
58917
+ for (const prefix of rules.blockedPrefix) {
58918
+ if (normalizedPath.startsWith(prefix)) {
58919
+ return {
58920
+ allowed: false,
58921
+ reason: `Path blocked: ${normalizedPath} is under ${prefix}`
58922
+ };
58923
+ }
58924
+ }
58925
+ }
58733
58926
  if (rules.allowedPrefix != null && rules.allowedPrefix.length > 0) {
58734
58927
  const isAllowed = rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix));
58735
58928
  if (!isAllowed) {
@@ -58744,16 +58937,6 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules) {
58744
58937
  reason: `Path ${normalizedPath} not in allowed list for ${normalizedAgent}`
58745
58938
  };
58746
58939
  }
58747
- if (rules.blockedPrefix && rules.blockedPrefix.length > 0) {
58748
- for (const prefix of rules.blockedPrefix) {
58749
- if (normalizedPath.startsWith(prefix)) {
58750
- return {
58751
- allowed: false,
58752
- reason: `Path blocked: ${normalizedPath} is under ${prefix}`
58753
- };
58754
- }
58755
- }
58756
- }
58757
58940
  if (rules.blockedZones && rules.blockedZones.length > 0) {
58758
58941
  const { zone } = classifyFile(normalizedPath);
58759
58942
  if (rules.blockedZones.includes(zone)) {
@@ -59526,7 +59709,9 @@ var END_OF_SENTENCE_QUESTION_PATTERN = /\?\s*$/;
59526
59709
  var PHASE_COMPLETION_PATTERNS = [
59527
59710
  /Ready for Phase (?:\d+|\[?N\+1\]?)\??/i,
59528
59711
  /phase.{0,20}(?:complete|finish|done|wrap)/i,
59529
- /move(?:d?)?\s+(?:on\s+)?to\s+(?:the\s+)?(?:next\s+)?phase/i
59712
+ /move(?:d?)?\s+(?:on\s+)?to\s+(?:the\s+)?(?:next\s+)?phase/i,
59713
+ /(?:proceed|move)\s+to\s+the\s+next\s+phase/i,
59714
+ /what would you like.{0,20}(?:next|do next)/i
59530
59715
  ];
59531
59716
  var QUESTION_ESCALATION_PATTERNS = [
59532
59717
  /escalat/i,
@@ -61431,6 +61616,47 @@ ${handoffBlock}`);
61431
61616
  }
61432
61617
  } catch {}
61433
61618
  }
61619
+ if (baseRole === "coder") {
61620
+ const sessionId_ccp = _input.sessionID ?? "";
61621
+ const ccpSession = swarmState.agentSessions.get(sessionId_ccp);
61622
+ try {
61623
+ const coderScope = ccpSession?.declaredCoderScope;
61624
+ const primaryFile = coderScope?.[0] ?? "";
61625
+ if (primaryFile.length > 0) {
61626
+ const { knowledge_recall: knowledge_recall2 } = await Promise.resolve().then(() => (init_knowledge_recall(), exports_knowledge_recall));
61627
+ const rawResult = await knowledge_recall2.execute({ query: primaryFile }, { directory });
61628
+ if (rawResult && typeof rawResult === "string") {
61629
+ const parsed = JSON.parse(rawResult);
61630
+ if (parsed.results.length > 0) {
61631
+ const lines = parsed.results.map((r) => {
61632
+ const lesson = r.lesson.length > 200 ? `${r.lesson.slice(0, 200)}...` : r.lesson;
61633
+ return `- [${r.category}] ${lesson}`;
61634
+ });
61635
+ tryInject(`## CONTEXT FROM KNOWLEDGE BASE
61636
+ ${lines.join(`
61637
+ `)}`);
61638
+ }
61639
+ }
61640
+ }
61641
+ } catch {}
61642
+ try {
61643
+ const taskId_ccp = ccpSession?.currentTaskId;
61644
+ if (taskId_ccp && !taskId_ccp.includes("..") && !taskId_ccp.includes("/") && !taskId_ccp.includes("\\") && !taskId_ccp.includes("\x00")) {
61645
+ const evidencePath = path47.join(directory, ".swarm", "evidence", `${taskId_ccp}.json`);
61646
+ if (fs35.existsSync(evidencePath)) {
61647
+ const evidenceContent = fs35.readFileSync(evidencePath, "utf-8");
61648
+ const evidenceData = JSON.parse(evidenceContent);
61649
+ const rejections = (evidenceData.bundle?.entries ?? []).filter((e) => e.type === "gate" && e.gate_type === "reviewer" && e.verdict === "reject");
61650
+ if (rejections.length > 0) {
61651
+ const lines = rejections.map((r) => `- ${r.reason ?? "No reason provided"}`);
61652
+ tryInject(`## PRIOR REJECTIONS
61653
+ ${lines.join(`
61654
+ `)}`);
61655
+ }
61656
+ }
61657
+ }
61658
+ } catch {}
61659
+ }
61434
61660
  if (baseRole === "coder") {
61435
61661
  const taskText_lang_a = plan2 && plan2.migration_status !== "migration_failed" ? extractCurrentTaskFromPlan(plan2) : null;
61436
61662
  const langConstraints_a = buildLanguageCoderConstraints(taskText_lang_a);
@@ -67760,90 +67986,10 @@ var knowledge_query = createSwarmTool({
67760
67986
  `);
67761
67987
  }
67762
67988
  });
67763
- // src/tools/knowledge-recall.ts
67764
- init_dist();
67765
- init_knowledge_store();
67766
- init_create_tool();
67767
- var knowledge_recall = createSwarmTool({
67768
- description: "Search the knowledge base for relevant past decisions, patterns, and lessons learned. Returns ranked results by semantic similarity.",
67769
- args: {
67770
- query: tool.schema.string().min(3).describe("Natural language search query"),
67771
- top_n: tool.schema.number().int().min(1).max(20).optional().describe("Maximum results to return (default: 5)"),
67772
- tier: tool.schema.enum(["all", "swarm", "hive"]).optional().describe("Knowledge tier to search (default: 'all')")
67773
- },
67774
- execute: async (args2, directory) => {
67775
- let queryInput;
67776
- let topNInput;
67777
- let tierInput;
67778
- try {
67779
- if (args2 && typeof args2 === "object") {
67780
- const obj = args2;
67781
- queryInput = obj.query;
67782
- topNInput = obj.top_n;
67783
- tierInput = obj.tier;
67784
- }
67785
- } catch {}
67786
- if (typeof queryInput !== "string" || queryInput.length < 3) {
67787
- return JSON.stringify({
67788
- results: [],
67789
- total: 0,
67790
- error: "query must be a string with at least 3 characters"
67791
- });
67792
- }
67793
- let topN = 5;
67794
- if (topNInput !== undefined) {
67795
- if (typeof topNInput === "number" && Number.isInteger(topNInput)) {
67796
- topN = Math.max(1, Math.min(20, topNInput));
67797
- }
67798
- }
67799
- let tier = "all";
67800
- if (tierInput !== undefined && typeof tierInput === "string") {
67801
- if (tierInput === "swarm" || tierInput === "hive") {
67802
- tier = tierInput;
67803
- }
67804
- }
67805
- const swarmPath = resolveSwarmKnowledgePath(directory);
67806
- const hivePath = resolveHiveKnowledgePath();
67807
- const [swarmEntries, hiveEntries] = await Promise.all([
67808
- readKnowledge(swarmPath),
67809
- readKnowledge(hivePath)
67810
- ]);
67811
- let entries = [];
67812
- if (tier === "all" || tier === "swarm") {
67813
- entries = entries.concat(swarmEntries);
67814
- }
67815
- if (tier === "all" || tier === "hive") {
67816
- entries = entries.concat(hiveEntries);
67817
- }
67818
- if (entries.length === 0) {
67819
- const result2 = { results: [], total: 0 };
67820
- return JSON.stringify(result2);
67821
- }
67822
- const normalizedQuery = normalize2(queryInput);
67823
- const queryBigrams = wordBigrams(normalizedQuery);
67824
- const scoredEntries = entries.map((entry) => {
67825
- const entryText = `${entry.lesson} ${entry.tags.join(" ")} ${entry.category}`;
67826
- const entryBigrams = wordBigrams(entryText);
67827
- const textScore = jaccardBigram(queryBigrams, entryBigrams);
67828
- const boost = entry.status === "established" ? 0.1 : entry.status === "promoted" ? 0.05 : 0;
67829
- const finalScore = textScore + boost;
67830
- return {
67831
- id: entry.id,
67832
- confidence: entry.confidence,
67833
- category: entry.category,
67834
- lesson: entry.lesson,
67835
- score: finalScore
67836
- };
67837
- });
67838
- scoredEntries.sort((a, b) => b.score - a.score);
67839
- const topResults = scoredEntries.slice(0, topN);
67840
- const result = {
67841
- results: topResults,
67842
- total: topResults.length
67843
- };
67844
- return JSON.stringify(result);
67845
- }
67846
- });
67989
+
67990
+ // src/tools/index.ts
67991
+ init_knowledge_recall();
67992
+
67847
67993
  // src/tools/knowledge-remove.ts
67848
67994
  init_dist();
67849
67995
  init_knowledge_store();
@@ -76173,7 +76319,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
76173
76319
  message: "Invalid working_directory: null bytes are not allowed"
76174
76320
  };
76175
76321
  }
76176
- if (process.platform === "win32") {
76322
+ {
76177
76323
  const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
76178
76324
  if (devicePathPattern.test(args2.working_directory)) {
76179
76325
  return {
@@ -76329,7 +76475,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
76329
76475
  errors: [error93 instanceof Error ? error93.message : String(error93)]
76330
76476
  };
76331
76477
  } finally {
76332
- if (lockResult && lockResult.acquired && lockResult.lock._release) {
76478
+ if (lockResult?.acquired && lockResult.lock._release) {
76333
76479
  try {
76334
76480
  await lockResult.lock._release();
76335
76481
  } catch (releaseError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.59.0",
3
+ "version": "6.60.1",
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",