opencode-swarm 7.98.0 → 7.98.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.
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-wmm21nsk.js";
5
- import"./index-pc10e4d7.js";
4
+ } from "./index-0rgde8qc.js";
5
+ import"./index-zgba613y.js";
6
6
  import"./index-5z2e78tv.js";
7
7
  import"./index-2a6ppa65.js";
8
8
  import"./index-fjxjb66n.js";
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-pc10e4d7.js";
15
+ } from "./index-zgba613y.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-wmm21nsk.js";
4
+ } from "./index-0rgde8qc.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-gnd1280x.js";
@@ -78,7 +78,7 @@ import {
78
78
  handleWriteRetroCommand,
79
79
  normalizeSwarmCommandInput,
80
80
  resolveCommand
81
- } from "./index-pc10e4d7.js";
81
+ } from "./index-zgba613y.js";
82
82
  import"./index-5z2e78tv.js";
83
83
  import"./index-2a6ppa65.js";
84
84
  import"./index-fjxjb66n.js";
@@ -909,7 +909,7 @@ var init_executor = __esm(() => {
909
909
  // package.json
910
910
  var package_default = {
911
911
  name: "opencode-swarm",
912
- version: "7.98.0",
912
+ version: "7.98.1",
913
913
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
914
914
  main: "dist/index.js",
915
915
  types: "dist/index.d.ts",
@@ -10193,48 +10193,49 @@ function recordSeenRetroSection(key, value, timestamp) {
10193
10193
  function hashContent(content) {
10194
10194
  return createHash3("sha1").update(content).digest("hex");
10195
10195
  }
10196
- function isWriteToSwarmPlan(input) {
10197
- if (typeof input !== "object" || input === null)
10198
- return false;
10199
- const record = input;
10200
- const toolName = record.toolName;
10201
- if (typeof toolName !== "string")
10202
- return false;
10203
- if (!["write", "edit", "apply_patch", "swarm_apply_patch"].includes(toolName))
10204
- return false;
10205
- const rawPath = record.path;
10206
- const rawFile = record.file;
10207
- const pathField = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : undefined;
10208
- const fileField = typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : undefined;
10209
- if (typeof pathField === "string" && pathField.includes(".swarm/plan.md")) {
10210
- return true;
10211
- }
10212
- if (typeof fileField === "string" && fileField.includes(".swarm/plan.md")) {
10213
- return true;
10214
- }
10215
- return false;
10216
- }
10217
10196
  function isWriteToEvidenceFile(input) {
10218
- if (typeof input !== "object" || input === null)
10219
- return false;
10220
- const record = input;
10221
- const toolName = record.toolName;
10222
- if (typeof toolName !== "string")
10223
- return false;
10224
- if (!["write", "edit", "apply_patch", "swarm_apply_patch"].includes(toolName))
10197
+ const trigger = normalizeWriteTrigger(input);
10198
+ return isEvidencePath(trigger?.filePath);
10199
+ }
10200
+ var WRITE_TOOLS = new Set([
10201
+ "write",
10202
+ "edit",
10203
+ "apply_patch",
10204
+ "swarm_apply_patch"
10205
+ ]);
10206
+ function isRecord(value) {
10207
+ return typeof value === "object" && value !== null;
10208
+ }
10209
+ function normalizePathField(value) {
10210
+ return typeof value === "string" ? value.replace(/\\/g, "/") : null;
10211
+ }
10212
+ function firstPathFromRecord(record) {
10213
+ return normalizePathField(record.path) ?? normalizePathField(record.filePath) ?? normalizePathField(record.file);
10214
+ }
10215
+ function normalizeWriteTrigger(input, output) {
10216
+ if (!isRecord(input))
10217
+ return null;
10218
+ const toolName = typeof input.toolName === "string" ? input.toolName : typeof input.tool === "string" ? input.tool : null;
10219
+ if (!toolName || !WRITE_TOOLS.has(toolName))
10220
+ return null;
10221
+ const inputArgs = isRecord(input.args) ? input.args : null;
10222
+ const outputArgs = isRecord(output) && isRecord(output.args) ? output.args : null;
10223
+ const filePath = firstPathFromRecord(input) ?? (inputArgs ? firstPathFromRecord(inputArgs) : null) ?? (outputArgs ? firstPathFromRecord(outputArgs) : null);
10224
+ if (!filePath)
10225
+ return null;
10226
+ return {
10227
+ toolName,
10228
+ filePath,
10229
+ sessionID: typeof input.sessionID === "string" ? input.sessionID : "default"
10230
+ };
10231
+ }
10232
+ function isEvidencePath(filePath) {
10233
+ if (!filePath)
10225
10234
  return false;
10226
- const rawPath = record.path;
10227
- const rawFile = record.file;
10228
- const pathField = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : undefined;
10229
- const fileField = typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : undefined;
10230
- const evidenceRegex = /\.swarm\/+evidence\/+/i;
10231
- if (typeof pathField === "string" && evidenceRegex.test(pathField)) {
10232
- return true;
10233
- }
10234
- if (typeof fileField === "string" && evidenceRegex.test(fileField)) {
10235
- return true;
10236
- }
10237
- return false;
10235
+ return /(?:^|\/)\.swarm\/+evidence\//i.test(filePath);
10236
+ }
10237
+ function isPlanPath(filePath) {
10238
+ return filePath?.includes(".swarm/plan.md") ?? false;
10238
10239
  }
10239
10240
  function extractRetrospectiveSection(planContent) {
10240
10241
  const headingRegex = /^###\s+Lessons\s+Learned$/m;
@@ -10941,24 +10942,22 @@ async function runAutoPromotion(directory, config) {
10941
10942
  }
10942
10943
  }
10943
10944
  function createKnowledgeCuratorHook(directory, config, options = {}) {
10944
- const handler = async (input, _output) => {
10945
+ const handler = async (input, output) => {
10945
10946
  pruneSeenRetroSections();
10946
10947
  if (!config.enabled)
10947
10948
  return;
10948
- if (!isWriteToSwarmPlan(input) && !isWriteToEvidenceFile(input))
10949
+ const trigger = normalizeWriteTrigger(input, output);
10950
+ if (!trigger)
10951
+ return;
10952
+ const isPlanTrigger = isPlanPath(trigger.filePath);
10953
+ const isEvidenceTrigger = isEvidencePath(trigger.filePath) && !isPlanTrigger;
10954
+ if (!isPlanTrigger && !isEvidenceTrigger)
10949
10955
  return;
10950
- const sessionID = input?.sessionID ?? "default";
10951
- const isEvidenceTrigger = isWriteToEvidenceFile(input) && !isWriteToSwarmPlan(input);
10952
10956
  if (isEvidenceTrigger) {
10953
- const record = input;
10954
- const rawPath = record.path;
10955
- const rawFile = record.file;
10956
- const filePath = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : null;
10957
- if (!filePath)
10958
- return;
10959
- const evidenceKey = `evidence:${sessionID}:${filePath}`;
10957
+ const relativeEvidencePath = trigger.filePath.replace(/^.*\.swarm\//, "");
10958
+ const evidenceKey = `evidence:${trigger.sessionID}:${relativeEvidencePath}`;
10960
10959
  const lastSeenEvidence = seenRetroSections.get(evidenceKey);
10961
- const evidenceContent = await readSwarmFileAsync(directory, filePath.replace(/^.*\.swarm\//, ""));
10960
+ const evidenceContent = await readSwarmFileAsync(directory, relativeEvidencePath);
10962
10961
  if (!evidenceContent)
10963
10962
  return;
10964
10963
  let evidenceData;
@@ -10986,7 +10985,7 @@ function createKnowledgeCuratorHook(directory, config, options = {}) {
10986
10985
  const projectName2 = evidenceData.project_name ?? "unknown";
10987
10986
  const phaseNumber2 = typeof evidenceData.phase_number === "number" ? evidenceData.phase_number : 1;
10988
10987
  await _internals17.curateAndStoreSwarm(lessons, projectName2, { phase_number: phaseNumber2 }, directory, config, {
10989
- llmDelegate: options.llmDelegateFactory?.(sessionID),
10988
+ llmDelegate: options.llmDelegateFactory?.(trigger.sessionID),
10990
10989
  enrichmentQuota: options.enrichmentQuota
10991
10990
  });
10992
10991
  return;
@@ -10997,7 +10996,7 @@ function createKnowledgeCuratorHook(directory, config, options = {}) {
10997
10996
  const section = extractRetrospectiveSection(planContent);
10998
10997
  if (!section)
10999
10998
  return;
11000
- if (!checkRetroChanged(sessionID, section))
10999
+ if (!checkRetroChanged(trigger.sessionID, section))
11001
11000
  return;
11002
11001
  const allLessons = extractLessonsFromRetro(section);
11003
11002
  if (allLessons.length === 0)
@@ -11011,7 +11010,7 @@ function createKnowledgeCuratorHook(directory, config, options = {}) {
11011
11010
  const phaseMatch = /^Phase:\s*(\d+)/m.exec(planContent);
11012
11011
  const phaseNumber = phaseMatch ? parseInt(phaseMatch[1], 10) : 1;
11013
11012
  await _internals17.curateAndStoreSwarm(normalLessons, projectName, { phase_number: phaseNumber }, directory, config, {
11014
- llmDelegate: options.llmDelegateFactory?.(sessionID),
11013
+ llmDelegate: options.llmDelegateFactory?.(trigger.sessionID),
11015
11014
  enrichmentQuota: options.enrichmentQuota
11016
11015
  });
11017
11016
  };
@@ -31857,7 +31856,7 @@ function buildDetailedHelp(commandName, entry) {
31857
31856
  async function handleHelpCommand(ctx) {
31858
31857
  const targetCommand = ctx.args.join(" ");
31859
31858
  if (!targetCommand) {
31860
- const { buildHelpText } = await import("./index-h6h8qfsh.js");
31859
+ const { buildHelpText } = await import("./index-attgb1ma.js");
31861
31860
  return buildHelpText();
31862
31861
  }
31863
31862
  const tokens = targetCommand.split(/\s+/);
@@ -31866,7 +31865,7 @@ async function handleHelpCommand(ctx) {
31866
31865
  return _internals45.buildDetailedHelp(resolved.key, resolved.entry);
31867
31866
  }
31868
31867
  const similar = _internals45.findSimilarCommands(targetCommand);
31869
- const { buildHelpText: fullHelp } = await import("./index-h6h8qfsh.js");
31868
+ const { buildHelpText: fullHelp } = await import("./index-attgb1ma.js");
31870
31869
  if (similar.length > 0) {
31871
31870
  return `Command '/swarm ${targetCommand}' not found.
31872
31871
 
@@ -31999,7 +31998,7 @@ var COMMAND_REGISTRY = {
31999
31998
  },
32000
31999
  "guardrail explain": {
32001
32000
  handler: async (ctx) => {
32002
- const { handleGuardrailExplain } = await import("./guardrail-explain-xdv74tfk.js");
32001
+ const { handleGuardrailExplain } = await import("./guardrail-explain-cm08h4mt.js");
32003
32002
  return handleGuardrailExplain(ctx.directory, ctx.args);
32004
32003
  },
32005
32004
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-pc10e4d7.js";
10
+ } from "./index-zgba613y.js";
11
11
  import"./index-5z2e78tv.js";
12
12
  import"./index-2a6ppa65.js";
13
13
  import"./index-fjxjb66n.js";
@@ -21,6 +21,7 @@ declare function hashContent(content: string): string;
21
21
  * Exported for testing purposes only.
22
22
  */
23
23
  export declare function isWriteToEvidenceFile(input: unknown): boolean;
24
+ export declare function isEvidencePath(filePath: string | undefined | null): boolean;
24
25
  /** Build the v3-schema enrichment prompt for a single prose lesson. */
25
26
  export declare function buildV3EnrichmentPrompt(lesson: string, category: string, tags: string[]): string;
26
27
  /**
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.98.0",
72
+ version: "7.98.1",
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",
@@ -70430,48 +70430,43 @@ function recordSeenRetroSection(key, value, timestamp) {
70430
70430
  function hashContent2(content) {
70431
70431
  return createHash9("sha1").update(content).digest("hex");
70432
70432
  }
70433
- function isWriteToSwarmPlan(input) {
70434
- if (typeof input !== "object" || input === null)
70435
- return false;
70436
- const record3 = input;
70437
- const toolName = record3.toolName;
70438
- if (typeof toolName !== "string")
70439
- return false;
70440
- if (!["write", "edit", "apply_patch", "swarm_apply_patch"].includes(toolName))
70441
- return false;
70442
- const rawPath = record3.path;
70443
- const rawFile = record3.file;
70444
- const pathField = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : undefined;
70445
- const fileField = typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : undefined;
70446
- if (typeof pathField === "string" && pathField.includes(".swarm/plan.md")) {
70447
- return true;
70448
- }
70449
- if (typeof fileField === "string" && fileField.includes(".swarm/plan.md")) {
70450
- return true;
70451
- }
70452
- return false;
70453
- }
70454
70433
  function isWriteToEvidenceFile(input) {
70455
- if (typeof input !== "object" || input === null)
70456
- return false;
70457
- const record3 = input;
70458
- const toolName = record3.toolName;
70459
- if (typeof toolName !== "string")
70460
- return false;
70461
- if (!["write", "edit", "apply_patch", "swarm_apply_patch"].includes(toolName))
70434
+ const trigger = normalizeWriteTrigger(input);
70435
+ return isEvidencePath(trigger?.filePath);
70436
+ }
70437
+ function isRecord(value) {
70438
+ return typeof value === "object" && value !== null;
70439
+ }
70440
+ function normalizePathField(value) {
70441
+ return typeof value === "string" ? value.replace(/\\/g, "/") : null;
70442
+ }
70443
+ function firstPathFromRecord(record3) {
70444
+ return normalizePathField(record3.path) ?? normalizePathField(record3.filePath) ?? normalizePathField(record3.file);
70445
+ }
70446
+ function normalizeWriteTrigger(input, output) {
70447
+ if (!isRecord(input))
70448
+ return null;
70449
+ const toolName = typeof input.toolName === "string" ? input.toolName : typeof input.tool === "string" ? input.tool : null;
70450
+ if (!toolName || !WRITE_TOOLS.has(toolName))
70451
+ return null;
70452
+ const inputArgs = isRecord(input.args) ? input.args : null;
70453
+ const outputArgs = isRecord(output) && isRecord(output.args) ? output.args : null;
70454
+ const filePath = firstPathFromRecord(input) ?? (inputArgs ? firstPathFromRecord(inputArgs) : null) ?? (outputArgs ? firstPathFromRecord(outputArgs) : null);
70455
+ if (!filePath)
70456
+ return null;
70457
+ return {
70458
+ toolName,
70459
+ filePath,
70460
+ sessionID: typeof input.sessionID === "string" ? input.sessionID : "default"
70461
+ };
70462
+ }
70463
+ function isEvidencePath(filePath) {
70464
+ if (!filePath)
70462
70465
  return false;
70463
- const rawPath = record3.path;
70464
- const rawFile = record3.file;
70465
- const pathField = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : undefined;
70466
- const fileField = typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : undefined;
70467
- const evidenceRegex = /\.swarm\/+evidence\/+/i;
70468
- if (typeof pathField === "string" && evidenceRegex.test(pathField)) {
70469
- return true;
70470
- }
70471
- if (typeof fileField === "string" && evidenceRegex.test(fileField)) {
70472
- return true;
70473
- }
70474
- return false;
70466
+ return /(?:^|\/)\.swarm\/+evidence\//i.test(filePath);
70467
+ }
70468
+ function isPlanPath(filePath) {
70469
+ return filePath?.includes(".swarm/plan.md") ?? false;
70475
70470
  }
70476
70471
  function extractRetrospectiveSection(planContent) {
70477
70472
  const headingRegex = /^###\s+Lessons\s+Learned$/m;
@@ -71153,24 +71148,22 @@ async function runAutoPromotion(directory, config3) {
71153
71148
  }
71154
71149
  }
71155
71150
  function createKnowledgeCuratorHook(directory, config3, options = {}) {
71156
- const handler = async (input, _output) => {
71151
+ const handler = async (input, output) => {
71157
71152
  pruneSeenRetroSections();
71158
71153
  if (!config3.enabled)
71159
71154
  return;
71160
- if (!isWriteToSwarmPlan(input) && !isWriteToEvidenceFile(input))
71155
+ const trigger = normalizeWriteTrigger(input, output);
71156
+ if (!trigger)
71157
+ return;
71158
+ const isPlanTrigger = isPlanPath(trigger.filePath);
71159
+ const isEvidenceTrigger = isEvidencePath(trigger.filePath) && !isPlanTrigger;
71160
+ if (!isPlanTrigger && !isEvidenceTrigger)
71161
71161
  return;
71162
- const sessionID = input?.sessionID ?? "default";
71163
- const isEvidenceTrigger = isWriteToEvidenceFile(input) && !isWriteToSwarmPlan(input);
71164
71162
  if (isEvidenceTrigger) {
71165
- const record3 = input;
71166
- const rawPath = record3.path;
71167
- const rawFile = record3.file;
71168
- const filePath = typeof rawPath === "string" ? rawPath.replace(/\\/g, "/") : typeof rawFile === "string" ? rawFile.replace(/\\/g, "/") : null;
71169
- if (!filePath)
71170
- return;
71171
- const evidenceKey = `evidence:${sessionID}:${filePath}`;
71163
+ const relativeEvidencePath = trigger.filePath.replace(/^.*\.swarm\//, "");
71164
+ const evidenceKey = `evidence:${trigger.sessionID}:${relativeEvidencePath}`;
71172
71165
  const lastSeenEvidence = seenRetroSections.get(evidenceKey);
71173
- const evidenceContent = await readSwarmFileAsync(directory, filePath.replace(/^.*\.swarm\//, ""));
71166
+ const evidenceContent = await readSwarmFileAsync(directory, relativeEvidencePath);
71174
71167
  if (!evidenceContent)
71175
71168
  return;
71176
71169
  let evidenceData;
@@ -71198,7 +71191,7 @@ function createKnowledgeCuratorHook(directory, config3, options = {}) {
71198
71191
  const projectName2 = evidenceData.project_name ?? "unknown";
71199
71192
  const phaseNumber2 = typeof evidenceData.phase_number === "number" ? evidenceData.phase_number : 1;
71200
71193
  await _internals35.curateAndStoreSwarm(lessons, projectName2, { phase_number: phaseNumber2 }, directory, config3, {
71201
- llmDelegate: options.llmDelegateFactory?.(sessionID),
71194
+ llmDelegate: options.llmDelegateFactory?.(trigger.sessionID),
71202
71195
  enrichmentQuota: options.enrichmentQuota
71203
71196
  });
71204
71197
  return;
@@ -71209,7 +71202,7 @@ function createKnowledgeCuratorHook(directory, config3, options = {}) {
71209
71202
  const section = extractRetrospectiveSection(planContent);
71210
71203
  if (!section)
71211
71204
  return;
71212
- if (!checkRetroChanged(sessionID, section))
71205
+ if (!checkRetroChanged(trigger.sessionID, section))
71213
71206
  return;
71214
71207
  const allLessons = extractLessonsFromRetro(section);
71215
71208
  if (allLessons.length === 0)
@@ -71223,13 +71216,13 @@ function createKnowledgeCuratorHook(directory, config3, options = {}) {
71223
71216
  const phaseMatch = /^Phase:\s*(\d+)/m.exec(planContent);
71224
71217
  const phaseNumber = phaseMatch ? parseInt(phaseMatch[1], 10) : 1;
71225
71218
  await _internals35.curateAndStoreSwarm(normalLessons, projectName, { phase_number: phaseNumber }, directory, config3, {
71226
- llmDelegate: options.llmDelegateFactory?.(sessionID),
71219
+ llmDelegate: options.llmDelegateFactory?.(trigger.sessionID),
71227
71220
  enrichmentQuota: options.enrichmentQuota
71228
71221
  });
71229
71222
  };
71230
71223
  return safeHook(handler);
71231
71224
  }
71232
- var seenRetroSections, MAX_TRACKED_RETRO_SECTIONS = 500, ENRICHMENT_ALLOWED_FIELDS, ENRICHMENT_LLM_TIMEOUT_MS = 60000, ENRICHMENT_BATCH_SIZE = 6, MESO_INSIGHT_BATCH_LIMIT = 20, KNOWLEDGE_CATEGORIES, OUTCOME_PROMOTION_BLOCK = -0.3, _internals35;
71225
+ var seenRetroSections, MAX_TRACKED_RETRO_SECTIONS = 500, WRITE_TOOLS, ENRICHMENT_ALLOWED_FIELDS, ENRICHMENT_LLM_TIMEOUT_MS = 60000, ENRICHMENT_BATCH_SIZE = 6, MESO_INSIGHT_BATCH_LIMIT = 20, KNOWLEDGE_CATEGORIES, OUTCOME_PROMOTION_BLOCK = -0.3, _internals35;
71233
71226
  var init_knowledge_curator = __esm(() => {
71234
71227
  init_skill_improver_quota();
71235
71228
  init_synonym_map();
@@ -71241,6 +71234,12 @@ var init_knowledge_curator = __esm(() => {
71241
71234
  init_micro_reflector();
71242
71235
  init_utils2();
71243
71236
  seenRetroSections = new Map;
71237
+ WRITE_TOOLS = new Set([
71238
+ "write",
71239
+ "edit",
71240
+ "apply_patch",
71241
+ "swarm_apply_patch"
71242
+ ]);
71244
71243
  ENRICHMENT_ALLOWED_FIELDS = [
71245
71244
  "triggers",
71246
71245
  "required_actions",
@@ -125232,7 +125231,7 @@ init_state2();
125232
125231
  init_delegation_gate();
125233
125232
  init_normalize_tool_name();
125234
125233
  import * as path154 from "node:path";
125235
- var WRITE_TOOLS = new Set(WRITE_TOOL_NAMES);
125234
+ var WRITE_TOOLS2 = new Set(WRITE_TOOL_NAMES);
125236
125235
  function createScopeGuardHook(config3, directory, injectAdvisory) {
125237
125236
  const enabled = config3.enabled ?? true;
125238
125237
  const _skipInTurbo = config3.skip_in_turbo ?? false;
@@ -125241,7 +125240,7 @@ function createScopeGuardHook(config3, directory, injectAdvisory) {
125241
125240
  if (!enabled)
125242
125241
  return;
125243
125242
  const toolName = normalizeToolName(input.tool);
125244
- if (!WRITE_TOOLS.has(toolName))
125243
+ if (!WRITE_TOOLS2.has(toolName))
125245
125244
  return;
125246
125245
  const sessionId = input.sessionID;
125247
125246
  const activeAgent = swarmState.activeAgent.get(sessionId);
@@ -135730,8 +135729,99 @@ function scanInvisibleFormatChars(text, fieldName) {
135730
135729
  }
135731
135730
  return findings;
135732
135731
  }
135733
- function stripMarkdownCodeForUnsafeScan(text) {
135734
- return text.replace(/```[\s\S]*?```/g, " ").replace(/~~~[\s\S]*?~~~/g, " ").replace(/`[^`\n]*`/g, " ");
135732
+ var CODE_SPAN_OR_FENCE_REGEX = /```[\s\S]*?```|~~~[\s\S]*?~~~|`[^`\n]*`/g;
135733
+ var CODE_WARNING_ONLY_PATTERNS = new Set([
135734
+ "backtick_execution",
135735
+ "shell_substitution",
135736
+ "disk_format",
135737
+ "force_kill",
135738
+ "process_group_kill",
135739
+ "kill_all_processes"
135740
+ ]);
135741
+ function splitMarkdownCodeSegments(text) {
135742
+ const segments = [];
135743
+ let cursor = 0;
135744
+ for (const match of text.matchAll(CODE_SPAN_OR_FENCE_REGEX)) {
135745
+ const index = match.index ?? 0;
135746
+ if (index > cursor) {
135747
+ segments.push({ value: text.slice(cursor, index), isCode: false });
135748
+ }
135749
+ segments.push({ value: match[0], isCode: true });
135750
+ cursor = index + match[0].length;
135751
+ }
135752
+ if (cursor < text.length) {
135753
+ segments.push({ value: text.slice(cursor), isCode: false });
135754
+ }
135755
+ return segments.length === 0 ? [{ value: text, isCode: false }] : segments;
135756
+ }
135757
+ function getUnsafeInstructionMetadata(name) {
135758
+ const entry = UNSAFE_INSTRUCTION_PATTERNS.find((item) => item.name === name);
135759
+ return {
135760
+ description: entry?.description ?? "Destructive file removal command",
135761
+ severity: entry?.severity ?? "error"
135762
+ };
135763
+ }
135764
+ function tokenizeShellArgs(input) {
135765
+ const tokens = [];
135766
+ const tokenRegex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+/g;
135767
+ for (const match of input.matchAll(tokenRegex)) {
135768
+ const token = match[1] ?? match[2] ?? match[0];
135769
+ if (token.length > 0)
135770
+ tokens.push(token);
135771
+ }
135772
+ return tokens;
135773
+ }
135774
+ function rmFlagHasShortOption(token, option) {
135775
+ return /^-[A-Za-z]+$/.test(token) && token.slice(1).includes(option);
135776
+ }
135777
+ function isRmOption(token) {
135778
+ return token.startsWith("-") && token !== "-";
135779
+ }
135780
+ function isDangerousRemovalTarget(rawTarget) {
135781
+ const target = rawTarget.replace(/\\/g, "/");
135782
+ if (/^[A-Za-z]:[/\\]?$/.test(target))
135783
+ return true;
135784
+ return target === "/" || target === "/*" || target === "." || target === ".." || target.startsWith(".swarm") || target.startsWith("/.swarm") || target.startsWith("~") || target.startsWith("$") || target.startsWith("${") || target.startsWith("%") || target.startsWith("/home") || target.startsWith("/Users") || target.startsWith("/etc") || target.startsWith("/bin") || target.startsWith("/sbin") || target.startsWith("/usr") || target.startsWith("/var") || /^[A-Za-z]:\/(?:Users|Windows)(?:\/|$)/.test(target);
135785
+ }
135786
+ function findDangerousRemovalCommands(text) {
135787
+ const commands = [];
135788
+ const rmCommandRegex = /\b(?<sudo>sudo\s+)?rm\b(?<args>[^\r\n`;&|]*)/gi;
135789
+ for (const match of text.matchAll(rmCommandRegex)) {
135790
+ const args2 = tokenizeShellArgs(match.groups?.args ?? "");
135791
+ const hasRecursive = args2.some((token) => token === "--recursive" || token === "-R" || rmFlagHasShortOption(token, "r") || rmFlagHasShortOption(token, "R"));
135792
+ const hasForce = args2.some((token) => token === "--force" || token === "-f" || rmFlagHasShortOption(token, "f"));
135793
+ if (!hasRecursive || !hasForce)
135794
+ continue;
135795
+ if (!args2.some((token) => !isRmOption(token) && isDangerousRemovalTarget(token))) {
135796
+ continue;
135797
+ }
135798
+ const pattern = match.groups?.sudo === undefined ? "destructive_file_removal" : "privileged_file_removal";
135799
+ commands.push({
135800
+ pattern,
135801
+ description: getUnsafeInstructionMetadata(pattern).description,
135802
+ match: match[0].trim().slice(0, 100)
135803
+ });
135804
+ }
135805
+ return commands;
135806
+ }
135807
+ function hasDangerousRemovalTarget(text) {
135808
+ return findDangerousRemovalCommands(text).length > 0;
135809
+ }
135810
+ function shouldScanUnsafePatternInSegment(entry, segment) {
135811
+ if (!segment.isCode)
135812
+ return true;
135813
+ if (CODE_WARNING_ONLY_PATTERNS.has(entry.name))
135814
+ return false;
135815
+ if (entry.name === "destructive_file_removal" || entry.name === "privileged_file_removal") {
135816
+ return false;
135817
+ }
135818
+ return true;
135819
+ }
135820
+ function getUnsafeScanSegments(field, value) {
135821
+ if (field !== "skill_body") {
135822
+ return [{ value, isCode: false }];
135823
+ }
135824
+ return splitMarkdownCodeSegments(value);
135735
135825
  }
135736
135826
  function scanPromptInjection(candidate, trustLevel = "low") {
135737
135827
  const fields = extractCandidateFields(candidate);
@@ -135799,17 +135889,37 @@ function scanUnsafeInstructions(candidate, trustLevel = "low") {
135799
135889
  const fieldsScanned = [];
135800
135890
  for (const { field, value } of fields) {
135801
135891
  fieldsScanned.push(field);
135802
- const scanValue = field === "skill_body" ? stripMarkdownCodeForUnsafeScan(value) : value;
135892
+ const scanSegments = getUnsafeScanSegments(field, value);
135893
+ if (field === "skill_body") {
135894
+ for (const segment of scanSegments) {
135895
+ if (!segment.isCode)
135896
+ continue;
135897
+ for (const command of findDangerousRemovalCommands(segment.value)) {
135898
+ findings.push({
135899
+ pattern: command.pattern,
135900
+ field,
135901
+ description: command.description,
135902
+ severity: "error",
135903
+ match: command.match
135904
+ });
135905
+ }
135906
+ }
135907
+ }
135803
135908
  for (const entry of UNSAFE_INSTRUCTION_PATTERNS) {
135804
- const match = entry.pattern.exec(scanValue);
135805
- if (match !== null) {
135806
- findings.push({
135807
- pattern: entry.name,
135808
- field,
135809
- description: entry.description,
135810
- severity: entry.severity,
135811
- match: match[0].slice(0, 100)
135812
- });
135909
+ for (const segment of scanSegments) {
135910
+ if (!shouldScanUnsafePatternInSegment(entry, segment))
135911
+ continue;
135912
+ const match = entry.pattern.exec(segment.value);
135913
+ if (match !== null) {
135914
+ findings.push({
135915
+ pattern: entry.name,
135916
+ field,
135917
+ description: entry.description,
135918
+ severity: entry.severity,
135919
+ match: match[0].slice(0, 100)
135920
+ });
135921
+ break;
135922
+ }
135813
135923
  }
135814
135924
  }
135815
135925
  }
@@ -135973,7 +136083,9 @@ function evaluateCandidate(candidate, options) {
135973
136083
  var _internals103 = {
135974
136084
  getTimestamp: () => new Date().toISOString(),
135975
136085
  computeSha256: (content) => createHash21("sha256").update(content).digest("hex"),
135976
- stripMarkdownCodeForUnsafeScan
136086
+ splitMarkdownCodeSegments,
136087
+ hasDangerousRemovalTarget,
136088
+ isDangerousRemovalTarget
135977
136089
  };
135978
136090
 
135979
136091
  // src/tools/external-skill-discover.ts
@@ -105,7 +105,13 @@ export declare const VALIDATION_RATE_LIMITS: {
105
105
  /** Timeout for individual fetch operations in milliseconds. */
106
106
  readonly fetch_timeout_ms: 30000;
107
107
  };
108
- declare function stripMarkdownCodeForUnsafeScan(text: string): string;
108
+ interface MarkdownSegment {
109
+ value: string;
110
+ isCode: boolean;
111
+ }
112
+ declare function splitMarkdownCodeSegments(text: string): MarkdownSegment[];
113
+ declare function isDangerousRemovalTarget(rawTarget: string): boolean;
114
+ declare function hasDangerousRemovalTarget(text: string): boolean;
109
115
  /**
110
116
  * Scan an external skill candidate for prompt-injection patterns.
111
117
  *
@@ -158,6 +164,8 @@ export declare function evaluateCandidate(candidate: ExternalSkillCandidate, opt
158
164
  export declare const _internals: {
159
165
  getTimestamp: () => string;
160
166
  computeSha256: (content: string) => string;
161
- stripMarkdownCodeForUnsafeScan: typeof stripMarkdownCodeForUnsafeScan;
167
+ splitMarkdownCodeSegments: typeof splitMarkdownCodeSegments;
168
+ hasDangerousRemovalTarget: typeof hasDangerousRemovalTarget;
169
+ isDangerousRemovalTarget: typeof isDangerousRemovalTarget;
162
170
  };
163
171
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.98.0",
3
+ "version": "7.98.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",