@snowyroad/braid 0.75.0 → 0.76.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.
|
@@ -372,7 +372,7 @@ var InviteExpiredError = class extends Error {
|
|
|
372
372
|
}
|
|
373
373
|
};
|
|
374
374
|
async function redeemInvite(relayHttpUrl, code) {
|
|
375
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
375
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-YQS643AJ.js");
|
|
376
376
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/invites/redeem`, {
|
|
377
377
|
method: "POST",
|
|
378
378
|
headers: { "content-type": "application/json" },
|
|
@@ -401,7 +401,7 @@ async function redeemInvite(relayHttpUrl, code) {
|
|
|
401
401
|
|
|
402
402
|
// src/config.ts
|
|
403
403
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
404
|
-
import { dirname as dirname2, join as
|
|
404
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
405
405
|
import { homedir as homedir4 } from "os";
|
|
406
406
|
|
|
407
407
|
// src/keystore.ts
|
|
@@ -461,7 +461,7 @@ function validateReplacementToken(newToken, currentToken, nowMs = Date.now()) {
|
|
|
461
461
|
return { ok: true };
|
|
462
462
|
}
|
|
463
463
|
async function mintAccessToken(relayHttpUrl, agentKey, fetchFn = fetch, dpopProof) {
|
|
464
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
464
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-YQS643AJ.js");
|
|
465
465
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/agents/token`, {
|
|
466
466
|
method: "POST",
|
|
467
467
|
headers: {
|
|
@@ -1295,8 +1295,322 @@ async function chooseFirstRunProvider(agentName, io = { input: process.stdin, ou
|
|
|
1295
1295
|
return { provider: "claude-code", persist: false };
|
|
1296
1296
|
}
|
|
1297
1297
|
|
|
1298
|
+
// src/scope/scopeConfig.ts
|
|
1299
|
+
import { realpathSync, readdirSync as readdirSync2, lstatSync } from "fs";
|
|
1300
|
+
import { execSync } from "child_process";
|
|
1301
|
+
import { join as join3 } from "path";
|
|
1302
|
+
function resolveIpcProfile(scope, flagValue) {
|
|
1303
|
+
if (flagValue !== void 0) return flagValue;
|
|
1304
|
+
if (scope?.ipcProfile !== void 0) return scope.ipcProfile;
|
|
1305
|
+
return "compat";
|
|
1306
|
+
}
|
|
1307
|
+
var STRICT_IPC_ENV_STRIP = [
|
|
1308
|
+
"SSH_AUTH_SOCK",
|
|
1309
|
+
"DOCKER_HOST",
|
|
1310
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
1311
|
+
"DBUS_SYSTEM_BUS_ADDRESS",
|
|
1312
|
+
"GPG_AGENT_INFO",
|
|
1313
|
+
"DISPLAY",
|
|
1314
|
+
"WAYLAND_DISPLAY"
|
|
1315
|
+
];
|
|
1316
|
+
function strictIpcDenyRead(env, lstatFn = lstatSync) {
|
|
1317
|
+
const base = ["/run", "/var/run", "/tmp/.X11-unix"];
|
|
1318
|
+
const xdg = env["XDG_RUNTIME_DIR"];
|
|
1319
|
+
const candidates = xdg ? (() => {
|
|
1320
|
+
const segments = xdg.split("/").filter((s) => s.length > 0);
|
|
1321
|
+
if (!xdg.startsWith("/") || segments.length < 2) {
|
|
1322
|
+
console.warn(
|
|
1323
|
+
`[braid] warn: XDG_RUNTIME_DIR="${xdg}" failed the sanity gate (must be absolute, not "/", and have \u22652 path segments) \u2014 skipping from strict IPC denyRead shroud`
|
|
1324
|
+
);
|
|
1325
|
+
return base;
|
|
1326
|
+
}
|
|
1327
|
+
return [...base, xdg];
|
|
1328
|
+
})() : base;
|
|
1329
|
+
return candidates.filter((p) => {
|
|
1330
|
+
try {
|
|
1331
|
+
return !lstatFn(p).isSymbolicLink();
|
|
1332
|
+
} catch {
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
var DEFAULT_ALLOWED_DOMAINS = [
|
|
1338
|
+
// Anthropic / Claude
|
|
1339
|
+
"api.anthropic.com",
|
|
1340
|
+
"*.anthropic.com",
|
|
1341
|
+
// OpenAI / Codex
|
|
1342
|
+
"api.openai.com",
|
|
1343
|
+
// Google / Gemini
|
|
1344
|
+
"generativelanguage.googleapis.com",
|
|
1345
|
+
"*.googleapis.com",
|
|
1346
|
+
// xAI / Grok
|
|
1347
|
+
"api.x.ai",
|
|
1348
|
+
// Cursor
|
|
1349
|
+
"api.cursor.sh",
|
|
1350
|
+
// opencode (BL-169): its serve process fetches the models.dev registry AT BOOT;
|
|
1351
|
+
// without it opencode dies inside the jail (`ServeError` / ACP connection closed
|
|
1352
|
+
// on every connect). `models.dev` is repro-CONFIRMED (blocked → 403, allowed →
|
|
1353
|
+
// 200 through the srt proxy). `opencode.ai` (opencode's own service) and the
|
|
1354
|
+
// ChatGPT-sign-in OAuth hosts are opencode's documented auth/boot endpoints
|
|
1355
|
+
// (from the 2026-07-02 dry-run that hard-failed opencode). claude-code was
|
|
1356
|
+
// unaffected because all its hosts were already listed above.
|
|
1357
|
+
"models.dev",
|
|
1358
|
+
"opencode.ai",
|
|
1359
|
+
"*.opencode.ai",
|
|
1360
|
+
"auth.openai.com",
|
|
1361
|
+
"chatgpt.com",
|
|
1362
|
+
// npm
|
|
1363
|
+
"registry.npmjs.org",
|
|
1364
|
+
"*.npmjs.org",
|
|
1365
|
+
// PyPI
|
|
1366
|
+
"pypi.org",
|
|
1367
|
+
"files.pythonhosted.org",
|
|
1368
|
+
// Go modules
|
|
1369
|
+
"proxy.golang.org",
|
|
1370
|
+
// Crates.io
|
|
1371
|
+
"crates.io",
|
|
1372
|
+
"static.crates.io",
|
|
1373
|
+
// Source hosts
|
|
1374
|
+
"github.com",
|
|
1375
|
+
"*.github.com",
|
|
1376
|
+
"*.githubusercontent.com",
|
|
1377
|
+
"gitlab.com",
|
|
1378
|
+
"bitbucket.org"
|
|
1379
|
+
];
|
|
1380
|
+
var PROVIDER_PATHS = {
|
|
1381
|
+
"claude-code": ["~/.claude", "~/.claude.json"],
|
|
1382
|
+
codex: ["~/.codex"],
|
|
1383
|
+
gemini: ["~/.gemini"],
|
|
1384
|
+
grok: ["~/.grok"],
|
|
1385
|
+
cursor: ["~/.cursor"],
|
|
1386
|
+
opencode: ["~/.local/share/opencode", "~/.config/opencode"],
|
|
1387
|
+
goose: ["~/.config/goose"],
|
|
1388
|
+
cline: ["~/.cline"],
|
|
1389
|
+
copilot: ["~/.copilot"],
|
|
1390
|
+
qwen: ["~/.qwen"]
|
|
1391
|
+
};
|
|
1392
|
+
function expandHome(p, home) {
|
|
1393
|
+
if (p.startsWith("~/")) return `${home}${p.slice(1)}`;
|
|
1394
|
+
if (p === "~") return home;
|
|
1395
|
+
return p;
|
|
1396
|
+
}
|
|
1397
|
+
function canonicalize(p) {
|
|
1398
|
+
try {
|
|
1399
|
+
return realpathSync(p);
|
|
1400
|
+
} catch {
|
|
1401
|
+
return p;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function parseEnvList(val) {
|
|
1405
|
+
if (!val) return [];
|
|
1406
|
+
return val.split(/[,:]/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1407
|
+
}
|
|
1408
|
+
function dedup(arr) {
|
|
1409
|
+
return [...new Set(arr)];
|
|
1410
|
+
}
|
|
1411
|
+
function darwinMdsCacheDir() {
|
|
1412
|
+
try {
|
|
1413
|
+
const cacheRoot = execSync("/usr/bin/getconf DARWIN_USER_CACHE_DIR", {
|
|
1414
|
+
encoding: "utf8"
|
|
1415
|
+
}).trim();
|
|
1416
|
+
if (!cacheRoot) return [];
|
|
1417
|
+
return [`${cacheRoot.replace(/\/+$/, "")}/mds`];
|
|
1418
|
+
} catch {
|
|
1419
|
+
return [];
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function resolvePaths(paths, home) {
|
|
1423
|
+
return dedup(paths.map((p) => canonicalize(expandHome(p, home))));
|
|
1424
|
+
}
|
|
1425
|
+
function foldCase(p, platform) {
|
|
1426
|
+
return platform === "darwin" || platform === "win32" ? p.toLowerCase() : p;
|
|
1427
|
+
}
|
|
1428
|
+
function checkGrantsAgainstReservations(grants, reservations, platform) {
|
|
1429
|
+
const sep2 = platform === "win32" ? "\\" : "/";
|
|
1430
|
+
const out = [];
|
|
1431
|
+
for (const g of grants) {
|
|
1432
|
+
const gf = foldCase(g.path, platform);
|
|
1433
|
+
for (const r of reservations) {
|
|
1434
|
+
const rf = foldCase(r, platform);
|
|
1435
|
+
if (gf === rf) {
|
|
1436
|
+
out.push({ grant: g.path, source: g.source, reservation: r, relation: "equal" });
|
|
1437
|
+
} else if (gf.startsWith(rf + sep2)) {
|
|
1438
|
+
out.push({ grant: g.path, source: g.source, reservation: r, relation: "descendant" });
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return out;
|
|
1443
|
+
}
|
|
1444
|
+
function resolveScope(cfg, provider, env, platform = process.platform) {
|
|
1445
|
+
const scope = cfg.scope ?? {};
|
|
1446
|
+
const home = env["HOME"] ?? "";
|
|
1447
|
+
const cwd = env["PWD"] ?? process.cwd();
|
|
1448
|
+
const tmpDir = env["TMPDIR"] ?? "/tmp";
|
|
1449
|
+
const braidConfigDir = env["BRAID_CONFIG_DIR"]?.trim() || expandHome("~/.braid", home);
|
|
1450
|
+
const legacyArpConfigDir = expandHome("~/.arp", home);
|
|
1451
|
+
const protectedConfigDirs = braidConfigDir === legacyArpConfigDir ? [braidConfigDir] : [braidConfigDir, legacyArpConfigDir];
|
|
1452
|
+
const flagOverride = cfg.scopeFlags?.scopeOverride;
|
|
1453
|
+
const disabled = flagOverride !== void 0 ? flagOverride === "off" : scope.disabled === true || env["BRAID_SCOPE"] === "off";
|
|
1454
|
+
const secretDirs = [
|
|
1455
|
+
"~/.ssh",
|
|
1456
|
+
"~/.aws",
|
|
1457
|
+
"~/.gnupg",
|
|
1458
|
+
"~/.kube/config"
|
|
1459
|
+
].map((p) => expandHome(p, home));
|
|
1460
|
+
const protectedConfigDirEntries = protectedConfigDirs.flatMap((configDirPath) => {
|
|
1461
|
+
if (platform !== "win32") return [configDirPath];
|
|
1462
|
+
try {
|
|
1463
|
+
const children = readdirSync2(configDirPath, { withFileTypes: true });
|
|
1464
|
+
const denied = children.filter((entry) => entry.name !== "runtime").map((entry) => canonicalize(join3(configDirPath, entry.name)));
|
|
1465
|
+
if (denied.length === 0) return [configDirPath];
|
|
1466
|
+
return denied;
|
|
1467
|
+
} catch {
|
|
1468
|
+
return [configDirPath];
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
const providerPaths = PROVIDER_PATHS[provider].map((p) => expandHome(p, home));
|
|
1472
|
+
const uid = process.getuid?.();
|
|
1473
|
+
const claudeTmpRoot = provider === "claude-code" && uid !== void 0 ? [`${canonicalize("/tmp")}/claude-${uid}`, `${canonicalize("/tmp")}/claude-*-cwd`] : [];
|
|
1474
|
+
const mdsCacheDir = platform === "darwin" ? darwinMdsCacheDir() : [];
|
|
1475
|
+
const opAllowWrite = (scope.allowWrite ?? []).map((p) => expandHome(p, home));
|
|
1476
|
+
const envAllowWrite = parseEnvList(env["BRAID_SCOPE_ALLOW_WRITE"]).map((p) => expandHome(p, home));
|
|
1477
|
+
const flagAllowWrite = (cfg.scopeFlags?.allowWrite ?? []).map((p) => expandHome(p, home));
|
|
1478
|
+
const rawAllowWrite = [
|
|
1479
|
+
cwd,
|
|
1480
|
+
...providerPaths,
|
|
1481
|
+
...claudeTmpRoot,
|
|
1482
|
+
...mdsCacheDir,
|
|
1483
|
+
expandHome("~/.npm", home),
|
|
1484
|
+
tmpDir,
|
|
1485
|
+
...opAllowWrite,
|
|
1486
|
+
...envAllowWrite,
|
|
1487
|
+
...flagAllowWrite
|
|
1488
|
+
];
|
|
1489
|
+
const allowWrite = resolvePaths(rawAllowWrite, home);
|
|
1490
|
+
const rawDenyWrite = [
|
|
1491
|
+
...secretDirs,
|
|
1492
|
+
...protectedConfigDirEntries,
|
|
1493
|
+
...(scope.denyWrite ?? []).map((p) => expandHome(p, home)),
|
|
1494
|
+
...parseEnvList(env["BRAID_SCOPE_DENY_WRITE"]).map((p) => expandHome(p, home))
|
|
1495
|
+
];
|
|
1496
|
+
const denyWrite = resolvePaths(rawDenyWrite, home);
|
|
1497
|
+
const ipcProfile = resolveIpcProfile(scope, cfg.scopeFlags?.ipcProfile);
|
|
1498
|
+
const rawDenyRead = [
|
|
1499
|
+
...secretDirs,
|
|
1500
|
+
...protectedConfigDirEntries,
|
|
1501
|
+
...(scope.denyRead ?? []).map((p) => expandHome(p, home)),
|
|
1502
|
+
...parseEnvList(env["BRAID_SCOPE_DENY_READ"]).map((p) => expandHome(p, home))
|
|
1503
|
+
];
|
|
1504
|
+
const denyRead = dedup([
|
|
1505
|
+
...resolvePaths(rawDenyRead, home),
|
|
1506
|
+
...ipcProfile === "strict" && platform === "linux" ? strictIpcDenyRead(env) : []
|
|
1507
|
+
]);
|
|
1508
|
+
const opAllowRead = (scope.allowRead ?? []).map((p) => expandHome(p, home));
|
|
1509
|
+
const envAllowRead = parseEnvList(env["BRAID_SCOPE_ALLOW_READ"]).map((p) => expandHome(p, home));
|
|
1510
|
+
const flagAllowRead = (cfg.scopeFlags?.allowRead ?? []).map((p) => expandHome(p, home));
|
|
1511
|
+
const rawAllowRead = [
|
|
1512
|
+
...opAllowRead,
|
|
1513
|
+
...envAllowRead,
|
|
1514
|
+
...flagAllowRead
|
|
1515
|
+
];
|
|
1516
|
+
const allowRead = resolvePaths(rawAllowRead, home);
|
|
1517
|
+
if (!disabled) {
|
|
1518
|
+
const reservations = resolvePaths([...secretDirs, ...protectedConfigDirs], home);
|
|
1519
|
+
const labelled = (paths, source) => resolvePaths(paths, home).map((p) => ({ path: p, source }));
|
|
1520
|
+
const grants = [
|
|
1521
|
+
...labelled([cwd], "launch directory (cwd)"),
|
|
1522
|
+
...labelled(providerPaths, "provider default paths"),
|
|
1523
|
+
...labelled(opAllowWrite, "scope.allowWrite"),
|
|
1524
|
+
...labelled(envAllowWrite, "BRAID_SCOPE_ALLOW_WRITE"),
|
|
1525
|
+
...labelled(flagAllowWrite, "--allow-write"),
|
|
1526
|
+
...labelled(opAllowRead, "scope.allowRead"),
|
|
1527
|
+
...labelled(envAllowRead, "BRAID_SCOPE_ALLOW_READ"),
|
|
1528
|
+
...labelled(flagAllowRead, "--allow-read")
|
|
1529
|
+
];
|
|
1530
|
+
const conflicts = checkGrantsAgainstReservations(grants, reservations, platform);
|
|
1531
|
+
if (conflicts.length > 0) {
|
|
1532
|
+
const detail = conflicts.map((c) => ` - ${c.source}: "${c.grant}" is ${c.relation === "equal" ? "" : "inside "}the protected path "${c.reservation}"`).join("\n");
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
`[braid] SCOPE ERROR: a grant covers a protected credential/secret path and was refused:
|
|
1535
|
+
${detail}
|
|
1536
|
+
Protected paths (never grantable): ${reservations.join(", ")}
|
|
1537
|
+
Narrow the grant to a path outside the protected set. (Ancestor grants like "~" are fine \u2014 the protected dirs are carved back out automatically.)`
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
const writableDefaults = resolvePaths(
|
|
1541
|
+
[...providerPaths, expandHome("~/.npm", home), tmpDir],
|
|
1542
|
+
home
|
|
1543
|
+
);
|
|
1544
|
+
const resolvedConfigDir = canonicalize(braidConfigDir);
|
|
1545
|
+
const placement = checkGrantsAgainstReservations(
|
|
1546
|
+
[{ path: resolvedConfigDir, source: "BRAID_CONFIG_DIR" }],
|
|
1547
|
+
writableDefaults,
|
|
1548
|
+
platform
|
|
1549
|
+
);
|
|
1550
|
+
if (placement.length > 0) {
|
|
1551
|
+
const c = placement[0];
|
|
1552
|
+
throw new Error(
|
|
1553
|
+
`[braid] SCOPE ERROR: BRAID_CONFIG_DIR ("${c.grant}") is ${c.relation === "equal" ? "" : "inside "}a default provider-writable location ("${c.reservation}"). The bridge credential store must live outside provider/tmp/npm paths. Move it (default: ~/.braid) or unset BRAID_CONFIG_DIR.`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const allowedDomains = dedup([
|
|
1558
|
+
...DEFAULT_ALLOWED_DOMAINS,
|
|
1559
|
+
...scope.network?.allowedDomains ?? [],
|
|
1560
|
+
...cfg.derivedAllowedDomains ?? [],
|
|
1561
|
+
...parseEnvList(env["BRAID_SCOPE_ALLOW_DOMAINS"]),
|
|
1562
|
+
...cfg.scopeFlags?.allowDomains ?? []
|
|
1563
|
+
]);
|
|
1564
|
+
const deniedDomains = dedup([
|
|
1565
|
+
...scope.network?.deniedDomains ?? [],
|
|
1566
|
+
...parseEnvList(env["BRAID_SCOPE_DENY_DOMAINS"])
|
|
1567
|
+
]);
|
|
1568
|
+
function describe() {
|
|
1569
|
+
const lines = [];
|
|
1570
|
+
if (disabled) {
|
|
1571
|
+
lines.push("[scope] SANDBOX DISABLED (BRAID_SCOPE=off or scope.disabled:true) \u2014 agent runs unconfined");
|
|
1572
|
+
return lines;
|
|
1573
|
+
}
|
|
1574
|
+
lines.push("[scope] Agent sandbox is ACTIVE");
|
|
1575
|
+
lines.push(
|
|
1576
|
+
`[scope] protected (never readable/writable by the agent): ${resolvePaths([...secretDirs, ...protectedConfigDirs], home).join(", ")}`
|
|
1577
|
+
);
|
|
1578
|
+
lines.push(`[scope] cwd (writable): ${cwd}`);
|
|
1579
|
+
if (providerPaths.length > 0) {
|
|
1580
|
+
lines.push(`[scope] provider paths (writable): ${providerPaths.join(", ")}`);
|
|
1581
|
+
}
|
|
1582
|
+
lines.push(`[scope] allowed domains (${allowedDomains.length}): ${allowedDomains.slice(0, 5).join(", ")}${allowedDomains.length > 5 ? " ..." : ""}`);
|
|
1583
|
+
if ((cfg.derivedAllowedDomains ?? []).length > 0) {
|
|
1584
|
+
lines.push(`[scope] model endpoints (derived): ${(cfg.derivedAllowedDomains ?? []).join(", ")}`);
|
|
1585
|
+
}
|
|
1586
|
+
lines.push(`[scope] denied read: ${denyRead.join(", ")}`);
|
|
1587
|
+
if (denyWrite.length > 0) {
|
|
1588
|
+
lines.push(`[scope] denied write: ${denyWrite.join(", ")}`);
|
|
1589
|
+
}
|
|
1590
|
+
if (deniedDomains.length > 0) {
|
|
1591
|
+
lines.push(`[scope] denied domains: ${deniedDomains.join(", ")}`);
|
|
1592
|
+
}
|
|
1593
|
+
if (ipcProfile === "compat") {
|
|
1594
|
+
lines.push("[scope] ipc profile: compat \u2014 all unix sockets reachable inside the jail (Linux seccomp constraint)");
|
|
1595
|
+
} else {
|
|
1596
|
+
lines.push("[scope] ipc profile: strict \u2014 /run, /var/run, XDG_RUNTIME_DIR, /tmp/.X11-unix hidden; socket env vars stripped");
|
|
1597
|
+
}
|
|
1598
|
+
lines.push("[scope] To widen: add paths to scope.allowWrite/allowRead in agent config, or BRAID_SCOPE_ALLOW_WRITE/BRAID_SCOPE=off env.");
|
|
1599
|
+
return lines;
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
runtimeConfig: {
|
|
1603
|
+
filesystem: { allowWrite, denyWrite, allowRead, denyRead },
|
|
1604
|
+
network: { allowedDomains, deniedDomains }
|
|
1605
|
+
},
|
|
1606
|
+
disabled,
|
|
1607
|
+
ipcProfile,
|
|
1608
|
+
describe
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1298
1612
|
// src/cardSigning.ts
|
|
1299
|
-
import
|
|
1613
|
+
import canonicalize2 from "canonicalize";
|
|
1300
1614
|
import {
|
|
1301
1615
|
FlattenedSign,
|
|
1302
1616
|
flattenedVerify,
|
|
@@ -1353,7 +1667,7 @@ function pruneForSigning(card) {
|
|
|
1353
1667
|
return pruneNode(clone, REQUIRED_TOP);
|
|
1354
1668
|
}
|
|
1355
1669
|
function canonicalCardString(card) {
|
|
1356
|
-
return
|
|
1670
|
+
return canonicalize2(pruneForSigning(card));
|
|
1357
1671
|
}
|
|
1358
1672
|
function publicOkpJwk(jwk) {
|
|
1359
1673
|
return { kty: jwk.kty, crv: jwk.crv, x: jwk.x };
|
|
@@ -1825,10 +2139,10 @@ async function loadConfigFromInvite(code, env, flags) {
|
|
|
1825
2139
|
}
|
|
1826
2140
|
function buildJoinScopeBlock(flags, env) {
|
|
1827
2141
|
const split = (v) => v ? v.split(/[,:]/).map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
1828
|
-
const
|
|
1829
|
-
const allowWrite =
|
|
1830
|
-
const allowRead =
|
|
1831
|
-
const allowedDomains =
|
|
2142
|
+
const dedup2 = (arr) => [...new Set(arr)];
|
|
2143
|
+
const allowWrite = dedup2([...split(env.BRAID_SCOPE_ALLOW_WRITE), ...flags?.allowWrite ?? []]);
|
|
2144
|
+
const allowRead = dedup2([...split(env.BRAID_SCOPE_ALLOW_READ), ...flags?.allowRead ?? []]);
|
|
2145
|
+
const allowedDomains = dedup2([...split(env.BRAID_SCOPE_ALLOW_DOMAINS), ...flags?.allowDomains ?? []]);
|
|
1832
2146
|
const disabled = flags?.scope === "off" || flags?.scope === void 0 && env.BRAID_SCOPE === "off";
|
|
1833
2147
|
if (allowWrite.length === 0 && allowRead.length === 0 && allowedDomains.length === 0 && !disabled) {
|
|
1834
2148
|
return void 0;
|
|
@@ -1901,7 +2215,7 @@ function parseClaudeConfigModel(jsonText) {
|
|
|
1901
2215
|
function readClaudeConfigModel(agent) {
|
|
1902
2216
|
if (agent !== "claude-code") return null;
|
|
1903
2217
|
try {
|
|
1904
|
-
const text = readFileSync2(
|
|
2218
|
+
const text = readFileSync2(join4(homedir4(), ".claude.json"), "utf8");
|
|
1905
2219
|
return parseClaudeConfigModel(text);
|
|
1906
2220
|
} catch {
|
|
1907
2221
|
return null;
|
|
@@ -1920,16 +2234,21 @@ function redactConfig(cfg) {
|
|
|
1920
2234
|
return { ...cfg, token: "<redacted>", model };
|
|
1921
2235
|
}
|
|
1922
2236
|
function startingLines(cfg) {
|
|
1923
|
-
const
|
|
2237
|
+
const row = (label, value) => `${label.padEnd(8)} : ${value}`;
|
|
2238
|
+
const anthropicCredential = cfg.agentMode !== "acp" || cfg.agent === "claude-code";
|
|
2239
|
+
const catchUpHours = cfg.catchUpTtlMs % 36e5 === 0 ? `${cfg.catchUpTtlMs / 36e5}h` : `${Math.round(cfg.catchUpTtlMs / 6e4)}min`;
|
|
1924
2240
|
return [
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2241
|
+
row("agent", sanitizeForTty(cfg.agentName)),
|
|
2242
|
+
row("relay", sanitizeForTty(cfg.relayWsUrl)),
|
|
2243
|
+
row("provider", cfg.agentMode === "acp" ? cfg.agent : "bundled SDK (generic mode)"),
|
|
2244
|
+
row("model", sanitizeForTty(redactConfig(cfg).model)),
|
|
2245
|
+
row("fallback", cfg.fallbackModel ? sanitizeForTty(cfg.fallbackModel) : "(none \u2014 a rate limit is surfaced, not switched)"),
|
|
2246
|
+
row("tools", cfg.toolMode),
|
|
2247
|
+
row("auth", anthropicCredential ? cfg.authMode ?? "auto" : `(managed by ${cfg.agent})`),
|
|
2248
|
+
row("sandbox", cfg.scope?.disabled ? "OFF (unconfined)" : "on"),
|
|
2249
|
+
row("ipc", resolveIpcProfile(cfg.scope, cfg.scopeFlags?.ipcProfile)),
|
|
2250
|
+
row("catch-up", `${catchUpHours} window \xB7 up to ${cfg.catchUpMaxMentions} missed mentions`),
|
|
2251
|
+
row("keystore", cfg.agentFile ? sanitizeForTty(cfg.agentFile) : "(env-provided credential \u2014 no saved file)")
|
|
1933
2252
|
];
|
|
1934
2253
|
}
|
|
1935
2254
|
|
|
@@ -2162,6 +2481,9 @@ export {
|
|
|
2162
2481
|
evaluateAcpPermission,
|
|
2163
2482
|
evaluateSdkTool,
|
|
2164
2483
|
pickRejectOptionId,
|
|
2484
|
+
resolveIpcProfile,
|
|
2485
|
+
STRICT_IPC_ENV_STRIP,
|
|
2486
|
+
resolveScope,
|
|
2165
2487
|
verifyCardSignature,
|
|
2166
2488
|
cleanContent,
|
|
2167
2489
|
resolveAgentSelection,
|