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 +5 -2
- package/dist/cli/index.js +10 -4
- package/dist/config/schema.d.ts +2 -0
- package/dist/hooks/guardrails.d.ts +5 -0
- package/dist/index.js +293 -147
- package/package.json +1 -1
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. `
|
|
769
|
-
7. `
|
|
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
|
|
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)) {
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
48055
|
-
|
|
48056
|
-
|
|
48057
|
-
|
|
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
|
-
|
|
48060
|
-
|
|
48061
|
-
|
|
48062
|
-
|
|
48063
|
-
|
|
48064
|
-
|
|
48065
|
-
|
|
48066
|
-
|
|
48067
|
-
|
|
48068
|
-
|
|
48069
|
-
|
|
48070
|
-
|
|
48071
|
-
|
|
48072
|
-
|
|
48073
|
-
|
|
48074
|
-
|
|
48075
|
-
|
|
48076
|
-
|
|
48077
|
-
|
|
48078
|
-
|
|
48079
|
-
|
|
48080
|
-
|
|
48081
|
-
|
|
48082
|
-
|
|
48083
|
-
|
|
48084
|
-
|
|
48085
|
-
|
|
48086
|
-
|
|
48087
|
-
|
|
48088
|
-
|
|
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
|
-
|
|
58598
|
-
|
|
58599
|
-
|
|
58600
|
-
|
|
58601
|
-
|
|
58602
|
-
|
|
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,
|
|
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
|
-
|
|
67764
|
-
|
|
67765
|
-
|
|
67766
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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",
|