agent-trellis 0.1.0 → 0.3.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 +69 -17
- package/dist/adapters/claude-code.d.ts +5 -3
- package/dist/adapters/claude-code.js +27 -14
- package/dist/adapters/codex.d.ts +8 -4
- package/dist/adapters/codex.js +47 -16
- package/dist/adapters/jsonMcp.d.ts +16 -5
- package/dist/adapters/jsonMcp.js +38 -29
- package/dist/adapters/kiro.d.ts +5 -3
- package/dist/adapters/kiro.js +29 -16
- package/dist/adapters/mcpPlan.d.ts +11 -6
- package/dist/adapters/mcpPlan.js +40 -7
- package/dist/adapters/pi.d.ts +2 -1
- package/dist/adapters/pi.js +4 -4
- package/dist/adapters/symlinkPlan.d.ts +7 -3
- package/dist/adapters/symlinkPlan.js +42 -16
- package/dist/cli.js +161 -18
- package/dist/commands/init.js +11 -0
- package/dist/commands/mcp.d.ts +114 -7
- package/dist/commands/mcp.js +258 -17
- package/dist/commands/memory.d.ts +39 -0
- package/dist/commands/memory.js +78 -0
- package/dist/commands/migrate.d.ts +30 -4
- package/dist/commands/migrate.js +83 -16
- package/dist/commands/onboard.d.ts +52 -7
- package/dist/commands/onboard.js +318 -35
- package/dist/commands/rollback.d.ts +44 -0
- package/dist/commands/rollback.js +201 -0
- package/dist/commands/secretsAudit.d.ts +7 -0
- package/dist/commands/secretsAudit.js +14 -7
- package/dist/commands/skill.d.ts +51 -0
- package/dist/commands/skill.js +104 -0
- package/dist/commands/sync.d.ts +13 -0
- package/dist/commands/sync.js +31 -5
- package/dist/core/adapter.d.ts +28 -11
- package/dist/core/adapter.js +2 -2
- package/dist/core/canonical.d.ts +26 -1
- package/dist/core/canonical.js +103 -3
- package/dist/core/types.d.ts +29 -1
- package/dist/core/types.js +11 -2
- package/dist/lib/backup.d.ts +56 -0
- package/dist/lib/backup.js +98 -0
- package/dist/lib/deepEqual.d.ts +8 -0
- package/dist/lib/deepEqual.js +26 -0
- package/dist/lib/dirEquals.d.ts +9 -0
- package/dist/lib/dirEquals.js +15 -1
- package/dist/lib/installAgent.d.ts +26 -0
- package/dist/lib/installAgent.js +46 -0
- package/dist/lib/mcpMigrateRead.d.ts +69 -0
- package/dist/lib/mcpMigrateRead.js +188 -0
- package/dist/lib/mcpOwnership.d.ts +25 -0
- package/dist/lib/mcpOwnership.js +50 -0
- package/dist/lib/memoryGraph.d.ts +60 -0
- package/dist/lib/memoryGraph.js +101 -0
- package/dist/lib/realHomeSnapshot.d.ts +26 -0
- package/dist/lib/realHomeSnapshot.js +77 -0
- package/dist/lib/terminalPicker.d.ts +45 -0
- package/dist/lib/terminalPicker.js +193 -0
- package/dist/lib/tomlSection.d.ts +20 -6
- package/dist/lib/tomlSection.js +78 -12
- package/dist/pi-bridge/bundle.js +100 -51
- package/dist/pi-bridge/index.js +14 -2
- package/dist/probes/codex.js +10 -2
- package/docs/architecture.md +7 -4
- package/docs/getting-started.md +267 -33
- package/docs/roadmap.md +444 -0
- package/package.json +1 -1
- package/schema/servers.example.yaml +39 -2
package/dist/pi-bridge/bundle.js
CHANGED
|
@@ -7818,7 +7818,7 @@ var require_identity = __commonJS({
|
|
|
7818
7818
|
var NODE_TYPE = /* @__PURE__ */ Symbol.for("yaml.node.type");
|
|
7819
7819
|
var isAlias = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === ALIAS;
|
|
7820
7820
|
var isDocument = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === DOC;
|
|
7821
|
-
var
|
|
7821
|
+
var isMap2 = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === MAP;
|
|
7822
7822
|
var isPair = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === PAIR;
|
|
7823
7823
|
var isScalar = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SCALAR;
|
|
7824
7824
|
var isSeq = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SEQ;
|
|
@@ -7854,7 +7854,7 @@ var require_identity = __commonJS({
|
|
|
7854
7854
|
exports.isAlias = isAlias;
|
|
7855
7855
|
exports.isCollection = isCollection;
|
|
7856
7856
|
exports.isDocument = isDocument;
|
|
7857
|
-
exports.isMap =
|
|
7857
|
+
exports.isMap = isMap2;
|
|
7858
7858
|
exports.isNode = isNode;
|
|
7859
7859
|
exports.isPair = isPair;
|
|
7860
7860
|
exports.isScalar = isScalar;
|
|
@@ -11966,9 +11966,9 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
11966
11966
|
var blockMsg = "Block collections are not allowed within flow collections";
|
|
11967
11967
|
var isBlock = (token) => token && (token.type === "block-map" || token.type === "block-seq");
|
|
11968
11968
|
function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) {
|
|
11969
|
-
const
|
|
11970
|
-
const fcName =
|
|
11971
|
-
const NodeClass = tag?.nodeClass ?? (
|
|
11969
|
+
const isMap2 = fc.start.source === "{";
|
|
11970
|
+
const fcName = isMap2 ? "flow map" : "flow sequence";
|
|
11971
|
+
const NodeClass = tag?.nodeClass ?? (isMap2 ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq);
|
|
11972
11972
|
const coll = new NodeClass(ctx.schema);
|
|
11973
11973
|
coll.flow = true;
|
|
11974
11974
|
const atRoot = ctx.atRoot;
|
|
@@ -12004,7 +12004,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12004
12004
|
offset = props.end;
|
|
12005
12005
|
continue;
|
|
12006
12006
|
}
|
|
12007
|
-
if (!
|
|
12007
|
+
if (!isMap2 && ctx.options.strict && utilContainsNewline.containsNewline(key))
|
|
12008
12008
|
onError(
|
|
12009
12009
|
key,
|
|
12010
12010
|
// checked by containsNewline()
|
|
@@ -12044,7 +12044,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12044
12044
|
}
|
|
12045
12045
|
}
|
|
12046
12046
|
}
|
|
12047
|
-
if (!
|
|
12047
|
+
if (!isMap2 && !sep && !props.found) {
|
|
12048
12048
|
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError);
|
|
12049
12049
|
coll.items.push(valueNode);
|
|
12050
12050
|
offset = valueNode.range[2];
|
|
@@ -12067,7 +12067,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12067
12067
|
startOnNewline: false
|
|
12068
12068
|
});
|
|
12069
12069
|
if (valueProps.found) {
|
|
12070
|
-
if (!
|
|
12070
|
+
if (!isMap2 && !props.found && ctx.options.strict) {
|
|
12071
12071
|
if (sep)
|
|
12072
12072
|
for (const st of sep) {
|
|
12073
12073
|
if (st === valueProps.found)
|
|
@@ -12099,7 +12099,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12099
12099
|
const pair = new Pair.Pair(keyNode, valueNode);
|
|
12100
12100
|
if (ctx.options.keepSourceTokens)
|
|
12101
12101
|
pair.srcToken = collItem;
|
|
12102
|
-
if (
|
|
12102
|
+
if (isMap2) {
|
|
12103
12103
|
const map = coll;
|
|
12104
12104
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
12105
12105
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
@@ -12115,7 +12115,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12115
12115
|
offset = valueNode ? valueNode.range[2] : valueProps.end;
|
|
12116
12116
|
}
|
|
12117
12117
|
}
|
|
12118
|
-
const expectedEnd =
|
|
12118
|
+
const expectedEnd = isMap2 ? "}" : "]";
|
|
12119
12119
|
const [ce, ...ee] = fc.end;
|
|
12120
12120
|
let cePos = offset;
|
|
12121
12121
|
if (ce?.source === expectedEnd)
|
|
@@ -15016,7 +15016,7 @@ var require_public_api = __commonJS({
|
|
|
15016
15016
|
return docs;
|
|
15017
15017
|
return Object.assign([], { empty: true }, composer$1.streamInfo());
|
|
15018
15018
|
}
|
|
15019
|
-
function
|
|
15019
|
+
function parseDocument2(source, options = {}) {
|
|
15020
15020
|
const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
|
|
15021
15021
|
const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
|
|
15022
15022
|
const composer$1 = new composer.Composer(options);
|
|
@@ -15042,7 +15042,7 @@ var require_public_api = __commonJS({
|
|
|
15042
15042
|
} else if (options === void 0 && reviver && typeof reviver === "object") {
|
|
15043
15043
|
options = reviver;
|
|
15044
15044
|
}
|
|
15045
|
-
const doc =
|
|
15045
|
+
const doc = parseDocument2(src, options);
|
|
15046
15046
|
if (!doc)
|
|
15047
15047
|
return null;
|
|
15048
15048
|
doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
|
|
@@ -15078,7 +15078,7 @@ var require_public_api = __commonJS({
|
|
|
15078
15078
|
}
|
|
15079
15079
|
exports.parse = parse3;
|
|
15080
15080
|
exports.parseAllDocuments = parseAllDocuments;
|
|
15081
|
-
exports.parseDocument =
|
|
15081
|
+
exports.parseDocument = parseDocument2;
|
|
15082
15082
|
exports.stringify = stringify;
|
|
15083
15083
|
}
|
|
15084
15084
|
});
|
|
@@ -27243,7 +27243,7 @@ import { homedir as homedir2 } from "node:os";
|
|
|
27243
27243
|
|
|
27244
27244
|
// src/core/canonical.ts
|
|
27245
27245
|
var import_yaml = __toESM(require_dist2(), 1);
|
|
27246
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
27246
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
27247
27247
|
import { homedir } from "node:os";
|
|
27248
27248
|
import { basename, join } from "node:path";
|
|
27249
27249
|
|
|
@@ -27254,11 +27254,16 @@ var ALL_AGENTS = [
|
|
|
27254
27254
|
"kiro",
|
|
27255
27255
|
"pi"
|
|
27256
27256
|
];
|
|
27257
|
-
function resolveScope(scope) {
|
|
27258
|
-
|
|
27257
|
+
function resolveScope(scope, managedAgents) {
|
|
27258
|
+
const candidates = scope ?? managedAgents;
|
|
27259
|
+
return candidates.filter((id) => managedAgents.includes(id));
|
|
27259
27260
|
}
|
|
27260
27261
|
|
|
27261
27262
|
// src/core/canonical.ts
|
|
27263
|
+
function fromServerDefYaml(def) {
|
|
27264
|
+
const { static_env, ...rest } = def;
|
|
27265
|
+
return static_env ? { ...rest, staticEnv: static_env } : rest;
|
|
27266
|
+
}
|
|
27262
27267
|
function trellisRoot(homeDir) {
|
|
27263
27268
|
return join(homeDir, ".trellis");
|
|
27264
27269
|
}
|
|
@@ -27302,8 +27307,9 @@ function loadServersYaml(path) {
|
|
|
27302
27307
|
return { servers: {}, knownHostInjected: [] };
|
|
27303
27308
|
}
|
|
27304
27309
|
const parsed = (0, import_yaml.parse)(readFileSync(path, "utf-8")) ?? {};
|
|
27310
|
+
const servers = Object.fromEntries(Object.entries(parsed.servers ?? {}).map(([name, def]) => [name, fromServerDefYaml(def)]));
|
|
27305
27311
|
return {
|
|
27306
|
-
servers
|
|
27312
|
+
servers,
|
|
27307
27313
|
knownHostInjected: parsed.known_host_injected ?? [],
|
|
27308
27314
|
hub: parsed.hub
|
|
27309
27315
|
};
|
|
@@ -27319,6 +27325,22 @@ function loadSecretsPolicyYaml(path, homeDir) {
|
|
|
27319
27325
|
envFile: parsed.env_file ? parsed.env_file.replace(/^~(?=$|\/)/, homeDir) : void 0
|
|
27320
27326
|
};
|
|
27321
27327
|
}
|
|
27328
|
+
function loadManagedYaml(path, diagnostics) {
|
|
27329
|
+
if (!existsSync(path)) {
|
|
27330
|
+
return [];
|
|
27331
|
+
}
|
|
27332
|
+
const parsed = (0, import_yaml.parse)(readFileSync(path, "utf-8")) ?? {};
|
|
27333
|
+
const raw = parsed.agents ?? [];
|
|
27334
|
+
const valid = [];
|
|
27335
|
+
for (const id of raw) {
|
|
27336
|
+
if (ALL_AGENTS.includes(id)) {
|
|
27337
|
+
valid.push(id);
|
|
27338
|
+
} else {
|
|
27339
|
+
diagnostics.push(`managed.yaml: "${id}" is not a recognized agent id \u2014 ignored`);
|
|
27340
|
+
}
|
|
27341
|
+
}
|
|
27342
|
+
return valid;
|
|
27343
|
+
}
|
|
27322
27344
|
function scopeFor(map, name) {
|
|
27323
27345
|
return map?.[name];
|
|
27324
27346
|
}
|
|
@@ -27334,6 +27356,7 @@ function loadCanonicalSource(homeDir = homedir()) {
|
|
|
27334
27356
|
);
|
|
27335
27357
|
}
|
|
27336
27358
|
const diagnostics = [];
|
|
27359
|
+
const managedAgents = loadManagedYaml(join(root, "managed.yaml"), diagnostics);
|
|
27337
27360
|
const scopeYaml = loadScopeYaml(join(root, "scope.yaml"));
|
|
27338
27361
|
const skillDirs = listSkillDirs(join(root, "skills"));
|
|
27339
27362
|
const knownSkillNames = new Set(skillDirs.map((s) => s.name));
|
|
@@ -27368,6 +27391,7 @@ function loadCanonicalSource(homeDir = homedir()) {
|
|
|
27368
27391
|
}
|
|
27369
27392
|
return {
|
|
27370
27393
|
instructionsFile: join(root, "agents.md"),
|
|
27394
|
+
managedAgents,
|
|
27371
27395
|
skills,
|
|
27372
27396
|
agents,
|
|
27373
27397
|
memories,
|
|
@@ -27381,8 +27405,8 @@ function loadCanonicalSource(homeDir = homedir()) {
|
|
|
27381
27405
|
}
|
|
27382
27406
|
|
|
27383
27407
|
// src/core/adapter.ts
|
|
27384
|
-
function isInScope(id, scope) {
|
|
27385
|
-
return resolveScope(scope).includes(id);
|
|
27408
|
+
function isInScope(id, scope, managedAgents) {
|
|
27409
|
+
return resolveScope(scope, managedAgents).includes(id);
|
|
27386
27410
|
}
|
|
27387
27411
|
|
|
27388
27412
|
// src/lib/tomlSection.ts
|
|
@@ -27397,6 +27421,33 @@ function codexBearerTokenEnvVar(def) {
|
|
|
27397
27421
|
return BEARER_TOKEN_VALUE_RE.exec(value)?.[1];
|
|
27398
27422
|
}
|
|
27399
27423
|
|
|
27424
|
+
// src/lib/secretEnv.ts
|
|
27425
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
27426
|
+
var LINE_RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
27427
|
+
function parseDotenv(content) {
|
|
27428
|
+
const result = {};
|
|
27429
|
+
for (const rawLine of content.split("\n")) {
|
|
27430
|
+
const line = rawLine.trim();
|
|
27431
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
27432
|
+
const match = LINE_RE.exec(line);
|
|
27433
|
+
if (match) result[match[1]] = match[2];
|
|
27434
|
+
}
|
|
27435
|
+
return result;
|
|
27436
|
+
}
|
|
27437
|
+
function loadEnvFile(path) {
|
|
27438
|
+
return existsSync2(path) ? parseDotenv(readFileSync2(path, "utf-8")) : {};
|
|
27439
|
+
}
|
|
27440
|
+
function resolveSecretEnv(names, policy) {
|
|
27441
|
+
const result = {};
|
|
27442
|
+
if (policy.envFile) {
|
|
27443
|
+
const map = loadEnvFile(policy.envFile);
|
|
27444
|
+
for (const name of names) result[name] = map[name];
|
|
27445
|
+
} else {
|
|
27446
|
+
for (const name of names) result[name] = process.env[name];
|
|
27447
|
+
}
|
|
27448
|
+
return result;
|
|
27449
|
+
}
|
|
27450
|
+
|
|
27400
27451
|
// src/adapters/mcpPlan.ts
|
|
27401
27452
|
var HUB_ENTRY_NAME = "trellis-hub";
|
|
27402
27453
|
var CODEX_STDIO_URL_CRASH_NOTE = ' On Codex specifically, this crashes the entire process at startup ("url is not supported for stdio"), not just this one server \u2014 see docs/research.md.';
|
|
@@ -27410,7 +27461,9 @@ var DANGEROUS_LITERAL_PATTERNS = [
|
|
|
27410
27461
|
{ label: "mcp-router token (mcpr_)", pattern: /mcpr_/ }
|
|
27411
27462
|
];
|
|
27412
27463
|
function findLiteralSecret(def) {
|
|
27413
|
-
const candidates = [def.command, def.url, ...def.args ?? [], ...Object.values(def.headers ?? {})
|
|
27464
|
+
const candidates = [def.command, def.url, ...def.args ?? [], ...Object.values(def.headers ?? {}), ...Object.values(def.staticEnv ?? {})].filter(
|
|
27465
|
+
(v) => typeof v === "string"
|
|
27466
|
+
);
|
|
27414
27467
|
for (const candidate of candidates) {
|
|
27415
27468
|
for (const { label, pattern } of DANGEROUS_LITERAL_PATTERNS) {
|
|
27416
27469
|
if (pattern.test(candidate)) {
|
|
@@ -27420,7 +27473,13 @@ function findLiteralSecret(def) {
|
|
|
27420
27473
|
}
|
|
27421
27474
|
return void 0;
|
|
27422
27475
|
}
|
|
27423
|
-
function
|
|
27476
|
+
function findUnresolvedEnvName(def, policy) {
|
|
27477
|
+
const names = def.env ?? [];
|
|
27478
|
+
if (names.length === 0) return void 0;
|
|
27479
|
+
const resolved = resolveSecretEnv(names, policy);
|
|
27480
|
+
return names.find((name) => !resolved[name]);
|
|
27481
|
+
}
|
|
27482
|
+
function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
27424
27483
|
if (mcp.hub) {
|
|
27425
27484
|
if (mcp.knownHostInjected.includes(HUB_ENTRY_NAME)) {
|
|
27426
27485
|
return { desired: [], conflicts: [{ name: HUB_ENTRY_NAME, message: collisionMessage(HUB_ENTRY_NAME, agentId) }] };
|
|
@@ -27430,7 +27489,10 @@ function resolveMcpPlan(agentId, mcp) {
|
|
|
27430
27489
|
const desired = [];
|
|
27431
27490
|
const conflicts = [];
|
|
27432
27491
|
for (const [name, def] of Object.entries(mcp.servers)) {
|
|
27433
|
-
if (
|
|
27492
|
+
if (def.enabled === false) {
|
|
27493
|
+
continue;
|
|
27494
|
+
}
|
|
27495
|
+
if (!isInScope(agentId, def.agents, managedAgents)) {
|
|
27434
27496
|
continue;
|
|
27435
27497
|
}
|
|
27436
27498
|
if (mcp.knownHostInjected.includes(name)) {
|
|
@@ -27452,38 +27514,20 @@ function resolveMcpPlan(agentId, mcp) {
|
|
|
27452
27514
|
});
|
|
27453
27515
|
continue;
|
|
27454
27516
|
}
|
|
27517
|
+
const unresolvedName = findUnresolvedEnvName(def, policy);
|
|
27518
|
+
if (unresolvedName) {
|
|
27519
|
+
const source = policy.envFile ?? "process environment";
|
|
27520
|
+
conflicts.push({
|
|
27521
|
+
name,
|
|
27522
|
+
message: `refusing to write MCP server "${name}": its declared env var "${unresolvedName}" has no resolvable value in ${source} \u2014 writing it now would silently break this server's connection once the agent starts it (trellis-mcp-static-env-and-disabled-servers)`
|
|
27523
|
+
});
|
|
27524
|
+
continue;
|
|
27525
|
+
}
|
|
27455
27526
|
desired.push({ name, def });
|
|
27456
27527
|
}
|
|
27457
27528
|
return { desired, conflicts };
|
|
27458
27529
|
}
|
|
27459
27530
|
|
|
27460
|
-
// src/lib/secretEnv.ts
|
|
27461
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
27462
|
-
var LINE_RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
27463
|
-
function parseDotenv(content) {
|
|
27464
|
-
const result = {};
|
|
27465
|
-
for (const rawLine of content.split("\n")) {
|
|
27466
|
-
const line = rawLine.trim();
|
|
27467
|
-
if (line === "" || line.startsWith("#")) continue;
|
|
27468
|
-
const match = LINE_RE.exec(line);
|
|
27469
|
-
if (match) result[match[1]] = match[2];
|
|
27470
|
-
}
|
|
27471
|
-
return result;
|
|
27472
|
-
}
|
|
27473
|
-
function loadEnvFile(path) {
|
|
27474
|
-
return existsSync2(path) ? parseDotenv(readFileSync2(path, "utf-8")) : {};
|
|
27475
|
-
}
|
|
27476
|
-
function resolveSecretEnv(names, policy) {
|
|
27477
|
-
const result = {};
|
|
27478
|
-
if (policy.envFile) {
|
|
27479
|
-
const map = loadEnvFile(policy.envFile);
|
|
27480
|
-
for (const name of names) result[name] = map[name];
|
|
27481
|
-
} else {
|
|
27482
|
-
for (const name of names) result[name] = process.env[name];
|
|
27483
|
-
}
|
|
27484
|
-
return result;
|
|
27485
|
-
}
|
|
27486
|
-
|
|
27487
27531
|
// src/lib/envVarNames.ts
|
|
27488
27532
|
var TEMPLATE_VAR_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
27489
27533
|
function extractTemplateVarNames(value) {
|
|
@@ -31955,7 +31999,12 @@ async function connectStdio(def, secretsPolicy, timeoutMs) {
|
|
|
31955
31999
|
const transport = new StdioClientTransport({
|
|
31956
32000
|
command: def.command,
|
|
31957
32001
|
args: def.args,
|
|
31958
|
-
|
|
32002
|
+
// staticEnv merges last: a literal, intentionally-plain value (an
|
|
32003
|
+
// email, an environment tag) always wins over an unresolved name-only
|
|
32004
|
+
// entry's empty-string fallback for the same key — though in practice
|
|
32005
|
+
// resolveMcpPlan's D6 refusal never lets an unresolved name reach
|
|
32006
|
+
// this point at all (trellis-mcp-static-env-and-disabled-servers).
|
|
32007
|
+
env: { ...getDefaultEnvironment(), ...namedEnv, ...def.staticEnv ?? {} }
|
|
31959
32008
|
});
|
|
31960
32009
|
return connectWithCleanup(client, transport, timeoutMs, `connect timed out after ${timeoutMs}ms`);
|
|
31961
32010
|
}
|
|
@@ -32010,7 +32059,7 @@ function registerServerTools(pi, serverName, client, timeoutMs) {
|
|
|
32010
32059
|
}
|
|
32011
32060
|
async function trellisMcpBridge(pi, homeDir = homedir2(), connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
32012
32061
|
const canonical = loadCanonicalSource(homeDir);
|
|
32013
|
-
const { desired } = resolveMcpPlan("pi", canonical.mcp);
|
|
32062
|
+
const { desired } = resolveMcpPlan("pi", canonical.mcp, ALL_AGENTS, canonical.secretsPolicy);
|
|
32014
32063
|
const clients = /* @__PURE__ */ new Set();
|
|
32015
32064
|
const closeClient = async (client) => {
|
|
32016
32065
|
if (!clients.delete(client)) return;
|
package/dist/pi-bridge/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { loadCanonicalSource } from "../core/canonical.js";
|
|
|
20
20
|
import { resolveMcpPlan } from "../adapters/mcpPlan.js";
|
|
21
21
|
import { resolveSecretEnv } from "../lib/secretEnv.js";
|
|
22
22
|
import { extractTemplateVarNames } from "../lib/envVarNames.js";
|
|
23
|
+
import { ALL_AGENTS } from "../core/types.js";
|
|
23
24
|
import { bridgedToolName, toParametersSchema, toPiContent } from "./schemaTranslate.js";
|
|
24
25
|
const CLIENT_INFO = { name: "trellis-mcp-bridge", version: "0.0.0" };
|
|
25
26
|
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
@@ -74,7 +75,12 @@ async function connectStdio(def, secretsPolicy, timeoutMs) {
|
|
|
74
75
|
const transport = new StdioClientTransport({
|
|
75
76
|
command: def.command,
|
|
76
77
|
args: def.args,
|
|
77
|
-
|
|
78
|
+
// staticEnv merges last: a literal, intentionally-plain value (an
|
|
79
|
+
// email, an environment tag) always wins over an unresolved name-only
|
|
80
|
+
// entry's empty-string fallback for the same key — though in practice
|
|
81
|
+
// resolveMcpPlan's D6 refusal never lets an unresolved name reach
|
|
82
|
+
// this point at all (trellis-mcp-static-env-and-disabled-servers).
|
|
83
|
+
env: { ...getDefaultEnvironment(), ...namedEnv, ...(def.staticEnv ?? {}) },
|
|
78
84
|
});
|
|
79
85
|
return connectWithCleanup(client, transport, timeoutMs, `connect timed out after ${timeoutMs}ms`);
|
|
80
86
|
}
|
|
@@ -128,7 +134,13 @@ function registerServerTools(pi, serverName, client, timeoutMs) {
|
|
|
128
134
|
*/
|
|
129
135
|
export default async function trellisMcpBridge(pi, homeDir = homedir(), connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
130
136
|
const canonical = loadCanonicalSource(homeDir);
|
|
131
|
-
|
|
137
|
+
// Deliberately ALL_AGENTS, not canonical.managedAgents: this bridge only
|
|
138
|
+
// ever runs because pi itself loaded the extension — that's already the
|
|
139
|
+
// strongest possible consent signal (trellis-managed-agents design.md),
|
|
140
|
+
// independent of whether `trellis onboard` was ever run to add pi to
|
|
141
|
+
// managed.yaml. managedAgents governs static config-file writes; this
|
|
142
|
+
// is pi reading canonical directly at its own runtime, P4's own concern.
|
|
143
|
+
const { desired } = resolveMcpPlan("pi", canonical.mcp, ALL_AGENTS, canonical.secretsPolicy);
|
|
132
144
|
const clients = new Set();
|
|
133
145
|
const closeClient = async (client) => {
|
|
134
146
|
if (!clients.delete(client))
|
package/dist/probes/codex.js
CHANGED
|
@@ -36,16 +36,24 @@ export async function probe(homeDir = homedir(), opts = {}) {
|
|
|
36
36
|
return { agent: "codex", present: false, skillRoots: [], mcpServers: [], diagnostics: [] };
|
|
37
37
|
}
|
|
38
38
|
const diagnostics = [];
|
|
39
|
+
// `codex` resolves its own config via $HOME (confirmed by running it with
|
|
40
|
+
// an overridden HOME against an empty scratch dir — it returns `[]`, not
|
|
41
|
+
// the real machine's servers), so every subprocess call here must be
|
|
42
|
+
// scoped to `homeDir` explicitly — otherwise a caller probing a non-
|
|
43
|
+
// default `homeDir` would silently get this real machine's real MCP
|
|
44
|
+
// server list instead (trellis-migrate-mcp-servers found this gap in
|
|
45
|
+
// src/lib/mcpMigrateRead.ts's own equivalent call; fixed there first).
|
|
46
|
+
const codexEnv = { ...process.env, HOME: homeDir };
|
|
39
47
|
let version;
|
|
40
48
|
try {
|
|
41
|
-
version = execFileSync("codex", ["--version"], { encoding: "utf-8", timeout: 5_000 }).trim();
|
|
49
|
+
version = execFileSync("codex", ["--version"], { encoding: "utf-8", timeout: 5_000, env: codexEnv }).trim();
|
|
42
50
|
}
|
|
43
51
|
catch {
|
|
44
52
|
// config exists without the binary on PATH — unusual, still probeable
|
|
45
53
|
}
|
|
46
54
|
let mcpEntries = [];
|
|
47
55
|
try {
|
|
48
|
-
const raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000 });
|
|
56
|
+
const raw = execFileSync("codex", ["mcp", "list", "--json"], { encoding: "utf-8", timeout: 5_000, env: codexEnv });
|
|
49
57
|
mcpEntries = JSON.parse(raw);
|
|
50
58
|
}
|
|
51
59
|
catch (err) {
|
package/docs/architecture.md
CHANGED
|
@@ -45,10 +45,13 @@ Aligned to the `.agents Protocol` draft, extended where the draft is silent:
|
|
|
45
45
|
├── memories/*.md # shared memory entries (server-memory backed)
|
|
46
46
|
├── scope.yaml # exceptions to "shared with all agents" —
|
|
47
47
|
│ # see "Private / agent-specific capabilities"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
└── secrets.policy.yaml # which var names are allowed, nothing else
|
|
49
|
+
|
|
50
|
+
# Not user-authored, so not shown in the tree above — Trellis's own
|
|
51
|
+
# bookkeeping, written/read only by `trellis mcp sync` itself:
|
|
52
|
+
# mcp/ownership.json # what Trellis last wrote per (agent, server name) —
|
|
53
|
+
# # the ownership marker MCP removal needed
|
|
54
|
+
# # (docs/roadmap.md P14, src/lib/mcpOwnership.ts)
|
|
52
55
|
```
|
|
53
56
|
|
|
54
57
|
## Global vs. workspace scope
|