@heretyc/subagent-mcp 2.12.3 → 2.12.5-beta.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/README.md +193 -163
- package/dist/advanced-ruleset.py +67 -67
- package/dist/concurrency.js +325 -11
- package/dist/config-scaffold.js +2 -2
- package/dist/drivers.js +340 -30
- package/dist/effort.js +10 -5
- package/dist/global-concurrency.jsonc +39 -34
- package/dist/global-subagent-mcp-config.jsonc +39 -0
- package/dist/index.js +242 -32
- package/dist/pending-permissions.js +193 -0
- package/dist/permission-classes.json +36 -0
- package/dist/permission-engine.js +253 -0
- package/dist/routing-table.json +3561 -3561
- package/dist/ruleset-scaffold.js +1 -1
- package/dist/setup.js +1 -1
- package/dist/status-helpers.js +10 -2
- package/dist/wait-helpers.js +3 -0
- package/dist/zombie.js +16 -3
- package/package.json +69 -69
- package/scripts/postinstall.mjs +102 -102
package/dist/concurrency.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { platform } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import { dirname, join, parse as parsePath, resolve } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { CONCURRENCY_SCAFFOLD } from "./config-scaffold.js";
|
|
6
7
|
import { cullStaleSlots, ZOMBIE_FORCE_GRACE_MS, ZOMBIE_LIVE_IDLE_MS, ZOMBIE_TERMINAL_IDLE_MS, buildProcessTreeKillCommands, drainZombieIntents, drainZombieReports, parseSlotMetadata, readSlotMetadata, slotPathForAgent, writeSlotMetadata, } from "./zombie.js";
|
|
7
8
|
export const DEFAULT_CAP = 20;
|
|
8
9
|
export const MIN_CAP = 10;
|
|
9
10
|
export const DEFAULT_CHECK_FOR_UPDATES = true;
|
|
10
|
-
export const
|
|
11
|
+
export const DEFAULT_PERMISSIONS_CEILING = "auto";
|
|
12
|
+
export const DEFAULT_ESCALATION = "irreversible-only";
|
|
13
|
+
export const DEFAULT_STRICT_READ_PARITY = "warn";
|
|
14
|
+
export const CONFIG_FILENAME = "global-subagent-mcp-config.jsonc";
|
|
15
|
+
export const LEGACY_CONFIG_FILENAME = "global-concurrency.jsonc";
|
|
11
16
|
export { cullStaleSlots, ZOMBIE_FORCE_GRACE_MS, ZOMBIE_LIVE_IDLE_MS, ZOMBIE_TERMINAL_IDLE_MS, buildProcessTreeKillCommands, drainZombieIntents, drainZombieReports, parseSlotMetadata, readSlotMetadata, slotPathForAgent, writeSlotMetadata, };
|
|
12
17
|
export function clampCap(raw) {
|
|
13
18
|
if (!Number.isInteger(raw))
|
|
@@ -22,6 +27,13 @@ export function clampCap(raw) {
|
|
|
22
27
|
export function stripJsoncComments(text) {
|
|
23
28
|
return text.replace(/^\s*\/\/.*$/gm, "");
|
|
24
29
|
}
|
|
30
|
+
function parseJsonObject(text) {
|
|
31
|
+
const parsed = JSON.parse(stripJsoncComments(text));
|
|
32
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33
|
+
throw new Error("expected JSON object");
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
25
37
|
export function parseConcurrencyConfig(text) {
|
|
26
38
|
let raw;
|
|
27
39
|
try {
|
|
@@ -42,34 +54,336 @@ export function parseCheckForUpdatesConfig(text) {
|
|
|
42
54
|
return DEFAULT_CHECK_FOR_UPDATES;
|
|
43
55
|
}
|
|
44
56
|
}
|
|
57
|
+
export function parsePermissionsCeilingConfig(text) {
|
|
58
|
+
try {
|
|
59
|
+
const raw = parseJsonObject(text).permissionsCeiling;
|
|
60
|
+
if (raw === "yolo" || raw === "manual" || raw === "auto")
|
|
61
|
+
return raw;
|
|
62
|
+
return raw === undefined ? DEFAULT_PERMISSIONS_CEILING : "manual";
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "manual";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function parseEscalationConfig(text) {
|
|
69
|
+
try {
|
|
70
|
+
const raw = parseJsonObject(text).escalation;
|
|
71
|
+
return raw === "irreversible-only" || raw === "off" ? raw : DEFAULT_ESCALATION;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return DEFAULT_ESCALATION;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function parseStrictReadParityConfig(text) {
|
|
78
|
+
try {
|
|
79
|
+
const raw = parseJsonObject(text).strictReadParity;
|
|
80
|
+
return raw === "warn" || raw === "off" ? raw : DEFAULT_STRICT_READ_PARITY;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return DEFAULT_STRICT_READ_PARITY;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
45
86
|
export function defaultConfigPath() {
|
|
46
87
|
return fileURLToPath(new URL("./" + CONFIG_FILENAME, import.meta.url));
|
|
47
88
|
}
|
|
89
|
+
export function legacyConfigPath(path = defaultConfigPath()) {
|
|
90
|
+
return join(dirname(path), LEGACY_CONFIG_FILENAME);
|
|
91
|
+
}
|
|
92
|
+
export function resolveGlobalConfigPath(path = defaultConfigPath()) {
|
|
93
|
+
if (existsSync(path))
|
|
94
|
+
return { path, usedLegacyPath: false };
|
|
95
|
+
const legacyPath = legacyConfigPath(path);
|
|
96
|
+
if (existsSync(legacyPath))
|
|
97
|
+
return { path: legacyPath, usedLegacyPath: true };
|
|
98
|
+
return { path, usedLegacyPath: false };
|
|
99
|
+
}
|
|
48
100
|
export function ensureConcurrencyConfig(path = defaultConfigPath()) {
|
|
49
101
|
try {
|
|
50
|
-
|
|
102
|
+
const resolved = resolveGlobalConfigPath(path);
|
|
103
|
+
if (existsSync(resolved.path))
|
|
51
104
|
return;
|
|
52
105
|
writeFileSync(path, CONCURRENCY_SCAFFOLD);
|
|
53
106
|
}
|
|
54
107
|
catch { }
|
|
55
108
|
}
|
|
56
|
-
export function
|
|
109
|
+
export function readGlobalConfig(path = defaultConfigPath()) {
|
|
57
110
|
try {
|
|
58
111
|
ensureConcurrencyConfig(path);
|
|
59
|
-
|
|
112
|
+
const resolved = resolveGlobalConfigPath(path);
|
|
113
|
+
const text = readFileSync(resolved.path, "utf8");
|
|
114
|
+
parseJsonObject(text);
|
|
115
|
+
return {
|
|
116
|
+
globalConcurrentSubagents: parseConcurrencyConfig(text),
|
|
117
|
+
checkForUpdates: parseCheckForUpdatesConfig(text),
|
|
118
|
+
permissionsCeiling: parsePermissionsCeilingConfig(text),
|
|
119
|
+
escalation: parseEscalationConfig(text),
|
|
120
|
+
strictReadParity: parseStrictReadParityConfig(text),
|
|
121
|
+
path: resolved.path,
|
|
122
|
+
usedLegacyPath: resolved.usedLegacyPath,
|
|
123
|
+
parseFailure: null,
|
|
124
|
+
};
|
|
60
125
|
}
|
|
61
|
-
catch {
|
|
62
|
-
return
|
|
126
|
+
catch (e) {
|
|
127
|
+
return {
|
|
128
|
+
globalConcurrentSubagents: DEFAULT_CAP,
|
|
129
|
+
checkForUpdates: DEFAULT_CHECK_FOR_UPDATES,
|
|
130
|
+
permissionsCeiling: "manual",
|
|
131
|
+
escalation: DEFAULT_ESCALATION,
|
|
132
|
+
strictReadParity: DEFAULT_STRICT_READ_PARITY,
|
|
133
|
+
path,
|
|
134
|
+
usedLegacyPath: false,
|
|
135
|
+
parseFailure: e instanceof Error ? e.message : String(e),
|
|
136
|
+
};
|
|
63
137
|
}
|
|
64
138
|
}
|
|
139
|
+
export function readGlobalCap(path = defaultConfigPath()) {
|
|
140
|
+
return readGlobalConfig(path).globalConcurrentSubagents;
|
|
141
|
+
}
|
|
65
142
|
export function readCheckForUpdates(path = defaultConfigPath()) {
|
|
143
|
+
return readGlobalConfig(path).checkForUpdates;
|
|
144
|
+
}
|
|
145
|
+
export function readPermissionsCeiling(path = defaultConfigPath()) {
|
|
146
|
+
return readGlobalConfig(path).permissionsCeiling;
|
|
147
|
+
}
|
|
148
|
+
export function readEscalation(path = defaultConfigPath()) {
|
|
149
|
+
return readGlobalConfig(path).escalation;
|
|
150
|
+
}
|
|
151
|
+
export function readStrictReadParity(path = defaultConfigPath()) {
|
|
152
|
+
return readGlobalConfig(path).strictReadParity;
|
|
153
|
+
}
|
|
154
|
+
let legacyConfigDeprecationPending = false;
|
|
155
|
+
export function noteLegacyConfigIfUsed(path = defaultConfigPath()) {
|
|
156
|
+
if (resolveGlobalConfigPath(path).usedLegacyPath)
|
|
157
|
+
legacyConfigDeprecationPending = true;
|
|
158
|
+
}
|
|
159
|
+
export function consumeLegacyConfigDeprecationNotice() {
|
|
160
|
+
if (!legacyConfigDeprecationPending)
|
|
161
|
+
return null;
|
|
162
|
+
legacyConfigDeprecationPending = false;
|
|
163
|
+
return `${LEGACY_CONFIG_FILENAME} is deprecated; rename it to ${CONFIG_FILENAME}. The legacy name is still read for one major version when the new file is absent.`;
|
|
164
|
+
}
|
|
165
|
+
function fileDigest(path) {
|
|
66
166
|
try {
|
|
67
|
-
|
|
68
|
-
return parseCheckForUpdatesConfig(readFileSync(path, "utf8"));
|
|
167
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
69
168
|
}
|
|
70
169
|
catch {
|
|
71
|
-
return
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
let startupCeilingDigest = fileDigest(resolveGlobalConfigPath().path);
|
|
174
|
+
export function checkCeilingIntegrity(path = defaultConfigPath()) {
|
|
175
|
+
const digest = fileDigest(resolveGlobalConfigPath(path).path);
|
|
176
|
+
if (digest !== startupCeilingDigest) {
|
|
177
|
+
startupCeilingDigest = digest;
|
|
178
|
+
return "changed_since_startup";
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
export function configSelfProtectionDenyRules(path = defaultConfigPath()) {
|
|
183
|
+
const current = resolve(path);
|
|
184
|
+
const legacy = resolve(legacyConfigPath(path));
|
|
185
|
+
return [current, legacy].flatMap((p) => [`Edit(${p})`, `Write(${p})`, `NotebookEdit(${p})`]);
|
|
186
|
+
}
|
|
187
|
+
function asStringArray(raw) {
|
|
188
|
+
if (raw === undefined)
|
|
189
|
+
return [];
|
|
190
|
+
if (!Array.isArray(raw) || raw.some((v) => typeof v !== "string"))
|
|
191
|
+
return null;
|
|
192
|
+
return raw;
|
|
193
|
+
}
|
|
194
|
+
function readClaudePermissions(source, path, userScoped) {
|
|
195
|
+
try {
|
|
196
|
+
const parsed = parseJsonObject(readFileSync(path, "utf8"));
|
|
197
|
+
const permissions = parsed.permissions;
|
|
198
|
+
if (permissions !== undefined && (!permissions || typeof permissions !== "object" || Array.isArray(permissions))) {
|
|
199
|
+
throw new Error("permissions must be an object");
|
|
200
|
+
}
|
|
201
|
+
const p = (permissions ?? {});
|
|
202
|
+
const allow = asStringArray(p.allow);
|
|
203
|
+
const deny = asStringArray(p.deny);
|
|
204
|
+
const ask = asStringArray(p.ask);
|
|
205
|
+
const additionalDirectories = asStringArray(p.additionalDirectories);
|
|
206
|
+
if (!allow || !deny || !ask || !additionalDirectories) {
|
|
207
|
+
throw new Error("permissions arrays must contain only strings");
|
|
208
|
+
}
|
|
209
|
+
const disableBypass = userScoped && parsed.disableBypassPermissionsMode === "disable";
|
|
210
|
+
return {
|
|
211
|
+
rules: { allow, deny, ask, additionalDirectories },
|
|
212
|
+
disableBypass,
|
|
213
|
+
failure: null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
return {
|
|
218
|
+
rules: blanketMutatingAskRules(),
|
|
219
|
+
disableBypass: false,
|
|
220
|
+
failure: { source, path, error: e instanceof Error ? e.message : String(e) },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function blanketMutatingAskRules() {
|
|
225
|
+
return {
|
|
226
|
+
allow: [],
|
|
227
|
+
deny: [],
|
|
228
|
+
ask: ["Bash", "Edit", "Write", "NotebookEdit", "MultiEdit"],
|
|
229
|
+
additionalDirectories: [],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function mergeRules(target, next) {
|
|
233
|
+
target.allow.push(...next.allow);
|
|
234
|
+
target.deny.push(...next.deny);
|
|
235
|
+
target.ask.push(...next.ask);
|
|
236
|
+
target.additionalDirectories.push(...next.additionalDirectories);
|
|
237
|
+
}
|
|
238
|
+
function unique(items) {
|
|
239
|
+
return [...new Set(items)];
|
|
240
|
+
}
|
|
241
|
+
function codexTomlValue(text, key) {
|
|
242
|
+
const m = text.match(new RegExp(`^\\s*${key}\\s*=\\s*["']?([^"'\\r\\n#]+)["']?`, "m"));
|
|
243
|
+
return m ? m[1].trim() : null;
|
|
244
|
+
}
|
|
245
|
+
function codexTomlArray(text, key) {
|
|
246
|
+
const m = text.match(new RegExp(`^\\s*${key}\\s*=\\s*\\[([^\\]]*)\\]`, "m"));
|
|
247
|
+
if (!m)
|
|
248
|
+
return [];
|
|
249
|
+
return [...m[1].matchAll(/["']([^"']+)["']/g)].map((v) => v[1]);
|
|
250
|
+
}
|
|
251
|
+
function assertBasicTomlParsable(text) {
|
|
252
|
+
let inArray = false;
|
|
253
|
+
for (const line of text.split(/\r?\n/)) {
|
|
254
|
+
const trimmed = line.replace(/#.*$/, "").trim();
|
|
255
|
+
if (!trimmed)
|
|
256
|
+
continue;
|
|
257
|
+
if (/^\[[^\]]+\]$/.test(trimmed))
|
|
258
|
+
continue;
|
|
259
|
+
if (!trimmed.includes("=") && !inArray)
|
|
260
|
+
throw new Error(`malformed TOML line: ${trimmed}`);
|
|
261
|
+
const quoteCount = (trimmed.match(/(?<!\\)"/g) ?? []).length + (trimmed.match(/(?<!\\)'/g) ?? []).length;
|
|
262
|
+
if (quoteCount % 2 !== 0)
|
|
263
|
+
throw new Error(`unbalanced TOML quotes: ${trimmed}`);
|
|
264
|
+
const opens = (trimmed.match(/\[/g) ?? []).length;
|
|
265
|
+
const closes = (trimmed.match(/\]/g) ?? []).length;
|
|
266
|
+
inArray = inArray || opens > closes;
|
|
267
|
+
if (closes > opens)
|
|
268
|
+
inArray = false;
|
|
269
|
+
}
|
|
270
|
+
if (inArray)
|
|
271
|
+
throw new Error("unterminated TOML array");
|
|
272
|
+
}
|
|
273
|
+
function readCodexConfig(path) {
|
|
274
|
+
try {
|
|
275
|
+
const text = readFileSync(path, "utf8");
|
|
276
|
+
assertBasicTomlParsable(text);
|
|
277
|
+
const rules = { allow: [], deny: [], ask: [], additionalDirectories: [] };
|
|
278
|
+
if (codexTomlValue(text, "sandbox_mode") === "read-only") {
|
|
279
|
+
rules.deny.push("Edit", "Write", "NotebookEdit");
|
|
280
|
+
}
|
|
281
|
+
rules.additionalDirectories.push(...codexTomlArray(text, "writable_roots"));
|
|
282
|
+
for (const m of text.matchAll(/^\s*path\s*=\s*["']([^"']+)["']\s*\r?\n\s*access\s*=\s*["']deny["']/gm)) {
|
|
283
|
+
rules.deny.push(`Edit(${m[1]})`, `Write(${m[1]})`);
|
|
284
|
+
}
|
|
285
|
+
for (const m of text.matchAll(/^\s*domain\s*=\s*["']([^"']+)["']\s*\r?\n\s*access\s*=\s*["']deny["']/gm)) {
|
|
286
|
+
rules.deny.push(`WebFetch(domain:${m[1]})`);
|
|
287
|
+
}
|
|
288
|
+
return { rules, failure: null };
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
return {
|
|
292
|
+
rules: blanketMutatingAskRules(),
|
|
293
|
+
failure: { source: "repo-codex-config", path, error: e instanceof Error ? e.message : String(e) },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function codexConfigChain(cwd) {
|
|
298
|
+
const out = [];
|
|
299
|
+
let cur = resolve(cwd);
|
|
300
|
+
const root = parsePath(cur).root;
|
|
301
|
+
const dirs = [];
|
|
302
|
+
while (true) {
|
|
303
|
+
dirs.push(cur);
|
|
304
|
+
if (cur === root)
|
|
305
|
+
break;
|
|
306
|
+
cur = dirname(cur);
|
|
307
|
+
}
|
|
308
|
+
for (const dir of dirs.reverse()) {
|
|
309
|
+
const cfg = join(dir, ".codex", "config.toml");
|
|
310
|
+
if (existsSync(cfg))
|
|
311
|
+
out.push(cfg);
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
function repoConfigDigest(cwd) {
|
|
316
|
+
const files = [
|
|
317
|
+
join(cwd, ".claude", "settings.json"),
|
|
318
|
+
join(cwd, ".claude", "settings.local.json"),
|
|
319
|
+
...codexConfigChain(cwd),
|
|
320
|
+
].filter((p) => existsSync(p));
|
|
321
|
+
const h = createHash("sha256");
|
|
322
|
+
for (const file of files)
|
|
323
|
+
h.update(file).update("\0").update(readFileSync(file));
|
|
324
|
+
return h.digest("hex");
|
|
325
|
+
}
|
|
326
|
+
const firstRepoDigests = new Map();
|
|
327
|
+
export function readMergedPermissionConfig(cwd, path = defaultConfigPath()) {
|
|
328
|
+
const global = readGlobalConfig(path);
|
|
329
|
+
noteLegacyConfigIfUsed(path);
|
|
330
|
+
const merged = { allow: [], deny: [], ask: [], additionalDirectories: [] };
|
|
331
|
+
const failures = [];
|
|
332
|
+
let ceiling = global.permissionsCeiling;
|
|
333
|
+
if (global.parseFailure) {
|
|
334
|
+
failures.push({ source: "builtin", path: global.path, error: global.parseFailure });
|
|
335
|
+
ceiling = "manual";
|
|
336
|
+
mergeRules(merged, blanketMutatingAskRules());
|
|
337
|
+
}
|
|
338
|
+
let disableBypass = false;
|
|
339
|
+
const sources = [
|
|
340
|
+
["user-settings", join(homedir(), ".subagent-mcp", "settings.json"), true],
|
|
341
|
+
["user-settings-local", join(homedir(), ".subagent-mcp", "settings.local.json"), true],
|
|
342
|
+
["repo-claude-settings", join(cwd, ".claude", "settings.json"), false],
|
|
343
|
+
["repo-claude-settings-local", join(cwd, ".claude", "settings.local.json"), false],
|
|
344
|
+
];
|
|
345
|
+
for (const [source, file, userScoped] of sources) {
|
|
346
|
+
if (!existsSync(file))
|
|
347
|
+
continue;
|
|
348
|
+
const read = readClaudePermissions(source, file, userScoped);
|
|
349
|
+
mergeRules(merged, read.rules);
|
|
350
|
+
if (read.disableBypass)
|
|
351
|
+
disableBypass = true;
|
|
352
|
+
if (read.failure) {
|
|
353
|
+
failures.push(read.failure);
|
|
354
|
+
ceiling = "manual";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const file of codexConfigChain(cwd)) {
|
|
358
|
+
const read = readCodexConfig(file);
|
|
359
|
+
mergeRules(merged, read.rules);
|
|
360
|
+
if (read.failure) {
|
|
361
|
+
failures.push(read.failure);
|
|
362
|
+
ceiling = "manual";
|
|
363
|
+
}
|
|
72
364
|
}
|
|
365
|
+
if (disableBypass && ceiling === "yolo")
|
|
366
|
+
ceiling = "auto";
|
|
367
|
+
const digest = repoConfigDigest(cwd);
|
|
368
|
+
const first = firstRepoDigests.get(cwd);
|
|
369
|
+
const repoConfigChangedSinceFirstSeen = first !== undefined && first !== digest;
|
|
370
|
+
if (first === undefined)
|
|
371
|
+
firstRepoDigests.set(cwd, digest);
|
|
372
|
+
const selfProtectionDeny = configSelfProtectionDenyRules(path);
|
|
373
|
+
const protectedDirs = new Set([resolve(path), resolve(legacyConfigPath(path))]);
|
|
374
|
+
merged.deny.push(...selfProtectionDeny);
|
|
375
|
+
return {
|
|
376
|
+
allow: unique(merged.allow),
|
|
377
|
+
deny: unique(merged.deny),
|
|
378
|
+
ask: unique(merged.ask),
|
|
379
|
+
additionalDirectories: unique(merged.additionalDirectories).filter((p) => !protectedDirs.has(resolve(p))),
|
|
380
|
+
permissionsCeiling: ceiling,
|
|
381
|
+
escalation: global.escalation,
|
|
382
|
+
strictReadParity: global.strictReadParity,
|
|
383
|
+
configParseFailure: failures,
|
|
384
|
+
repoConfigChangedSinceFirstSeen,
|
|
385
|
+
selfProtectionDeny,
|
|
386
|
+
};
|
|
73
387
|
}
|
|
74
388
|
export function slotDir() {
|
|
75
389
|
if (process.env.SUBAGENT_SLOT_DIR)
|
package/dist/config-scaffold.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// GENERATED by scripts/gen-ruleset-scaffold.mjs from src/global-
|
|
2
|
-
export const CONCURRENCY_SCAFFOLD = "// subagent-mcp
|
|
1
|
+
// GENERATED by scripts/gen-ruleset-scaffold.mjs from src/global-subagent-mcp-config.jsonc — DO NOT EDIT.
|
|
2
|
+
export const CONCURRENCY_SCAFFOLD = "// subagent-mcp - Global Subagent MCP Config\n// ------------------------------------------------------------------\n// SOLE source of truth for machine-wide subagent-mcp defaults that must be\n// available beside the compiled server.\n//\n// globalConcurrentSubagents controls how many subagents may be ALIVE AT ONCE\n// across EVERY session, process, and user on this machine. There is NO\n// environment-variable override for the cap.\n//\n// RE-READ on every launch_agent call - edits take effect immediately, no\n// server restart required.\n//\n// Cap value rules:\n// - missing / unset / non-integer / 0 / negative -> reset to default 20\n// - 1 through 9 -> forced UP to minimum 10\n// - 10 or greater -> used as-is\n//\n// checkForUpdates controls the silent npmjs update check started when the MCP\n// server connects. Default true. Set to false to skip the registry fetch and\n// suppress hook notices. SUBAGENT_UPDATE_CHECK=0 or false also disables it.\n//\n// permissionsCeiling controls launch-time permissions posture:\n// - auto (default): shared engine gates unsafe/residue actions\n// - manual: human approval for residue while still auto-denying DANGER\n// - yolo: preserve the historical bypass/danger-full-access path, except\n// for non-bypassable config-file self-protection\n//\n// escalation applies only in auto mode. irreversible-only routes irreversible\n// NEUTRAL residue to a human; off leaves residue to orchestrator judgment.\n//\n// strictReadParity controls logging only. Unparseable Codex approval payloads\n// always fail closed to ask; warn logs the construct, off silences that log.\n{\n \"globalConcurrentSubagents\": 20,\n \"checkForUpdates\": true,\n \"permissionsCeiling\": \"auto\",\n \"escalation\": \"irreversible-only\",\n \"strictReadParity\": \"warn\"\n}\n";
|