opencode-swarm 7.125.6 → 7.126.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/.opencode/skills/brainstorm/SKILL.md +18 -14
- package/.opencode/skills/plan/SKILL.md +21 -15
- package/.opencode/skills/specify/SKILL.md +18 -14
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +19 -14
- package/.opencode/skills/swarm-pr-review/SKILL.md +17 -13
- package/dist/agents/architect.d.ts +9 -7
- package/dist/cli/{config-doctor-akxywx0c.js → config-doctor-23jxdqwv.js} +2 -2
- package/dist/cli/{core-v1kp7anb.js → core-wg3re04e.js} +1 -1
- package/dist/cli/{curation-policy-62w2pwfz.js → curation-policy-0t6hny6g.js} +2 -2
- package/dist/cli/{curator-gke6nrc4.js → curator-g8rggzbk.js} +14 -14
- package/dist/cli/{curator-llm-factory-40c4852v.js → curator-llm-factory-36psae60.js} +14 -14
- package/dist/cli/{guardrail-explain-m34vcgjg.js → guardrail-explain-6ffgrjx9.js} +15 -15
- package/dist/cli/{guardrail-log-222xyxfp.js → guardrail-log-57sczn5h.js} +3 -3
- package/dist/cli/{hive-promoter-6fg3redm.js → hive-promoter-c458rgp5.js} +14 -14
- package/dist/cli/{index-qzkcy22e.js → index-1p9z8cc4.js} +1 -1
- package/dist/cli/{index-e315gwc9.js → index-3hkbegq2.js} +1 -1
- package/dist/cli/{index-gnvg72y1.js → index-4ff0x4cg.js} +79 -1
- package/dist/cli/{index-yq4s3q43.js → index-6j9p0c40.js} +2 -2
- package/dist/cli/{index-74xejn4v.js → index-abw10n3f.js} +139 -83
- package/dist/cli/{index-r1gdkn2d.js → index-c8sqc3nh.js} +113 -21
- package/dist/cli/{index-gbnpz7rh.js → index-cnhp6c0k.js} +1 -1
- package/dist/cli/{index-dezgmvtw.js → index-echdpdhy.js} +1 -1
- package/dist/cli/{index-gd5w3ef8.js → index-k5xh81c9.js} +3 -3
- package/dist/cli/{index-1htanqp1.js → index-kx3533dm.js} +1 -1
- package/dist/cli/{index-0f45t7xy.js → index-nkhp7j16.js} +5 -5
- package/dist/cli/{index-1012f47p.js → index-nmyg9b7v.js} +4 -4
- package/dist/cli/{index-4b1e5m0d.js → index-vvwf9ark.js} +2 -2
- package/dist/cli/{index-jt7hnr7q.js → index-w0yztxgq.js} +1 -1
- package/dist/cli/{index-g4ef4v3m.js → index-w9cb5zwh.js} +1 -1
- package/dist/cli/{index-1cxp3b5y.js → index-x1w4cx3n.js} +2 -2
- package/dist/cli/{index-d17kqe54.js → index-y9t766sw.js} +4950 -4890
- package/dist/cli/{index-zp330ss0.js → index-yf4kvhrn.js} +16 -16
- package/dist/cli/index.js +14 -14
- package/dist/cli/{knowledge-escalator-h3hg7ek7.js → knowledge-escalator-kd80m9vp.js} +3 -3
- package/dist/cli/{knowledge-events-jtr66wxz.js → knowledge-events-j7jfwm1d.js} +1 -1
- package/dist/cli/{knowledge-store-69ttjjjs.js → knowledge-store-1d4byc7y.js} +1 -1
- package/dist/cli/{knowledge-validator-yn06rh13.js → knowledge-validator-63mkgt9x.js} +4 -4
- package/dist/cli/{scan-cursor-r0w2p5gz.js → scan-cursor-qzn5jmbe.js} +2 -2
- package/dist/cli/{schema-69z8w1ka.js → schema-2j92wnns.js} +1 -1
- package/dist/cli/{scope-persistence-habn4q8a.js → scope-persistence-dm5bab50.js} +3 -1
- package/dist/cli/{skill-generator-a7xfyddq.js → skill-generator-sv6zvdvz.js} +5 -5
- package/dist/commands/turbo-constants.d.ts +6 -0
- package/dist/config/constants.d.ts +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/loader.d.ts +51 -10
- package/dist/hooks/pr-workflow-gate.d.ts +39 -0
- package/dist/hooks/scope-guard.d.ts +6 -1
- package/dist/index.js +47 -84
- package/dist/scope/scope-binding.d.ts +21 -2
- package/dist/scope/scope-persistence.d.ts +6 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/manifest.d.ts +2 -0
- package/dist/tools/prepare-pr-feedback-scope.d.ts +5 -0
- package/dist/tools/tool-metadata.d.ts +98 -0
- package/dist/tools/write-pr-review-artifact.d.ts +5 -0
- package/package.json +4 -3
|
@@ -136,6 +136,7 @@ var SCOPES_DIR = ".swarm/scopes";
|
|
|
136
136
|
var MAX_FILES_PER_SCOPE = 1e4;
|
|
137
137
|
var MAX_PLAN_BYTES = 10 * 1024 * 1024;
|
|
138
138
|
var MAX_SCOPE_BYTES = 2 * 1024 * 1024;
|
|
139
|
+
var MAX_BINDING_FILES_TO_SCAN = 1e4;
|
|
139
140
|
var WINDOWS_RESERVED = new Set([
|
|
140
141
|
"CON",
|
|
141
142
|
"PRN",
|
|
@@ -432,6 +433,83 @@ function resolveAuthorizedScopeBindingForSession(input) {
|
|
|
432
433
|
}
|
|
433
434
|
return matches[0] ?? null;
|
|
434
435
|
}
|
|
436
|
+
function readPrFeedbackBindingFile(directory, filePath, activeSessionId, taskId) {
|
|
437
|
+
try {
|
|
438
|
+
const leaf = fs2.lstatSync(filePath);
|
|
439
|
+
if (!leaf.isFile() || leaf.isSymbolicLink())
|
|
440
|
+
return null;
|
|
441
|
+
if (normalizePathForComparison(fs2.realpathSync(filePath)) !== normalizePathForComparison(filePath))
|
|
442
|
+
return null;
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const nofollow = fs2.constants.O_NOFOLLOW ?? 0;
|
|
447
|
+
let fd;
|
|
448
|
+
try {
|
|
449
|
+
fd = fs2.openSync(filePath, fs2.constants.O_RDONLY | nofollow);
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
let raw = "";
|
|
454
|
+
try {
|
|
455
|
+
const stat = fs2.fstatSync(fd);
|
|
456
|
+
if (!stat.isFile() || stat.size > MAX_SCOPE_BYTES)
|
|
457
|
+
return null;
|
|
458
|
+
const buffer = Buffer.alloc(stat.size);
|
|
459
|
+
fs2.readSync(fd, buffer, 0, stat.size, 0);
|
|
460
|
+
raw = buffer.toString("utf8");
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
} finally {
|
|
464
|
+
try {
|
|
465
|
+
fs2.closeSync(fd);
|
|
466
|
+
} catch {}
|
|
467
|
+
}
|
|
468
|
+
let parsed;
|
|
469
|
+
try {
|
|
470
|
+
parsed = JSON.parse(raw);
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
const workspaceIdentity = canonicalWorkspaceIdentity(directory);
|
|
475
|
+
const files = Array.isArray(parsed.files) ? normalizeScopeFiles(parsed.files.filter((file) => typeof file === "string")) : null;
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
if (parsed.version !== 2 || parsed.source !== "pr_feedback" || !workspaceIdentity || parsed.workspaceIdentity !== workspaceIdentity || !isSafeTaskId(parsed.taskId ?? "") || taskId !== undefined && parsed.taskId !== taskId || parsed.ownerSessionId !== activeSessionId || parsed.activation !== "active" || typeof parsed.dispatchCallId !== "string" || !parsed.dispatchCallId || parsed.ownerMessageId !== parsed.dispatchCallId || typeof parsed.parentOwnerSessionId !== "string" || !parsed.parentOwnerSessionId || parsed.ownerSessionId === parsed.parentOwnerSessionId || parsed.parentCallId !== parsed.dispatchCallId || parsed.workflowSessionId !== parsed.parentOwnerSessionId || typeof parsed.workflowRevisionDigest !== "string" || !parsed.workflowRevisionDigest || parsed.planId !== `pr-feedback:${parsed.workflowSessionId}` || parsed.planStructureHash !== parsed.workflowRevisionDigest || typeof parsed.declaredAt !== "number" || parsed.declaredAt > now || typeof parsed.expiresAt !== "number" || parsed.expiresAt <= now || !files)
|
|
478
|
+
return null;
|
|
479
|
+
const expectedPath = getBindingFilePath(directory, parsed.taskId, activeSessionId);
|
|
480
|
+
if (normalizePathForComparison(expectedPath) !== normalizePathForComparison(filePath))
|
|
481
|
+
return null;
|
|
482
|
+
return { ...parsed, files };
|
|
483
|
+
}
|
|
484
|
+
function resolveAuthorizedPrFeedbackScopeBindingFromDisk(input) {
|
|
485
|
+
if (!input.activeSessionId.trim())
|
|
486
|
+
return null;
|
|
487
|
+
const scopesDir = getScopesDir(input.directory);
|
|
488
|
+
if (!isScopesDirSafe(input.directory, scopesDir))
|
|
489
|
+
return null;
|
|
490
|
+
let candidates;
|
|
491
|
+
if (input.taskId) {
|
|
492
|
+
if (!isSafeTaskId(input.taskId))
|
|
493
|
+
return null;
|
|
494
|
+
candidates = [
|
|
495
|
+
getBindingFilePath(input.directory, input.taskId, input.activeSessionId)
|
|
496
|
+
];
|
|
497
|
+
} else {
|
|
498
|
+
try {
|
|
499
|
+
const names = fs2.readdirSync(scopesDir);
|
|
500
|
+
if (names.length > MAX_BINDING_FILES_TO_SCAN)
|
|
501
|
+
return null;
|
|
502
|
+
candidates = names.filter((name) => name.startsWith("binding-") && name.endsWith(".json")).map((name) => path2.join(scopesDir, name));
|
|
503
|
+
} catch {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const matches = candidates.map((candidate) => readPrFeedbackBindingFile(input.directory, candidate, input.activeSessionId, input.taskId)).filter((binding) => binding !== null);
|
|
508
|
+
if (matches.length !== 1)
|
|
509
|
+
return null;
|
|
510
|
+
registerScopeBinding(matches[0]);
|
|
511
|
+
return matches[0];
|
|
512
|
+
}
|
|
435
513
|
async function atomicWrite(targetPath, content) {
|
|
436
514
|
const tempPath = `${targetPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
437
515
|
try {
|
|
@@ -561,4 +639,4 @@ function resolveScopeWithFallbacks(input) {
|
|
|
561
639
|
return null;
|
|
562
640
|
}
|
|
563
641
|
|
|
564
|
-
export { MAX_PENDING_SCOPE_BINDINGS, clearScopeBindings, writeScopeToDisk, writeScopeBindingToDisk, readScopeBindingFromDisk, clearScopeBindingFromDisk, resolveAuthorizedScopeBinding, resolveAuthorizedScopeBindingForSession, readScopeFromDisk, readPlanScope, clearScopeForTask, clearAllScopes, resolveScopeWithFallbacks };
|
|
642
|
+
export { MAX_PENDING_SCOPE_BINDINGS, clearScopeBindings, writeScopeToDisk, writeScopeBindingToDisk, readScopeBindingFromDisk, clearScopeBindingFromDisk, resolveAuthorizedScopeBinding, resolveAuthorizedScopeBindingForSession, resolveAuthorizedPrFeedbackScopeBindingFromDisk, readScopeFromDisk, readPlanScope, clearScopeForTask, clearAllScopes, resolveScopeWithFallbacks };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
authorizeCuration
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-kx3533dm.js";
|
|
5
5
|
import {
|
|
6
6
|
findNearDuplicate,
|
|
7
7
|
inferTags,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveSwarmKnowledgePath,
|
|
10
10
|
resolveSwarmRejectedPath,
|
|
11
11
|
transactKnowledge
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-nmyg9b7v.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveKnowledgeStoreDir
|
|
15
15
|
} from "./index-q27bajqb.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
init_constants,
|
|
9
9
|
init_schema,
|
|
10
10
|
resolveExternalSkillsConfig
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-c8sqc3nh.js";
|
|
12
12
|
import {
|
|
13
13
|
advisoryWarn,
|
|
14
14
|
init_warning_buffer
|
|
@@ -100,30 +100,35 @@ function migratePresetsConfig(raw) {
|
|
|
100
100
|
}
|
|
101
101
|
function sanitizeExternalSkillsConfig(raw) {
|
|
102
102
|
if (!("external_skills" in raw) || raw.external_skills === undefined) {
|
|
103
|
-
return raw;
|
|
103
|
+
return { result: raw, strippedKeys: [] };
|
|
104
104
|
}
|
|
105
105
|
const esResult = ExternalSkillsConfigSchema.safeParse(raw.external_skills);
|
|
106
106
|
if (esResult.success) {
|
|
107
107
|
return {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
result: {
|
|
109
|
+
...raw,
|
|
110
|
+
external_skills: resolveExternalSkillsConfig(esResult.data)
|
|
111
|
+
},
|
|
112
|
+
strippedKeys: []
|
|
110
113
|
};
|
|
111
114
|
}
|
|
112
115
|
advisoryWarn("[opencode-swarm] external_skills config validation failed:", formatZodIssues(esResult.error));
|
|
113
116
|
advisoryWarn("[opencode-swarm] External skills curation disabled due to invalid config. Fix the external_skills section to enable it.");
|
|
114
117
|
const cleaned = { ...raw };
|
|
115
118
|
delete cleaned.external_skills;
|
|
116
|
-
return cleaned;
|
|
119
|
+
return { result: cleaned, strippedKeys: ["external_skills"] };
|
|
117
120
|
}
|
|
118
121
|
function sanitizeGatesConfig(raw) {
|
|
122
|
+
const strippedKeys = [];
|
|
119
123
|
if (!("gates" in raw) || raw.gates === undefined) {
|
|
120
|
-
return raw;
|
|
124
|
+
return { result: raw, strippedKeys };
|
|
121
125
|
}
|
|
122
126
|
if (typeof raw.gates !== "object" || raw.gates === null || Array.isArray(raw.gates)) {
|
|
123
127
|
advisoryWarn("[opencode-swarm] gates config validation failed: expected an object. Quality gates will use defaults; other config sections remain active.");
|
|
124
128
|
const cleaned2 = { ...raw };
|
|
125
129
|
delete cleaned2.gates;
|
|
126
|
-
|
|
130
|
+
strippedKeys.push("gates");
|
|
131
|
+
return { result: cleaned2, strippedKeys };
|
|
127
132
|
}
|
|
128
133
|
const gateSchemas = GateConfigSchema.shape;
|
|
129
134
|
const cleanedGates = { ...raw.gates };
|
|
@@ -132,6 +137,7 @@ function sanitizeGatesConfig(raw) {
|
|
|
132
137
|
if (!schema) {
|
|
133
138
|
advisoryWarn(`[opencode-swarm] Unknown gates config section "gates.${key}" ignored. Other config sections remain active.`);
|
|
134
139
|
delete cleanedGates[key];
|
|
140
|
+
strippedKeys.push(`gates.${key}`);
|
|
135
141
|
continue;
|
|
136
142
|
}
|
|
137
143
|
const knownFields = GATE_CONFIG_KNOWN_SECTION_KEYS[key];
|
|
@@ -143,6 +149,7 @@ function sanitizeGatesConfig(raw) {
|
|
|
143
149
|
if (!knownFieldSet.has(fieldName)) {
|
|
144
150
|
advisoryWarn(`[opencode-swarm] Unknown gates config key "gates.${key}.${fieldName}" ignored. Other config sections remain active.`);
|
|
145
151
|
delete cleanedSection[fieldName];
|
|
152
|
+
strippedKeys.push(`gates.${key}.${fieldName}`);
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
sectionValue = cleanedSection;
|
|
@@ -152,22 +159,30 @@ function sanitizeGatesConfig(raw) {
|
|
|
152
159
|
if (!sectionResult.success) {
|
|
153
160
|
advisoryWarn(`[opencode-swarm] gates.${key} config validation failed; that gate section will use defaults and other config sections remain active:`, formatZodIssues(sectionResult.error));
|
|
154
161
|
delete cleanedGates[key];
|
|
162
|
+
if (!strippedKeys.includes(`gates.${key}`)) {
|
|
163
|
+
strippedKeys.push(`gates.${key}`);
|
|
164
|
+
}
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
const gatesResult = GateConfigSchema.safeParse(cleanedGates);
|
|
158
168
|
if (gatesResult.success) {
|
|
159
169
|
return {
|
|
160
|
-
...raw,
|
|
161
|
-
|
|
170
|
+
result: { ...raw, gates: gatesResult.data },
|
|
171
|
+
strippedKeys
|
|
162
172
|
};
|
|
163
173
|
}
|
|
164
174
|
advisoryWarn("[opencode-swarm] gates config validation failed after section cleanup; quality gates will use defaults and other config sections remain active:", formatZodIssues(gatesResult.error));
|
|
165
175
|
const cleaned = { ...raw };
|
|
166
176
|
delete cleaned.gates;
|
|
167
|
-
|
|
177
|
+
if (!strippedKeys.includes("gates")) {
|
|
178
|
+
strippedKeys.push("gates");
|
|
179
|
+
}
|
|
180
|
+
return { result: cleaned, strippedKeys };
|
|
168
181
|
}
|
|
169
182
|
function sanitizeSectionConfigs(raw) {
|
|
170
|
-
|
|
183
|
+
const { result: afterExternal, strippedKeys: externalStripped } = sanitizeExternalSkillsConfig(raw);
|
|
184
|
+
const { result, strippedKeys: gatesStripped } = sanitizeGatesConfig(afterExternal);
|
|
185
|
+
return { result, strippedKeys: [...externalStripped, ...gatesStripped] };
|
|
171
186
|
}
|
|
172
187
|
function formatZodIssues(error) {
|
|
173
188
|
if (error.issues.length === 0)
|
|
@@ -205,87 +220,133 @@ function stripUnrecognizedKeys(raw, error) {
|
|
|
205
220
|
}
|
|
206
221
|
return { cleaned, removed };
|
|
207
222
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
function warningForRemovedKeys(removedKeys) {
|
|
224
|
+
return `Ignored ${removedKeys.length} invalid or unrecognized config key(s): ${removedKeys.join(", ")}. The rest of your configuration was preserved. Fix or remove these keys.`;
|
|
225
|
+
}
|
|
226
|
+
function rawFullAutoLocked(raw) {
|
|
227
|
+
if (!raw || typeof raw !== "object")
|
|
228
|
+
return false;
|
|
229
|
+
const fullAuto = raw.full_auto;
|
|
230
|
+
if (!fullAuto || typeof fullAuto !== "object" || Array.isArray(fullAuto)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return fullAuto.locked === true;
|
|
234
|
+
}
|
|
235
|
+
function buildConfigWithMeta(rawUserConfig, rawProjectConfig, loadedFromFile, configHadErrors) {
|
|
236
|
+
let mergedRaw = rawUserConfig ?? {};
|
|
237
|
+
if (rawProjectConfig) {
|
|
238
|
+
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
239
|
+
}
|
|
240
|
+
if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
|
|
241
|
+
const fa = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
|
|
242
|
+
mergedRaw = { ...mergedRaw, full_auto: { ...fa, locked: true } };
|
|
243
|
+
}
|
|
244
|
+
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
245
|
+
const { result: sanitized, strippedKeys: gatesStripped } = sanitizeSectionConfigs(mergedRaw);
|
|
246
|
+
mergedRaw = sanitized;
|
|
247
|
+
const sanitizedWarning = gatesStripped.length > 0 ? warningForRemovedKeys(gatesStripped) : undefined;
|
|
248
|
+
const secure = (cfg) => loadedFromFile && configHadErrors ? PluginConfigSchema.parse({
|
|
249
|
+
...cfg,
|
|
212
250
|
guardrails: { enabled: true }
|
|
213
|
-
}) :
|
|
214
|
-
const
|
|
251
|
+
}) : cfg;
|
|
252
|
+
const firstResult = PluginConfigSchema.safeParse(mergedRaw);
|
|
253
|
+
if (firstResult.success) {
|
|
254
|
+
const config = secure(firstResult.data);
|
|
255
|
+
if (loadedFromFile && configHadErrors) {
|
|
256
|
+
const securityWarning = "\u26A0\uFE0F SECURITY: Falling back to conservative defaults with guardrails ENABLED. Fix the config file to restore custom configuration.";
|
|
257
|
+
advisoryWarn(`[opencode-swarm] ${securityWarning}`);
|
|
258
|
+
return {
|
|
259
|
+
config,
|
|
260
|
+
recovery: "guardrails_defaults",
|
|
261
|
+
removedKeys: [...gatesStripped],
|
|
262
|
+
warnings: [
|
|
263
|
+
...sanitizedWarning ? [sanitizedWarning] : [],
|
|
264
|
+
securityWarning
|
|
265
|
+
]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (sanitizedWarning)
|
|
269
|
+
advisoryWarn(`[opencode-swarm] ${sanitizedWarning}`);
|
|
270
|
+
return {
|
|
271
|
+
config,
|
|
272
|
+
recovery: gatesStripped.length > 0 ? "stripped_keys" : "none",
|
|
273
|
+
removedKeys: [...gatesStripped],
|
|
274
|
+
warnings: sanitizedWarning ? [sanitizedWarning] : []
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const { cleaned, removed } = stripUnrecognizedKeys(mergedRaw, firstResult.error);
|
|
215
278
|
if (removed.length > 0) {
|
|
216
279
|
const recovered = PluginConfigSchema.safeParse(cleaned);
|
|
217
280
|
if (recovered.success) {
|
|
218
|
-
|
|
219
|
-
|
|
281
|
+
const removedKeys = [...gatesStripped, ...removed];
|
|
282
|
+
const warning = warningForRemovedKeys(removedKeys);
|
|
283
|
+
advisoryWarn(`[opencode-swarm] ${warning}`);
|
|
284
|
+
return {
|
|
285
|
+
config: secure(recovered.data),
|
|
286
|
+
recovery: "stripped_keys",
|
|
287
|
+
removedKeys,
|
|
288
|
+
warnings: [warning]
|
|
289
|
+
};
|
|
220
290
|
}
|
|
221
291
|
}
|
|
222
292
|
if (rawUserConfig) {
|
|
223
|
-
const userSanitized = sanitizeSectionConfigs(rawUserConfig
|
|
293
|
+
const { result: userSanitized, strippedKeys: userGatesStripped } = sanitizeSectionConfigs(rawUserConfig);
|
|
224
294
|
const userParseResult = PluginConfigSchema.safeParse(userSanitized);
|
|
225
295
|
if (userParseResult.success) {
|
|
226
296
|
advisoryWarn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
|
|
227
|
-
return
|
|
297
|
+
return {
|
|
298
|
+
config: secure(userParseResult.data),
|
|
299
|
+
recovery: "user_only",
|
|
300
|
+
removedKeys: [...gatesStripped, ...userGatesStripped],
|
|
301
|
+
warnings: [
|
|
302
|
+
"Project config ignored due to validation errors; using user config."
|
|
303
|
+
]
|
|
304
|
+
};
|
|
228
305
|
}
|
|
229
306
|
const userStripped = stripUnrecognizedKeys(userSanitized, userParseResult.error);
|
|
230
307
|
if (userStripped.removed.length > 0) {
|
|
231
308
|
const userRecovered = PluginConfigSchema.safeParse(userStripped.cleaned);
|
|
232
309
|
if (userRecovered.success) {
|
|
233
310
|
advisoryWarn(`[opencode-swarm] Project config ignored; also ignored ${userStripped.removed.length} unrecognized user-config key(s): ${userStripped.removed.join(", ")}.`);
|
|
234
|
-
return
|
|
311
|
+
return {
|
|
312
|
+
config: secure(userRecovered.data),
|
|
313
|
+
recovery: "user_only",
|
|
314
|
+
removedKeys: [
|
|
315
|
+
...gatesStripped,
|
|
316
|
+
...userGatesStripped,
|
|
317
|
+
...userStripped.removed
|
|
318
|
+
],
|
|
319
|
+
warnings: [
|
|
320
|
+
"Project config ignored due to validation errors; using user config."
|
|
321
|
+
]
|
|
322
|
+
};
|
|
235
323
|
}
|
|
236
324
|
}
|
|
237
325
|
}
|
|
238
|
-
const offending = stripUnrecognizedKeys(mergedRaw,
|
|
326
|
+
const offending = stripUnrecognizedKeys(mergedRaw, firstResult.error).removed;
|
|
239
327
|
if (offending.length > 0) {
|
|
240
328
|
advisoryWarn(`[opencode-swarm] Merged config validation failed. Unrecognized key(s): ${offending.join(", ")}.`);
|
|
241
329
|
} else {
|
|
242
|
-
advisoryWarn("[opencode-swarm] Merged config validation failed:", formatZodIssues(
|
|
330
|
+
advisoryWarn("[opencode-swarm] Merged config validation failed:", formatZodIssues(firstResult.error));
|
|
243
331
|
}
|
|
244
332
|
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Falling back to conservative defaults with guardrails ENABLED. Fix the config file to restore custom configuration.");
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
return fullAuto.locked === true;
|
|
333
|
+
return {
|
|
334
|
+
config: PluginConfigSchema.parse({ guardrails: { enabled: true } }),
|
|
335
|
+
recovery: "guardrails_defaults",
|
|
336
|
+
removedKeys: [...gatesStripped],
|
|
337
|
+
warnings: [
|
|
338
|
+
"Config failed validation; using safe defaults with guardrails enabled."
|
|
339
|
+
]
|
|
340
|
+
};
|
|
255
341
|
}
|
|
256
342
|
function loadPluginConfig(directory) {
|
|
257
343
|
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
258
344
|
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
259
345
|
const userResult = loadRawConfigFromPath(userConfigPath);
|
|
260
346
|
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
261
|
-
const rawUserConfig = userResult.config;
|
|
262
|
-
const rawProjectConfig = projectResult.config;
|
|
263
347
|
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
264
348
|
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
265
|
-
|
|
266
|
-
if (rawProjectConfig) {
|
|
267
|
-
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
268
|
-
}
|
|
269
|
-
if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
|
|
270
|
-
const fullAutoRaw = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
|
|
271
|
-
mergedRaw = {
|
|
272
|
-
...mergedRaw,
|
|
273
|
-
full_auto: { ...fullAutoRaw, locked: true }
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
277
|
-
mergedRaw = sanitizeSectionConfigs(mergedRaw);
|
|
278
|
-
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
279
|
-
if (result.success) {
|
|
280
|
-
if (loadedFromFile && configHadErrors) {
|
|
281
|
-
return PluginConfigSchema.parse({
|
|
282
|
-
...mergedRaw,
|
|
283
|
-
guardrails: { enabled: true }
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
return result.data;
|
|
287
|
-
}
|
|
288
|
-
return parseConfigWithFallback(mergedRaw, rawUserConfig, result, loadedFromFile, configHadErrors);
|
|
349
|
+
return buildConfigWithMeta(userResult.config, projectResult.config, loadedFromFile, configHadErrors).config;
|
|
289
350
|
}
|
|
290
351
|
function loadPluginConfigWithMeta(directory) {
|
|
291
352
|
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
@@ -294,8 +355,15 @@ function loadPluginConfigWithMeta(directory) {
|
|
|
294
355
|
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
295
356
|
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
296
357
|
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
297
|
-
const config =
|
|
298
|
-
return {
|
|
358
|
+
const { config, recovery, removedKeys, warnings } = buildConfigWithMeta(userResult.config, projectResult.config, loadedFromFile, configHadErrors);
|
|
359
|
+
return {
|
|
360
|
+
config,
|
|
361
|
+
loadedFromFile,
|
|
362
|
+
configHadErrors,
|
|
363
|
+
recovery,
|
|
364
|
+
removedKeys,
|
|
365
|
+
warnings
|
|
366
|
+
};
|
|
299
367
|
}
|
|
300
368
|
async function loadRawConfigFromPathAsync(configPath) {
|
|
301
369
|
try {
|
|
@@ -335,25 +403,6 @@ async function loadRawConfigFromPathAsync(configPath) {
|
|
|
335
403
|
return { config: null, fileExisted: true, hadError: true };
|
|
336
404
|
}
|
|
337
405
|
}
|
|
338
|
-
function reduceParsedConfig(rawUserConfig, rawProjectConfig, loadedFromFile, configHadErrors) {
|
|
339
|
-
let mergedRaw = rawUserConfig ?? {};
|
|
340
|
-
if (rawProjectConfig) {
|
|
341
|
-
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
342
|
-
}
|
|
343
|
-
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
344
|
-
mergedRaw = sanitizeSectionConfigs(mergedRaw);
|
|
345
|
-
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
346
|
-
if (result.success) {
|
|
347
|
-
if (loadedFromFile && configHadErrors) {
|
|
348
|
-
return PluginConfigSchema.parse({
|
|
349
|
-
...mergedRaw,
|
|
350
|
-
guardrails: { enabled: true }
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return result.data;
|
|
354
|
-
}
|
|
355
|
-
return parseConfigWithFallback(mergedRaw, rawUserConfig, result, loadedFromFile, configHadErrors);
|
|
356
|
-
}
|
|
357
406
|
async function loadPluginConfigWithMetaAsync(directory) {
|
|
358
407
|
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
359
408
|
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
@@ -363,8 +412,15 @@ async function loadPluginConfigWithMetaAsync(directory) {
|
|
|
363
412
|
]);
|
|
364
413
|
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
365
414
|
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
366
|
-
const config =
|
|
367
|
-
return {
|
|
415
|
+
const { config, recovery, removedKeys, warnings } = buildConfigWithMeta(userResult.config, projectResult.config, loadedFromFile, configHadErrors);
|
|
416
|
+
return {
|
|
417
|
+
config,
|
|
418
|
+
loadedFromFile,
|
|
419
|
+
configHadErrors,
|
|
420
|
+
recovery,
|
|
421
|
+
removedKeys,
|
|
422
|
+
warnings
|
|
423
|
+
};
|
|
368
424
|
}
|
|
369
425
|
function loadAgentPrompt(agentName) {
|
|
370
426
|
const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
|