opencode-swarm 7.119.0 → 7.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{config-doctor-p73yfdz0.js → config-doctor-404c8mf8.js} +3 -3
- package/dist/cli/config-fingerprint-v22xh5a2.js +10 -0
- package/dist/cli/{core-gf1t2jmn.js → core-xnwasfpc.js} +1 -1
- package/dist/cli/curation-policy-re2kaysb.js +25 -0
- package/dist/cli/{curator-5xfkejsq.js → curator-852s9p0s.js} +30 -23
- package/dist/cli/{curator-drift-nn71xfz6.js → curator-drift-6b2fjsgr.js} +3 -2
- package/dist/cli/curator-llm-factory-5gnvggd7.js +50 -0
- package/dist/cli/{evidence-summary-service-cy3k37je.js → evidence-summary-service-jtx5q3r6.js} +9 -7
- package/dist/cli/{gate-evidence-j2m4cd85.js → gate-evidence-tbpezk28.js} +5 -5
- package/dist/cli/guardrail-explain-wadbhh06.js +51 -0
- package/dist/cli/{guardrail-log-qjqgg7pz.js → guardrail-log-dhpp0kmn.js} +4 -4
- package/dist/cli/{hive-promoter-03m8pwg6.js → hive-promoter-7fn55bgw.js} +30 -23
- package/dist/cli/index-0e1v02hq.js +343 -0
- package/dist/cli/{index-jbdgpqpj.js → index-0k4r8111.js} +28 -10
- package/dist/cli/index-0mbmfmt0.js +349 -0
- package/dist/cli/{index-7pspktns.js → index-1smvzr78.js} +2 -2
- package/dist/cli/{index-5bcrp3bc.js → index-22e127jw.js} +3 -3
- package/dist/cli/{index-pjhfe7c3.js → index-3366gs26.js} +43 -7
- package/dist/cli/{index-wpgv6hht.js → index-3jvqw5hy.js} +13 -26
- package/dist/cli/{index-bfwt3abw.js → index-4rhcth7a.js} +1 -1
- package/dist/cli/{index-n6fa2hhk.js → index-51gg9947.js} +1 -1
- package/dist/cli/index-69ms9ny1.js +261 -0
- package/dist/cli/{index-42cx6g2h.js → index-76kvm9z5.js} +1 -1
- package/dist/cli/{index-r2ysfdet.js → index-78b192v7.js} +7 -7
- package/dist/cli/{index-vwvkpg0d.js → index-816rkp4y.js} +3 -92
- package/dist/cli/index-89s4mhmw.js +6 -0
- package/dist/cli/index-9xpgytj9.js +386 -0
- package/dist/cli/index-ae75rja9.js +26 -0
- package/dist/cli/{index-mr3pnc4h.js → index-bndd885n.js} +1926 -2005
- package/dist/cli/index-fndvr7fr.js +135 -0
- package/dist/cli/index-gnp9x6se.js +93 -0
- package/dist/cli/{index-0pfqcjp6.js → index-h07wn84h.js} +1 -1
- package/dist/cli/{index-p45sshb5.js → index-j5xv8zbp.js} +17 -352
- package/dist/cli/{index-g2wrs7jy.js → index-kyvg2cmp.js} +1 -1
- package/dist/cli/{index-5hze8ztj.js → index-nhgw7de8.js} +7 -5
- package/dist/cli/{index-abjjn4yh.js → index-qg11yf8q.js} +4 -4
- package/dist/cli/index-rtry5xyf.js +27 -0
- package/dist/cli/{index-qx5z58a7.js → index-rywkdacj.js} +93 -11
- package/dist/cli/{index-pdk1n8yh.js → index-tftv9xh9.js} +44 -2
- package/dist/cli/{index-11q1t6g0.js → index-vqcgmy4y.js} +1 -1
- package/dist/cli/{index-05ee8pws.js → index-w64n2y0f.js} +32 -25
- package/dist/cli/{index-q4kgsvdd.js → index-wtn8wf7h.js} +1 -1
- package/dist/cli/{index-bcg89pt8.js → index-zvae4m8g.js} +1 -1
- package/dist/cli/index.js +30 -23
- package/dist/cli/{knowledge-escalator-gt1q632m.js → knowledge-escalator-0ejqs8c9.js} +9 -8
- package/dist/cli/{knowledge-events-0bjdtdsb.js → knowledge-events-wx9e37ba.js} +6 -5
- package/dist/cli/knowledge-link-sy85fw3w.js +37 -0
- package/dist/cli/{knowledge-store-fm2300xf.js → knowledge-store-rnajx33a.js} +17 -6
- package/dist/cli/{knowledge-validator-91kx3eqv.js → knowledge-validator-1f4mkzmc.js} +11 -7
- package/dist/cli/{pending-delegations-9cn17983.js → pending-delegations-0z3znsec.js} +5 -5
- package/dist/cli/{pr-subscriptions-8a6jwek2.js → pr-subscriptions-x653151h.js} +4 -4
- package/dist/cli/scan-cursor-qapm0sxj.js +25 -0
- package/dist/cli/{schema-7c484wjz.js → schema-vwxpsk6j.js} +2 -2
- package/dist/cli/{scope-persistence-fm0gzhnk.js → scope-persistence-mfr36yc9.js} +2 -2
- package/dist/cli/{skill-generator-3f8dqjj2.js → skill-generator-tkv7s12g.js} +12 -9
- package/dist/config/schema.d.ts +8 -8
- package/dist/hooks/curator-postmortem.d.ts +6 -4
- package/dist/hooks/curator.d.ts +5 -1
- package/dist/hooks/hive-promoter.d.ts +3 -1
- package/dist/hooks/knowledge-store.d.ts +54 -1
- package/dist/hooks/knowledge-types.d.ts +107 -2
- package/dist/hooks/knowledge-validator.d.ts +25 -4
- package/dist/index.js +387 -384
- package/dist/knowledge/curation-policy.d.ts +125 -0
- package/dist/knowledge/family-manifest.d.ts +30 -2
- package/dist/knowledge/family-migration.d.ts +16 -4
- package/dist/knowledge/scan-cursor.d.ts +107 -0
- package/dist/knowledge/worktree-identity.d.ts +39 -0
- package/dist/memory/schema.d.ts +1 -1
- package/dist/services/knowledge-diagnostics.d.ts +29 -0
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-rwwnmdvk.js +0 -43
- package/dist/cli/guardrail-explain-kacpc2zm.js +0 -44
- /package/dist/cli/{index-vvk2g744.js → index-7g4c7s5r.js} +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_WORKTREE_ISOLATION_CONFIG,
|
|
4
|
+
ExternalSkillsConfigSchema,
|
|
5
|
+
GATE_CONFIG_KNOWN_SECTION_KEYS,
|
|
6
|
+
GateConfigSchema,
|
|
7
|
+
PluginConfigSchema,
|
|
8
|
+
resolveExternalSkillsConfig
|
|
9
|
+
} from "./index-tftv9xh9.js";
|
|
10
|
+
import {
|
|
11
|
+
advisoryWarn
|
|
12
|
+
} from "./index-ye3fndsw.js";
|
|
13
|
+
import {
|
|
14
|
+
deepMerge
|
|
15
|
+
} from "./index-p0arc26j.js";
|
|
16
|
+
// src/config/loader.ts
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as fsPromises from "fs/promises";
|
|
19
|
+
import * as os from "os";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
var CONFIG_FILENAME = "opencode-swarm.json";
|
|
22
|
+
var PROMPTS_DIR_NAME = "opencode-swarm";
|
|
23
|
+
var MAX_CONFIG_FILE_BYTES = 102400;
|
|
24
|
+
function getUserConfigDir() {
|
|
25
|
+
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
26
|
+
}
|
|
27
|
+
function loadRawConfigFromPath(configPath) {
|
|
28
|
+
try {
|
|
29
|
+
const stats = fs.statSync(configPath);
|
|
30
|
+
if (stats.size > MAX_CONFIG_FILE_BYTES) {
|
|
31
|
+
advisoryWarn(`[opencode-swarm] Config file too large (max 100 KB): ${configPath}`);
|
|
32
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
33
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
34
|
+
}
|
|
35
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
36
|
+
if (content.length > MAX_CONFIG_FILE_BYTES) {
|
|
37
|
+
advisoryWarn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
|
|
38
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
39
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
40
|
+
}
|
|
41
|
+
let sanitizedContent = content;
|
|
42
|
+
if (content.charCodeAt(0) === 65279) {
|
|
43
|
+
sanitizedContent = content.slice(1);
|
|
44
|
+
}
|
|
45
|
+
const rawConfig = JSON.parse(sanitizedContent);
|
|
46
|
+
if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
|
|
47
|
+
advisoryWarn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
|
|
48
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config format invalid. Falling back to safe defaults with guardrails ENABLED.");
|
|
49
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
config: rawConfig,
|
|
53
|
+
fileExisted: true,
|
|
54
|
+
hadError: false
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const isFileNotFoundError = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
58
|
+
if (!isFileNotFoundError) {
|
|
59
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
60
|
+
advisoryWarn(`[opencode-swarm] \u26A0\uFE0F CONFIG LOAD FAILURE \u2014 config exists at ${configPath} but could not be loaded: ${errorMessage}`);
|
|
61
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config load failed. Falling back to safe defaults with guardrails ENABLED.");
|
|
62
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
63
|
+
}
|
|
64
|
+
return { config: null, fileExisted: false, hadError: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function migratePresetsConfig(raw) {
|
|
68
|
+
if (raw.presets && typeof raw.presets === "object" && !raw.agents) {
|
|
69
|
+
const presetName = raw.preset || "remote";
|
|
70
|
+
const presets = raw.presets;
|
|
71
|
+
const activePreset = presets[presetName] || Object.values(presets)[0];
|
|
72
|
+
if (activePreset && typeof activePreset === "object") {
|
|
73
|
+
const migrated = { ...raw, agents: activePreset };
|
|
74
|
+
delete migrated.preset;
|
|
75
|
+
delete migrated.presets;
|
|
76
|
+
delete migrated.swarm_mode;
|
|
77
|
+
advisoryWarn("[opencode-swarm] Migrated v6.12 presets config to agents format. Consider updating your opencode-swarm.json.");
|
|
78
|
+
return migrated;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
function sanitizeExternalSkillsConfig(raw) {
|
|
84
|
+
if (!("external_skills" in raw) || raw.external_skills === undefined) {
|
|
85
|
+
return raw;
|
|
86
|
+
}
|
|
87
|
+
const esResult = ExternalSkillsConfigSchema.safeParse(raw.external_skills);
|
|
88
|
+
if (esResult.success) {
|
|
89
|
+
return {
|
|
90
|
+
...raw,
|
|
91
|
+
external_skills: resolveExternalSkillsConfig(esResult.data)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
advisoryWarn("[opencode-swarm] external_skills config validation failed:", esResult.error.format());
|
|
95
|
+
advisoryWarn("[opencode-swarm] External skills curation disabled due to invalid config. Fix the external_skills section to enable it.");
|
|
96
|
+
const cleaned = { ...raw };
|
|
97
|
+
delete cleaned.external_skills;
|
|
98
|
+
return cleaned;
|
|
99
|
+
}
|
|
100
|
+
function sanitizeGatesConfig(raw) {
|
|
101
|
+
if (!("gates" in raw) || raw.gates === undefined) {
|
|
102
|
+
return raw;
|
|
103
|
+
}
|
|
104
|
+
if (typeof raw.gates !== "object" || raw.gates === null || Array.isArray(raw.gates)) {
|
|
105
|
+
advisoryWarn("[opencode-swarm] gates config validation failed: expected an object. Quality gates will use defaults; other config sections remain active.");
|
|
106
|
+
const cleaned2 = { ...raw };
|
|
107
|
+
delete cleaned2.gates;
|
|
108
|
+
return cleaned2;
|
|
109
|
+
}
|
|
110
|
+
const gateSchemas = GateConfigSchema.shape;
|
|
111
|
+
const cleanedGates = { ...raw.gates };
|
|
112
|
+
for (const [key, value] of Object.entries(cleanedGates)) {
|
|
113
|
+
const schema = gateSchemas[key];
|
|
114
|
+
if (!schema) {
|
|
115
|
+
advisoryWarn(`[opencode-swarm] Unknown gates config section "gates.${key}" ignored. Other config sections remain active.`);
|
|
116
|
+
delete cleanedGates[key];
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const knownFields = GATE_CONFIG_KNOWN_SECTION_KEYS[key];
|
|
120
|
+
let sectionValue = value;
|
|
121
|
+
if (knownFields && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
122
|
+
const cleanedSection = { ...value };
|
|
123
|
+
const knownFieldSet = new Set(knownFields);
|
|
124
|
+
for (const fieldName of Object.keys(cleanedSection)) {
|
|
125
|
+
if (!knownFieldSet.has(fieldName)) {
|
|
126
|
+
advisoryWarn(`[opencode-swarm] Unknown gates config key "gates.${key}.${fieldName}" ignored. Other config sections remain active.`);
|
|
127
|
+
delete cleanedSection[fieldName];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
sectionValue = cleanedSection;
|
|
131
|
+
cleanedGates[key] = cleanedSection;
|
|
132
|
+
}
|
|
133
|
+
const sectionResult = schema.safeParse(sectionValue);
|
|
134
|
+
if (!sectionResult.success) {
|
|
135
|
+
advisoryWarn(`[opencode-swarm] gates.${key} config validation failed; that gate section will use defaults and other config sections remain active:`, sectionResult.error.format());
|
|
136
|
+
delete cleanedGates[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const gatesResult = GateConfigSchema.safeParse(cleanedGates);
|
|
140
|
+
if (gatesResult.success) {
|
|
141
|
+
return {
|
|
142
|
+
...raw,
|
|
143
|
+
gates: gatesResult.data
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
advisoryWarn("[opencode-swarm] gates config validation failed after section cleanup; quality gates will use defaults and other config sections remain active:", gatesResult.error.format());
|
|
147
|
+
const cleaned = { ...raw };
|
|
148
|
+
delete cleaned.gates;
|
|
149
|
+
return cleaned;
|
|
150
|
+
}
|
|
151
|
+
function sanitizeSectionConfigs(raw) {
|
|
152
|
+
return sanitizeGatesConfig(sanitizeExternalSkillsConfig(raw));
|
|
153
|
+
}
|
|
154
|
+
function stripUnrecognizedKeys(raw, error) {
|
|
155
|
+
const removed = [];
|
|
156
|
+
const cleaned = structuredClone(raw);
|
|
157
|
+
for (const issue of error.issues) {
|
|
158
|
+
if (issue.code !== "unrecognized_keys")
|
|
159
|
+
continue;
|
|
160
|
+
const keys = issue.keys ?? [];
|
|
161
|
+
let node = cleaned;
|
|
162
|
+
for (const seg of issue.path) {
|
|
163
|
+
if (node && typeof node === "object") {
|
|
164
|
+
node = node[seg];
|
|
165
|
+
} else {
|
|
166
|
+
node = undefined;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!node || typeof node !== "object")
|
|
171
|
+
continue;
|
|
172
|
+
for (const key of keys) {
|
|
173
|
+
if (key in node) {
|
|
174
|
+
delete node[key];
|
|
175
|
+
const dotted = [...issue.path.map(String), key].join(".");
|
|
176
|
+
removed.push(dotted);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { cleaned, removed };
|
|
181
|
+
}
|
|
182
|
+
function parseConfigWithFallback(mergedRaw, rawUserConfig, failedResult, loadedFromFile, configHadErrors) {
|
|
183
|
+
const result = failedResult;
|
|
184
|
+
const secure = (config) => loadedFromFile && configHadErrors ? PluginConfigSchema.parse({
|
|
185
|
+
...config,
|
|
186
|
+
guardrails: { enabled: true }
|
|
187
|
+
}) : config;
|
|
188
|
+
const { cleaned, removed } = stripUnrecognizedKeys(mergedRaw, result.error);
|
|
189
|
+
if (removed.length > 0) {
|
|
190
|
+
const recovered = PluginConfigSchema.safeParse(cleaned);
|
|
191
|
+
if (recovered.success) {
|
|
192
|
+
advisoryWarn(`[opencode-swarm] Ignored ${removed.length} unrecognized config key(s): ${removed.join(", ")}. The rest of your configuration was preserved. Fix or remove these keys.`);
|
|
193
|
+
return secure(recovered.data);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (rawUserConfig) {
|
|
197
|
+
const userSanitized = sanitizeSectionConfigs(rawUserConfig ?? {});
|
|
198
|
+
const userParseResult = PluginConfigSchema.safeParse(userSanitized);
|
|
199
|
+
if (userParseResult.success) {
|
|
200
|
+
advisoryWarn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
|
|
201
|
+
return secure(userParseResult.data);
|
|
202
|
+
}
|
|
203
|
+
const userStripped = stripUnrecognizedKeys(userSanitized, userParseResult.error);
|
|
204
|
+
if (userStripped.removed.length > 0) {
|
|
205
|
+
const userRecovered = PluginConfigSchema.safeParse(userStripped.cleaned);
|
|
206
|
+
if (userRecovered.success) {
|
|
207
|
+
advisoryWarn(`[opencode-swarm] Project config ignored; also ignored ${userStripped.removed.length} unrecognized user-config key(s): ${userStripped.removed.join(", ")}.`);
|
|
208
|
+
return secure(userRecovered.data);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const offending = stripUnrecognizedKeys(mergedRaw, result.error).removed;
|
|
213
|
+
if (offending.length > 0) {
|
|
214
|
+
advisoryWarn(`[opencode-swarm] Merged config validation failed. Unrecognized key(s): ${offending.join(", ")}.`);
|
|
215
|
+
} else {
|
|
216
|
+
advisoryWarn("[opencode-swarm] Merged config validation failed:", result.error.format());
|
|
217
|
+
}
|
|
218
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Falling back to conservative defaults with guardrails ENABLED. Fix the config file to restore custom configuration.");
|
|
219
|
+
return PluginConfigSchema.parse({ guardrails: { enabled: true } });
|
|
220
|
+
}
|
|
221
|
+
function rawFullAutoLocked(raw) {
|
|
222
|
+
if (!raw || typeof raw !== "object")
|
|
223
|
+
return false;
|
|
224
|
+
const fullAuto = raw.full_auto;
|
|
225
|
+
if (!fullAuto || typeof fullAuto !== "object" || Array.isArray(fullAuto)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return fullAuto.locked === true;
|
|
229
|
+
}
|
|
230
|
+
function loadPluginConfig(directory) {
|
|
231
|
+
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
232
|
+
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
233
|
+
const userResult = loadRawConfigFromPath(userConfigPath);
|
|
234
|
+
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
235
|
+
const rawUserConfig = userResult.config;
|
|
236
|
+
const rawProjectConfig = projectResult.config;
|
|
237
|
+
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
238
|
+
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
239
|
+
let mergedRaw = rawUserConfig ?? {};
|
|
240
|
+
if (rawProjectConfig) {
|
|
241
|
+
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
242
|
+
}
|
|
243
|
+
if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
|
|
244
|
+
const fullAutoRaw = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
|
|
245
|
+
mergedRaw = {
|
|
246
|
+
...mergedRaw,
|
|
247
|
+
full_auto: { ...fullAutoRaw, locked: true }
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
251
|
+
mergedRaw = sanitizeSectionConfigs(mergedRaw);
|
|
252
|
+
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
253
|
+
if (result.success) {
|
|
254
|
+
if (loadedFromFile && configHadErrors) {
|
|
255
|
+
return PluginConfigSchema.parse({
|
|
256
|
+
...mergedRaw,
|
|
257
|
+
guardrails: { enabled: true }
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return result.data;
|
|
261
|
+
}
|
|
262
|
+
return parseConfigWithFallback(mergedRaw, rawUserConfig, result, loadedFromFile, configHadErrors);
|
|
263
|
+
}
|
|
264
|
+
function loadPluginConfigWithMeta(directory) {
|
|
265
|
+
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
266
|
+
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
267
|
+
const userResult = loadRawConfigFromPath(userConfigPath);
|
|
268
|
+
const projectResult = loadRawConfigFromPath(projectConfigPath);
|
|
269
|
+
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
270
|
+
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
271
|
+
const config = loadPluginConfig(directory);
|
|
272
|
+
return { config, loadedFromFile, configHadErrors };
|
|
273
|
+
}
|
|
274
|
+
async function loadRawConfigFromPathAsync(configPath) {
|
|
275
|
+
try {
|
|
276
|
+
const stats = await fsPromises.stat(configPath);
|
|
277
|
+
if (stats.size > MAX_CONFIG_FILE_BYTES) {
|
|
278
|
+
advisoryWarn(`[opencode-swarm] Config file too large (max 100 KB): ${configPath}`);
|
|
279
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
280
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
281
|
+
}
|
|
282
|
+
const content = await fsPromises.readFile(configPath, "utf-8");
|
|
283
|
+
if (content.length > MAX_CONFIG_FILE_BYTES) {
|
|
284
|
+
advisoryWarn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
|
|
285
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
|
|
286
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
287
|
+
}
|
|
288
|
+
let sanitizedContent = content;
|
|
289
|
+
if (content.charCodeAt(0) === 65279) {
|
|
290
|
+
sanitizedContent = content.slice(1);
|
|
291
|
+
}
|
|
292
|
+
const rawConfig = JSON.parse(sanitizedContent);
|
|
293
|
+
if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
|
|
294
|
+
advisoryWarn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
|
|
295
|
+
advisoryWarn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config format invalid. Falling back to safe defaults with guardrails ENABLED.");
|
|
296
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
config: rawConfig,
|
|
300
|
+
fileExisted: true,
|
|
301
|
+
hadError: false
|
|
302
|
+
};
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const code = error.code;
|
|
305
|
+
if (code === "ENOENT") {
|
|
306
|
+
return { config: null, fileExisted: false, hadError: false };
|
|
307
|
+
}
|
|
308
|
+
advisoryWarn(`[opencode-swarm] Failed to load config from ${configPath}:`, error instanceof Error ? error.message : String(error));
|
|
309
|
+
return { config: null, fileExisted: true, hadError: true };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function reduceParsedConfig(rawUserConfig, rawProjectConfig, loadedFromFile, configHadErrors) {
|
|
313
|
+
let mergedRaw = rawUserConfig ?? {};
|
|
314
|
+
if (rawProjectConfig) {
|
|
315
|
+
mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
|
|
316
|
+
}
|
|
317
|
+
mergedRaw = migratePresetsConfig(mergedRaw);
|
|
318
|
+
mergedRaw = sanitizeSectionConfigs(mergedRaw);
|
|
319
|
+
const result = PluginConfigSchema.safeParse(mergedRaw);
|
|
320
|
+
if (result.success) {
|
|
321
|
+
if (loadedFromFile && configHadErrors) {
|
|
322
|
+
return PluginConfigSchema.parse({
|
|
323
|
+
...mergedRaw,
|
|
324
|
+
guardrails: { enabled: true }
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return result.data;
|
|
328
|
+
}
|
|
329
|
+
return parseConfigWithFallback(mergedRaw, rawUserConfig, result, loadedFromFile, configHadErrors);
|
|
330
|
+
}
|
|
331
|
+
async function loadPluginConfigWithMetaAsync(directory) {
|
|
332
|
+
const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
|
|
333
|
+
const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
|
|
334
|
+
const [userResult, projectResult] = await Promise.all([
|
|
335
|
+
loadRawConfigFromPathAsync(userConfigPath),
|
|
336
|
+
loadRawConfigFromPathAsync(projectConfigPath)
|
|
337
|
+
]);
|
|
338
|
+
const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
|
|
339
|
+
const configHadErrors = userResult.hadError || projectResult.hadError;
|
|
340
|
+
const config = reduceParsedConfig(userResult.config, projectResult.config, loadedFromFile, configHadErrors);
|
|
341
|
+
return { config, loadedFromFile, configHadErrors };
|
|
342
|
+
}
|
|
343
|
+
function loadAgentPrompt(agentName) {
|
|
344
|
+
const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
|
|
345
|
+
const result = {};
|
|
346
|
+
const promptPath = path.join(promptsDir, `${agentName}.md`);
|
|
347
|
+
if (fs.existsSync(promptPath)) {
|
|
348
|
+
try {
|
|
349
|
+
result.prompt = fs.readFileSync(promptPath, "utf-8");
|
|
350
|
+
} catch (error) {
|
|
351
|
+
advisoryWarn(`[opencode-swarm] Error reading prompt file ${promptPath}:`, error instanceof Error ? error.message : String(error));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const appendPromptPath = path.join(promptsDir, `${agentName}_append.md`);
|
|
355
|
+
if (fs.existsSync(appendPromptPath)) {
|
|
356
|
+
try {
|
|
357
|
+
result.appendPrompt = fs.readFileSync(appendPromptPath, "utf-8");
|
|
358
|
+
} catch (error) {
|
|
359
|
+
advisoryWarn(`[opencode-swarm] Error reading append prompt ${appendPromptPath}:`, error instanceof Error ? error.message : String(error));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
// src/config/worktree-isolation-config.ts
|
|
365
|
+
function resolveWorktreeIsolationConfig(config) {
|
|
366
|
+
if (config.worktree) {
|
|
367
|
+
return { ...DEFAULT_WORKTREE_ISOLATION_CONFIG, ...config.worktree };
|
|
368
|
+
}
|
|
369
|
+
const lean = config.turbo?.strategy === "lean" ? config.turbo.lean : undefined;
|
|
370
|
+
if (lean?.worktree_isolation) {
|
|
371
|
+
return {
|
|
372
|
+
...DEFAULT_WORKTREE_ISOLATION_CONFIG,
|
|
373
|
+
policy: "auto",
|
|
374
|
+
merge_strategy: lean.merge_strategy ?? "merge",
|
|
375
|
+
worktree_dir: lean.worktree_dir,
|
|
376
|
+
deps_strategy: lean.deps_strategy ?? "skip",
|
|
377
|
+
runtime_isolation: lean.runtime_isolation ?? DEFAULT_WORKTREE_ISOLATION_CONFIG.runtime_isolation
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return DEFAULT_WORKTREE_ISOLATION_CONFIG;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/config/index.ts
|
|
384
|
+
var _internals = { loadPluginConfigWithMeta };
|
|
385
|
+
|
|
386
|
+
export { loadPluginConfig, loadPluginConfigWithMeta, loadPluginConfigWithMetaAsync, loadAgentPrompt, resolveWorktreeIsolationConfig, _internals };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/knowledge/hive-paths.ts
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
function resolveHiveDataDir() {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
const home = process.env.HOME || os.homedir();
|
|
8
|
+
if (platform === "win32") {
|
|
9
|
+
return path.join(process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "opencode-swarm", "Data");
|
|
10
|
+
}
|
|
11
|
+
if (platform === "darwin") {
|
|
12
|
+
return path.join(home, "Library", "Application Support", "opencode-swarm");
|
|
13
|
+
}
|
|
14
|
+
return path.join(process.env.XDG_DATA_HOME || path.join(home, ".local", "share"), "opencode-swarm");
|
|
15
|
+
}
|
|
16
|
+
function resolveHiveKnowledgePath() {
|
|
17
|
+
return path.join(resolveHiveDataDir(), "shared-learnings.jsonl");
|
|
18
|
+
}
|
|
19
|
+
function resolveHiveRejectedPath() {
|
|
20
|
+
return path.join(resolveHiveDataDir(), "shared-learnings-rejected.jsonl");
|
|
21
|
+
}
|
|
22
|
+
function resolveHiveEventsPath() {
|
|
23
|
+
return path.join(resolveHiveDataDir(), "shared-knowledge-events.jsonl");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { resolveHiveDataDir, resolveHiveKnowledgePath, resolveHiveRejectedPath, resolveHiveEventsPath };
|