agent-trellis 0.14.0 → 0.15.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.
Files changed (57) hide show
  1. package/dist/adapters/claude-code.js +4 -3
  2. package/dist/adapters/codex.js +4 -3
  3. package/dist/adapters/kimi-code.js +4 -3
  4. package/dist/adapters/kiro.js +5 -4
  5. package/dist/adapters/pi.js +4 -3
  6. package/dist/adapters/piMcp.d.ts +6 -3
  7. package/dist/adapters/piMcp.js +10 -3
  8. package/dist/adapters/zcode.d.ts +12 -1
  9. package/dist/adapters/zcode.js +7 -6
  10. package/dist/cli.js +38 -5
  11. package/dist/commands/doctor.d.ts +9 -1
  12. package/dist/commands/doctor.js +43 -6
  13. package/dist/commands/init.d.ts +6 -0
  14. package/dist/commands/init.js +24 -0
  15. package/dist/commands/mcp.d.ts +44 -1
  16. package/dist/commands/mcp.js +145 -33
  17. package/dist/commands/migrate.d.ts +12 -0
  18. package/dist/commands/migrate.js +8 -4
  19. package/dist/commands/migrateWorkspace.d.ts +40 -0
  20. package/dist/commands/migrateWorkspace.js +197 -0
  21. package/dist/commands/rollback.js +65 -35
  22. package/dist/commands/skill.d.ts +14 -0
  23. package/dist/commands/skill.js +71 -0
  24. package/dist/commands/sync.d.ts +16 -1
  25. package/dist/commands/sync.js +48 -15
  26. package/dist/core/adapter.d.ts +14 -0
  27. package/dist/core/canonical.d.ts +20 -32
  28. package/dist/core/canonical.js +18 -86
  29. package/dist/core/mcpServerDef.d.ts +39 -0
  30. package/dist/core/mcpServerDef.js +93 -0
  31. package/dist/core/types.d.ts +43 -4
  32. package/dist/lib/mcpMigrateRead.d.ts +25 -0
  33. package/dist/lib/mcpMigrateRead.js +9 -2
  34. package/dist/lib/skillProvider.js +18 -5
  35. package/dist/lib/workspace.d.ts +39 -0
  36. package/dist/lib/workspace.js +158 -0
  37. package/dist/lib/workspaceConfig.d.ts +45 -0
  38. package/dist/lib/workspaceConfig.js +98 -0
  39. package/dist/lib/workspaceDoctor.d.ts +44 -0
  40. package/dist/lib/workspaceDoctor.js +94 -0
  41. package/dist/lib/workspaceFiles.d.ts +38 -0
  42. package/dist/lib/workspaceFiles.js +264 -0
  43. package/dist/lib/workspaceMcp.d.ts +57 -0
  44. package/dist/lib/workspaceMcp.js +277 -0
  45. package/dist/lib/workspaceMerge.d.ts +37 -0
  46. package/dist/lib/workspaceMerge.js +98 -0
  47. package/dist/lib/workspaceMigrate.d.ts +118 -0
  48. package/dist/lib/workspaceMigrate.js +423 -0
  49. package/dist/pi-bridge/bundle.js +262 -96
  50. package/docs/architecture.md +111 -49
  51. package/docs/getting-started.md +102 -14
  52. package/docs/mcp-oauth-matrix.md +12 -1
  53. package/docs/overview.zh-CN.md +17 -0
  54. package/docs/research.md +28 -6
  55. package/docs/roadmap.md +17 -5
  56. package/package.json +1 -1
  57. package/docs/implementation-plan.md +0 -341
@@ -7463,12 +7463,12 @@ var require_isexe = __commonJS({
7463
7463
  if (typeof Promise !== "function") {
7464
7464
  throw new TypeError("callback not provided");
7465
7465
  }
7466
- return new Promise(function(resolve, reject) {
7466
+ return new Promise(function(resolve2, reject) {
7467
7467
  isexe(path, options || {}, function(er, is) {
7468
7468
  if (er) {
7469
7469
  reject(er);
7470
7470
  } else {
7471
- resolve(is);
7471
+ resolve2(is);
7472
7472
  }
7473
7473
  });
7474
7474
  });
@@ -7534,27 +7534,27 @@ var require_which = __commonJS({
7534
7534
  opt = {};
7535
7535
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
7536
7536
  const found = [];
7537
- const step = (i) => new Promise((resolve, reject) => {
7537
+ const step = (i) => new Promise((resolve2, reject) => {
7538
7538
  if (i === pathEnv.length)
7539
- return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
7539
+ return opt.all && found.length ? resolve2(found) : reject(getNotFoundError(cmd));
7540
7540
  const ppRaw = pathEnv[i];
7541
7541
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
7542
7542
  const pCmd = path.join(pathPart, cmd);
7543
7543
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
7544
- resolve(subStep(p, i, 0));
7544
+ resolve2(subStep(p, i, 0));
7545
7545
  });
7546
- const subStep = (p, i, ii) => new Promise((resolve, reject) => {
7546
+ const subStep = (p, i, ii) => new Promise((resolve2, reject) => {
7547
7547
  if (ii === pathExt.length)
7548
- return resolve(step(i + 1));
7548
+ return resolve2(step(i + 1));
7549
7549
  const ext = pathExt[ii];
7550
7550
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
7551
7551
  if (!er && is) {
7552
7552
  if (opt.all)
7553
7553
  found.push(p + ext);
7554
7554
  else
7555
- return resolve(p + ext);
7555
+ return resolve2(p + ext);
7556
7556
  }
7557
- return resolve(subStep(p, i, ii + 1));
7557
+ return resolve2(subStep(p, i, ii + 1));
7558
7558
  });
7559
7559
  });
7560
7560
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -7871,9 +7871,9 @@ import { homedir as homedir2 } from "node:os";
7871
7871
 
7872
7872
  // src/core/canonical.ts
7873
7873
  var import_yaml = __toESM(require_dist(), 1);
7874
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
7874
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, writeFileSync } from "node:fs";
7875
7875
  import { homedir } from "node:os";
7876
- import { basename, dirname, join } from "node:path";
7876
+ import { basename, dirname as dirname2, join as join2 } from "node:path";
7877
7877
 
7878
7878
  // src/core/types.ts
7879
7879
  function isOAuthAuth(auth2) {
@@ -7936,65 +7936,224 @@ function resolveSecretEnv(names, policy) {
7936
7936
  return result;
7937
7937
  }
7938
7938
 
7939
- // src/core/canonical.ts
7940
- function fromServerDefYaml(serverName, def) {
7939
+ // src/core/mcpServerDef.ts
7940
+ function fromServerDefYaml(serverName, def, source = "mcp/servers.yaml") {
7941
7941
  const { static_env, env_aliases, auth: auth2, ...rest } = def;
7942
7942
  const out = { ...rest };
7943
7943
  if (static_env) out.staticEnv = static_env;
7944
7944
  if (env_aliases) out.envAliases = env_aliases;
7945
- if (auth2 !== void 0) out.auth = fromAuthYaml(serverName, auth2);
7945
+ if (auth2 !== void 0) out.auth = fromAuthYaml(serverName, auth2, source);
7946
7946
  return out;
7947
7947
  }
7948
- function fromAuthYaml(serverName, auth2) {
7948
+ function fromAuthYaml(serverName, auth2, source) {
7949
7949
  if (auth2 === "oauth") return "oauth";
7950
7950
  if (typeof auth2 !== "object" || auth2 === null) {
7951
- throw new Error(`mcp/servers.yaml: "${serverName}" auth must be the scalar "oauth" or { kind: oauth, client_id?, client_secret_env? }`);
7951
+ throw new Error(`${source}: "${serverName}" auth must be the scalar "oauth" or { kind: oauth, client_id?, client_secret_env?, client_name? }`);
7952
7952
  }
7953
- const { kind, owner, client_id, client_secret_env, ...extra } = auth2;
7953
+ const { kind, owner, client_id, client_secret_env, client_name, ...extra } = auth2;
7954
7954
  const unknownKey = Object.keys(extra)[0];
7955
7955
  if (unknownKey !== void 0) {
7956
- throw new Error(`mcp/servers.yaml: "${serverName}" auth has unrecognized key "${unknownKey}" \u2014 expected kind, owner, client_id, client_secret_env`);
7956
+ throw new Error(`${source}: "${serverName}" auth has unrecognized key "${unknownKey}" \u2014 expected kind, owner, client_id, client_secret_env, client_name`);
7957
7957
  }
7958
7958
  if (kind !== "oauth") {
7959
- throw new Error(`mcp/servers.yaml: "${serverName}" auth.kind must be "oauth" \u2014 kind is the only recognized discriminator`);
7959
+ throw new Error(`${source}: "${serverName}" auth.kind must be "oauth" \u2014 kind is the only recognized discriminator`);
7960
7960
  }
7961
7961
  if (client_id !== void 0 && typeof client_id !== "string") {
7962
- throw new Error(`mcp/servers.yaml: "${serverName}" auth.client_id must be a string`);
7962
+ throw new Error(`${source}: "${serverName}" auth.client_id must be a string`);
7963
7963
  }
7964
7964
  if (client_secret_env !== void 0 && (typeof client_secret_env !== "string" || !isValidSecretVarName(client_secret_env))) {
7965
- throw new Error(`mcp/servers.yaml: "${serverName}" auth.client_secret_env must be a variable NAME (e.g. FIGMA_CLIENT_SECRET), never a secret value`);
7965
+ throw new Error(`${source}: "${serverName}" auth.client_secret_env must be a variable NAME (e.g. FIGMA_CLIENT_SECRET), never a secret value`);
7966
+ }
7967
+ if (client_name !== void 0 && typeof client_name !== "string") {
7968
+ throw new Error(`${source}: "${serverName}" auth.client_name must be a string`);
7966
7969
  }
7967
7970
  const out = { kind: "oauth" };
7968
- if (owner !== void 0 && owner !== "agent" && owner !== "trellis") throw new Error(`mcp/servers.yaml: "${serverName}" auth.owner must be agent or trellis`);
7971
+ if (owner !== void 0 && owner !== "agent" && owner !== "trellis") throw new Error(`${source}: "${serverName}" auth.owner must be agent or trellis`);
7969
7972
  if (owner !== void 0) out.owner = owner;
7970
7973
  if (typeof client_id === "string") out.clientId = client_id;
7971
7974
  if (typeof client_secret_env === "string") out.clientSecretEnv = client_secret_env;
7975
+ if (typeof client_name === "string") out.clientName = client_name;
7972
7976
  return out;
7973
7977
  }
7978
+
7979
+ // src/lib/workspace.ts
7980
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
7981
+ import { dirname, join, resolve } from "node:path";
7982
+ var WORKSPACE_DIR_NAME = ".agents";
7983
+ var WORKSPACE_CONFIG_VERSION = 1;
7984
+ var CONFIG_SOURCE_LABEL = ".agents/config.json";
7985
+ function isTombstone(def) {
7986
+ return def.enabled === false && def.transport === void 0;
7987
+ }
7988
+ function loadWorkspaceLayer(workspaceDir, policy) {
7989
+ const layerRoot = join(workspaceDir, WORKSPACE_DIR_NAME);
7990
+ const diagnostics = [];
7991
+ const layer = { root: workspaceDir, servers: {}, tombstones: [], skills: [], diagnostics };
7992
+ const configPath = join(layerRoot, "config.json");
7993
+ if (existsSync2(configPath)) {
7994
+ let parsed;
7995
+ try {
7996
+ parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
7997
+ } catch (err) {
7998
+ throw new Error(`${CONFIG_SOURCE_LABEL}: invalid JSON \u2014 ${err.message}`);
7999
+ }
8000
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8001
+ throw new Error(`${CONFIG_SOURCE_LABEL}: top level must be an object { "version", "mcp" }`);
8002
+ }
8003
+ const { version: version2, mcp, ...extra } = parsed;
8004
+ const configVersion = version2 === void 0 ? WORKSPACE_CONFIG_VERSION : version2;
8005
+ if (typeof configVersion !== "number" || !Number.isInteger(configVersion) || configVersion < 1) {
8006
+ throw new Error(`${CONFIG_SOURCE_LABEL}: "version" must be a positive integer, got ${JSON.stringify(version2)}`);
8007
+ }
8008
+ if (configVersion > WORKSPACE_CONFIG_VERSION) {
8009
+ throw new Error(
8010
+ `${CONFIG_SOURCE_LABEL}: schema version ${configVersion} is newer than this Trellis understands (${WORKSPACE_CONFIG_VERSION}) \u2014 upgrade Trellis`
8011
+ );
8012
+ }
8013
+ for (const key of Object.keys(extra)) {
8014
+ diagnostics.push(`${CONFIG_SOURCE_LABEL}: unrecognized top-level key "${key}" \u2014 ignored`);
8015
+ }
8016
+ if (mcp !== void 0) {
8017
+ if (typeof mcp !== "object" || mcp === null || typeof mcp.servers !== "object" || mcp.servers === null || Array.isArray(mcp.servers)) {
8018
+ throw new Error(`${CONFIG_SOURCE_LABEL}: "mcp.servers" must be an object mapping server names to definitions`);
8019
+ }
8020
+ for (const [name, rawDef] of Object.entries(mcp.servers)) {
8021
+ if (typeof rawDef !== "object" || rawDef === null) {
8022
+ throw new Error(`${CONFIG_SOURCE_LABEL}: "${name}" must be a server definition object or { "enabled": false }`);
8023
+ }
8024
+ if (isTombstone(rawDef)) {
8025
+ layer.tombstones.push(name);
8026
+ continue;
8027
+ }
8028
+ if (rawDef.enabled === false) {
8029
+ layer.tombstones.push(name);
8030
+ continue;
8031
+ }
8032
+ layer.servers[name] = fromServerDefYaml(name, rawDef, CONFIG_SOURCE_LABEL);
8033
+ }
8034
+ }
8035
+ }
8036
+ const instructionsPath = join(layerRoot, "AGENTS.md");
8037
+ if (existsSync2(instructionsPath)) {
8038
+ layer.instructionsFile = instructionsPath;
8039
+ }
8040
+ const skillsRoot = join(layerRoot, "skills");
8041
+ let skillEntries = [];
8042
+ try {
8043
+ skillEntries = readdirSync(skillsRoot);
8044
+ } catch {
8045
+ }
8046
+ for (const name of skillEntries) {
8047
+ const dir = join(skillsRoot, name);
8048
+ try {
8049
+ if (statSync(dir).isDirectory()) layer.skills.push({ name, dir });
8050
+ } catch {
8051
+ }
8052
+ }
8053
+ assertWorkspaceSecretsClean(layer.servers, policy);
8054
+ return layer;
8055
+ }
8056
+ var VAR_REF_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
8057
+ function assertWorkspaceSecretsClean(servers, policy) {
8058
+ const check2 = (server, field, value) => {
8059
+ if (value === void 0 || VAR_REF_RE.test(value)) return;
8060
+ for (const pattern of policy.rejectPatterns) {
8061
+ if (pattern.test(value)) {
8062
+ throw new Error(
8063
+ `${CONFIG_SOURCE_LABEL}: "${server}" ${field} matches a secrets.policy.yaml reject pattern \u2014 the project layer is committable and must carry only \${VAR} references; move the value to the global secret store (the offending value is intentionally not printed)`
8064
+ );
8065
+ }
8066
+ }
8067
+ };
8068
+ for (const [name, def] of Object.entries(servers)) {
8069
+ for (const entry of def.env ?? []) check2(name, "env", entry);
8070
+ for (const [key, value] of Object.entries(def.staticEnv ?? {})) check2(name, `static_env.${key}`, value);
8071
+ for (const [key, value] of Object.entries(def.headers ?? {})) check2(name, `headers.${key}`, value);
8072
+ const meta3 = oauthClientMetadata(def.auth);
8073
+ check2(name, "auth.client_id", meta3.clientId);
8074
+ check2(name, "auth.client_secret_env", meta3.clientSecretEnv);
8075
+ }
8076
+ }
8077
+
8078
+ // src/lib/workspaceMerge.ts
8079
+ function mergeWorkspaceLayer(base, layer) {
8080
+ const shadowed = [];
8081
+ const mcpGlobalServers = { ...base.mcp.servers };
8082
+ const servers = { ...base.mcp.servers };
8083
+ const mcpProvenance = Object.fromEntries(
8084
+ Object.keys(base.mcp.servers).map((name) => [name, "global"])
8085
+ );
8086
+ for (const name of layer.tombstones) {
8087
+ if (servers[name] !== void 0) {
8088
+ delete servers[name];
8089
+ delete mcpProvenance[name];
8090
+ shadowed.push(`mcp:${name}`);
8091
+ } else {
8092
+ layer.diagnostics.push(`.agents/config.json: tombstone "${name}" matches no global server \u2014 no-op`);
8093
+ }
8094
+ }
8095
+ for (const [name, def] of Object.entries(layer.servers)) {
8096
+ if (servers[name] !== void 0) shadowed.push(`mcp:${name}`);
8097
+ servers[name] = def;
8098
+ mcpProvenance[name] = "workspace";
8099
+ }
8100
+ const skills = [...base.skills];
8101
+ const globalSkills = [...base.skills];
8102
+ const skillProvenance = Object.fromEntries(
8103
+ base.skills.map((s) => [s.name, "global"])
8104
+ );
8105
+ for (const wsSkill of layer.skills) {
8106
+ const idx = skills.findIndex((s) => s.name === wsSkill.name);
8107
+ if (idx >= 0) {
8108
+ shadowed.push(`skill:${wsSkill.name}`);
8109
+ skills[idx] = wsSkill;
8110
+ } else {
8111
+ skills.push(wsSkill);
8112
+ }
8113
+ skillProvenance[wsSkill.name] = "workspace";
8114
+ }
8115
+ return {
8116
+ ...base,
8117
+ skills,
8118
+ mcp: { ...base.mcp, servers },
8119
+ diagnostics: [...base.diagnostics, ...layer.diagnostics],
8120
+ workspace: {
8121
+ root: layer.root,
8122
+ ...layer.instructionsFile !== void 0 ? { instructionsFile: layer.instructionsFile } : {},
8123
+ mcpProvenance,
8124
+ mcpGlobalServers,
8125
+ skillProvenance,
8126
+ globalSkills,
8127
+ shadowed
8128
+ }
8129
+ };
8130
+ }
8131
+
8132
+ // src/core/canonical.ts
7974
8133
  function trellisRoot(homeDir) {
7975
- return join(homeDir, ".trellis");
8134
+ return join2(homeDir, ".trellis");
7976
8135
  }
7977
8136
  function listMarkdownFiles(dir) {
7978
8137
  let entries;
7979
8138
  try {
7980
- entries = readdirSync(dir);
8139
+ entries = readdirSync2(dir);
7981
8140
  } catch {
7982
8141
  return [];
7983
8142
  }
7984
- return entries.filter((name) => name.endsWith(".md")).map((name) => ({ name: basename(name, ".md"), file: join(dir, name) }));
8143
+ return entries.filter((name) => name.endsWith(".md")).map((name) => ({ name: basename(name, ".md"), file: join2(dir, name) }));
7985
8144
  }
7986
8145
  function listSkillDirs(dir) {
7987
8146
  let entries;
7988
8147
  try {
7989
- entries = readdirSync(dir);
8148
+ entries = readdirSync2(dir);
7990
8149
  } catch {
7991
8150
  return [];
7992
8151
  }
7993
8152
  const skills = [];
7994
8153
  for (const name of entries) {
7995
- const skillDir = join(dir, name);
8154
+ const skillDir = join2(dir, name);
7996
8155
  try {
7997
- if (statSync(skillDir).isDirectory()) {
8156
+ if (statSync2(skillDir).isDirectory()) {
7998
8157
  skills.push({ name, dir: skillDir });
7999
8158
  }
8000
8159
  } catch {
@@ -8003,17 +8162,17 @@ function listSkillDirs(dir) {
8003
8162
  return skills;
8004
8163
  }
8005
8164
  function loadScopeYaml(path) {
8006
- if (!existsSync2(path)) {
8165
+ if (!existsSync3(path)) {
8007
8166
  return {};
8008
8167
  }
8009
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8"));
8168
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8"));
8010
8169
  return parsed ?? {};
8011
8170
  }
8012
8171
  function loadServersYaml(path) {
8013
- if (!existsSync2(path)) {
8172
+ if (!existsSync3(path)) {
8014
8173
  return { servers: {}, knownHostInjected: [] };
8015
8174
  }
8016
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8175
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8017
8176
  const servers = Object.fromEntries(Object.entries(parsed.servers ?? {}).map(([name, def]) => [name, fromServerDefYaml(name, def)]));
8018
8177
  return {
8019
8178
  servers,
@@ -8054,10 +8213,10 @@ function fromGatewayYaml(gateway) {
8054
8213
  return out;
8055
8214
  }
8056
8215
  function loadSecretsPolicyYaml(path, homeDir) {
8057
- if (!existsSync2(path)) {
8216
+ if (!existsSync3(path)) {
8058
8217
  return { allowedVars: [], rejectPatterns: [] };
8059
8218
  }
8060
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8219
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8061
8220
  return {
8062
8221
  allowedVars: parsed.allowed_vars ?? [],
8063
8222
  rejectPatterns: (parsed.reject_patterns ?? []).map((pattern) => new RegExp(pattern)),
@@ -8065,10 +8224,10 @@ function loadSecretsPolicyYaml(path, homeDir) {
8065
8224
  };
8066
8225
  }
8067
8226
  function loadManagedYaml(path, diagnostics) {
8068
- if (!existsSync2(path)) {
8227
+ if (!existsSync3(path)) {
8069
8228
  return [];
8070
8229
  }
8071
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8230
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8072
8231
  const raw = parsed.agents ?? [];
8073
8232
  const valid = [];
8074
8233
  for (const id of raw) {
@@ -8106,27 +8265,29 @@ function assertAuthMetadataClean(mcp, policy) {
8106
8265
  }
8107
8266
  }
8108
8267
  }
8109
- function loadCanonicalSource(homeDir = homedir()) {
8268
+ function loadCanonicalSource(arg = {}) {
8269
+ const options = typeof arg === "string" ? { homeDir: arg } : arg;
8270
+ const homeDir = options.homeDir ?? homedir();
8110
8271
  const root = trellisRoot(homeDir);
8111
- if (!existsSync2(root)) {
8272
+ if (!existsSync3(root)) {
8112
8273
  throw new Error(
8113
8274
  `No canonical source at ${root}. Run trellis init before trellis sync.`
8114
8275
  );
8115
8276
  }
8116
8277
  const diagnostics = [];
8117
- const managedAgents = loadManagedYaml(join(root, "managed.yaml"), diagnostics);
8118
- const scopeYaml = loadScopeYaml(join(root, "scope.yaml"));
8119
- const skillDirs = listSkillDirs(join(root, "skills"));
8278
+ const managedAgents = loadManagedYaml(join2(root, "managed.yaml"), diagnostics);
8279
+ const scopeYaml = loadScopeYaml(join2(root, "scope.yaml"));
8280
+ const skillDirs = listSkillDirs(join2(root, "skills"));
8120
8281
  const knownSkillNames = new Set(skillDirs.map((s) => s.name));
8121
8282
  const skills = skillDirs.map(({ name, dir }) => ({ name, dir, scope: scopeFor(scopeYaml.skills, name) }));
8122
- const agentFiles = listMarkdownFiles(join(root, "agents"));
8283
+ const agentFiles = listMarkdownFiles(join2(root, "agents"));
8123
8284
  const knownAgentProfileNames = new Set(agentFiles.map((a) => a.name));
8124
8285
  const agents = agentFiles.map(({ name, file: file2 }) => ({
8125
8286
  name,
8126
8287
  file: file2,
8127
8288
  scope: scopeFor(scopeYaml.agents, name)
8128
8289
  }));
8129
- const memoryFiles = listMarkdownFiles(join(root, "memories"));
8290
+ const memoryFiles = listMarkdownFiles(join2(root, "memories"));
8130
8291
  const knownMemoryNames = new Set(memoryFiles.map((m) => m.name));
8131
8292
  const memories = memoryFiles.map(({ name, file: file2 }) => ({
8132
8293
  name,
@@ -8147,11 +8308,11 @@ function loadCanonicalSource(homeDir = homedir()) {
8147
8308
  }
8148
8309
  }
8149
8310
  }
8150
- const mcp = loadServersYaml(join(root, "mcp", "servers.yaml"));
8151
- const secretsPolicy = loadSecretsPolicyYaml(join(root, "secrets.policy.yaml"), homeDir);
8311
+ const mcp = loadServersYaml(join2(root, "mcp", "servers.yaml"));
8312
+ const secretsPolicy = loadSecretsPolicyYaml(join2(root, "secrets.policy.yaml"), homeDir);
8152
8313
  assertAuthMetadataClean(mcp, secretsPolicy);
8153
- return {
8154
- instructionsFile: join(root, "agents.md"),
8314
+ const source = {
8315
+ instructionsFile: join2(root, "agents.md"),
8155
8316
  managedAgents,
8156
8317
  skills,
8157
8318
  agents,
@@ -8160,6 +8321,11 @@ function loadCanonicalSource(homeDir = homedir()) {
8160
8321
  secretsPolicy,
8161
8322
  diagnostics
8162
8323
  };
8324
+ if (options.workspaceDir) {
8325
+ const layer = loadWorkspaceLayer(options.workspaceDir, secretsPolicy);
8326
+ return mergeWorkspaceLayer(source, layer);
8327
+ }
8328
+ return source;
8163
8329
  }
8164
8330
 
8165
8331
  // src/core/adapter.ts
@@ -13464,7 +13630,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
13464
13630
  var NONE = 0;
13465
13631
  var ASSUMED = 1;
13466
13632
  var PROVEN = 2;
13467
- function isRecursive(inst, stack, resolve) {
13633
+ function isRecursive(inst, stack, resolve2) {
13468
13634
  const cached2 = recursive.get(inst);
13469
13635
  if (cached2 !== void 0)
13470
13636
  return cached2 ? PROVEN : NONE;
@@ -13474,7 +13640,7 @@ function isRecursive(inst, stack, resolve) {
13474
13640
  let result = NONE;
13475
13641
  const check2 = (child) => {
13476
13642
  if (result !== PROVEN && child?._zod) {
13477
- const answer = isRecursive(child, stack, resolve);
13643
+ const answer = isRecursive(child, stack, resolve2);
13478
13644
  if (answer > result)
13479
13645
  result = answer;
13480
13646
  }
@@ -13485,7 +13651,7 @@ function isRecursive(inst, stack, resolve) {
13485
13651
  const desc = Object.getOwnPropertyDescriptor(sh, key);
13486
13652
  if (spread && !desc.enumerable)
13487
13653
  continue;
13488
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
13654
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve2) : NONE;
13489
13655
  if (child > answer)
13490
13656
  answer = child;
13491
13657
  }
@@ -13549,7 +13715,7 @@ function isRecursive(inst, stack, resolve) {
13549
13715
  break;
13550
13716
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
13551
13717
  case "lazy": {
13552
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
13718
+ const inner = def._cachedInner ?? (resolve2 ? inst._zod.innerType : void 0);
13553
13719
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
13554
13720
  break;
13555
13721
  }
@@ -32471,14 +32637,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
32471
32637
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
32472
32638
  }
32473
32639
  function sleep(ms, signal) {
32474
- return new Promise((resolve, reject) => {
32640
+ return new Promise((resolve2, reject) => {
32475
32641
  if (signal?.aborted) {
32476
32642
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
32477
32643
  return;
32478
32644
  }
32479
32645
  const timer = setTimeout(() => {
32480
32646
  signal?.removeEventListener("abort", onAbort);
32481
- resolve();
32647
+ resolve2();
32482
32648
  }, ms);
32483
32649
  const onAbort = () => {
32484
32650
  clearTimeout(timer);
@@ -33322,7 +33488,7 @@ var Protocol = class {
33322
33488
  const flowStartedAt = Date.now();
33323
33489
  let onAbort;
33324
33490
  let cleanupMessageId;
33325
- return new Promise((resolve, reject) => {
33491
+ return new Promise((resolve2, reject) => {
33326
33492
  const earlyReject = (error62) => {
33327
33493
  reject(error62);
33328
33494
  };
@@ -33391,7 +33557,7 @@ var Protocol = class {
33391
33557
  }
33392
33558
  if (decoded.kind === "invalid") return reject(decoded.error);
33393
33559
  if (decoded.kind === "input_required") {
33394
- if (options?.allowInputRequired === true) return resolve(manualInputRequiredValue(decoded));
33560
+ if (options?.allowInputRequired === true) return resolve2(manualInputRequiredValue(decoded));
33395
33561
  const flow = {
33396
33562
  codec: codec2,
33397
33563
  request,
@@ -33403,11 +33569,11 @@ var Protocol = class {
33403
33569
  params
33404
33570
  }, resultSchema, legOptions)
33405
33571
  };
33406
- return resolve(this._resolveNonCompleteResult(decoded, flow));
33572
+ return resolve2(this._resolveNonCompleteResult(decoded, flow));
33407
33573
  }
33408
33574
  const result = decoded.result;
33409
33575
  validateStandardSchema(resultSchema, result).then((parseResult) => {
33410
- if (parseResult.success) resolve(parseResult.data);
33576
+ if (parseResult.success) resolve2(parseResult.data);
33411
33577
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
33412
33578
  }, reject);
33413
33579
  });
@@ -36424,7 +36590,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
36424
36590
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
36425
36591
  const schOrFunc = root.refs[ref];
36426
36592
  if (schOrFunc) return schOrFunc;
36427
- let _sch = resolve.call(this, root, ref);
36593
+ let _sch = resolve2.call(this, root, ref);
36428
36594
  if (_sch === void 0) {
36429
36595
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
36430
36596
  const { schemaId } = this.opts;
@@ -36450,7 +36616,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
36450
36616
  function sameSchemaEnv(s1, s2) {
36451
36617
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
36452
36618
  }
36453
- function resolve(root, ref) {
36619
+ function resolve2(root, ref) {
36454
36620
  let sch;
36455
36621
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
36456
36622
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -36900,7 +37066,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36900
37066
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
36901
37067
  return uri;
36902
37068
  }
36903
- function resolve(baseURI, relativeURI, options) {
37069
+ function resolve2(baseURI, relativeURI, options) {
36904
37070
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
36905
37071
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
36906
37072
  schemelessOptions.skipEscape = true;
@@ -37074,7 +37240,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
37074
37240
  const fastUri = {
37075
37241
  SCHEMES,
37076
37242
  normalize,
37077
- resolve,
37243
+ resolve: resolve2,
37078
37244
  resolveComponent,
37079
37245
  equal,
37080
37246
  serialize,
@@ -42939,14 +43105,14 @@ var ProbeWindow = class ProbeWindow2 {
42939
43105
  */
42940
43106
  async exchange(buildRequest, timeoutMs) {
42941
43107
  const id = `server-discover-probe-${++this._probeCounter}`;
42942
- return new Promise((resolve) => {
43108
+ return new Promise((resolve2) => {
42943
43109
  let settled = false;
42944
43110
  const settle2 = (reply) => {
42945
43111
  if (settled) return;
42946
43112
  settled = true;
42947
43113
  clearTimeout(timer);
42948
43114
  if (this._pending?.id === id) this._pending = void 0;
42949
- resolve(reply);
43115
+ resolve2(reply);
42950
43116
  };
42951
43117
  const timer = setTimeout(() => settle2({ kind: "timeout" }), timeoutMs);
42952
43118
  this._pending = {
@@ -44160,13 +44326,13 @@ var Client = class extends Protocol {
44160
44326
  let onCallerAbort;
44161
44327
  let resolveOpening;
44162
44328
  let rejectOpening;
44163
- const opening = new Promise((resolve, reject) => {
44164
- resolveOpening = resolve;
44329
+ const opening = new Promise((resolve2, reject) => {
44330
+ resolveOpening = resolve2;
44165
44331
  rejectOpening = reject;
44166
44332
  });
44167
44333
  let resolveClosed;
44168
- const closed = new Promise((resolve) => {
44169
- resolveClosed = resolve;
44334
+ const closed = new Promise((resolve2) => {
44335
+ resolveClosed = resolve2;
44170
44336
  });
44171
44337
  const settle2 = (outcome) => {
44172
44338
  if (state === "closed") return;
@@ -44722,7 +44888,7 @@ var SSEClientTransport = class {
44722
44888
  const eventSourceFetch = this._eventSourceInit?.fetch;
44723
44889
  const fetchImpl = this._redirects(eventSourceFetch ? this._dpop?.(eventSourceFetch) ?? eventSourceFetch : this._fetch ?? fetch);
44724
44890
  let redirect;
44725
- return new Promise((resolve, reject) => {
44891
+ return new Promise((resolve2, reject) => {
44726
44892
  this._eventSource = new EventSource(this._url.href, {
44727
44893
  ...this._eventSourceInit,
44728
44894
  fetch: async (url2, init) => {
@@ -44756,7 +44922,7 @@ var SSEClientTransport = class {
44756
44922
  response,
44757
44923
  serverUrl: this._url,
44758
44924
  fetchFn: this._fetchWithInit
44759
- }).then(() => this._startOrAuth().then(resolve, reject), (error$2) => {
44925
+ }).then(() => this._startOrAuth().then(resolve2, reject), (error$2) => {
44760
44926
  this._connectAuthRetried = false;
44761
44927
  markAuthSeamEscape(error$2);
44762
44928
  this.onerror?.(error$2);
@@ -44794,7 +44960,7 @@ var SSEClientTransport = class {
44794
44960
  this.close();
44795
44961
  return;
44796
44962
  }
44797
- resolve();
44963
+ resolve2();
44798
44964
  });
44799
44965
  this._eventSource.onmessage = (event) => {
44800
44966
  const messageEvent = event;
@@ -45539,7 +45705,7 @@ var StdioClientTransport = class {
45539
45705
  */
45540
45706
  async start() {
45541
45707
  if (this._process) throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
45542
- return new Promise((resolve, reject) => {
45708
+ return new Promise((resolve2, reject) => {
45543
45709
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
45544
45710
  env: {
45545
45711
  ...getDefaultEnvironment(),
@@ -45559,7 +45725,7 @@ var StdioClientTransport = class {
45559
45725
  this.onerror?.(error62);
45560
45726
  });
45561
45727
  this._process.on("spawn", () => {
45562
- resolve();
45728
+ resolve2();
45563
45729
  });
45564
45730
  this._process.on("close", (_code) => {
45565
45731
  this._process = void 0;
@@ -45624,7 +45790,7 @@ var StdioClientTransport = class {
45624
45790
  const proc = this._process;
45625
45791
  this._process = void 0;
45626
45792
  if (proc && proc.exitCode === null && proc.signalCode === null) {
45627
- const exited = new Promise((resolve) => proc.once("exit", () => resolve()));
45793
+ const exited = new Promise((resolve2) => proc.once("exit", () => resolve2()));
45628
45794
  try {
45629
45795
  proc.stdin?.end();
45630
45796
  } catch {
@@ -45633,7 +45799,7 @@ var StdioClientTransport = class {
45633
45799
  proc.kill("SIGTERM");
45634
45800
  } catch {
45635
45801
  }
45636
- await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1e3).unref())]);
45802
+ await Promise.race([exited, new Promise((resolve2) => setTimeout(resolve2, 1e3).unref())]);
45637
45803
  if (proc.exitCode === null && proc.signalCode === null) try {
45638
45804
  proc.kill("SIGKILL");
45639
45805
  } catch {
@@ -45658,22 +45824,22 @@ var StdioClientTransport = class {
45658
45824
  if (this._process) {
45659
45825
  const processToClose = this._process;
45660
45826
  this._process = void 0;
45661
- const closePromise = new Promise((resolve) => {
45827
+ const closePromise = new Promise((resolve2) => {
45662
45828
  processToClose.once("close", () => {
45663
- resolve();
45829
+ resolve2();
45664
45830
  });
45665
45831
  });
45666
45832
  try {
45667
45833
  processToClose.stdin?.end();
45668
45834
  } catch {
45669
45835
  }
45670
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
45836
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
45671
45837
  if (processToClose.exitCode === null) {
45672
45838
  try {
45673
45839
  processToClose.kill("SIGTERM");
45674
45840
  } catch {
45675
45841
  }
45676
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
45842
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
45677
45843
  }
45678
45844
  if (processToClose.exitCode === null) try {
45679
45845
  processToClose.kill("SIGKILL");
@@ -45683,11 +45849,11 @@ var StdioClientTransport = class {
45683
45849
  this._readBuffer.clear();
45684
45850
  }
45685
45851
  send(message) {
45686
- return new Promise((resolve) => {
45852
+ return new Promise((resolve2) => {
45687
45853
  if (!this._process?.stdin) throw new SdkError(SdkErrorCode.NotConnected, "Not connected");
45688
45854
  const json2 = serializeMessage(message);
45689
- if (this._process.stdin.write(json2)) resolve();
45690
- else this._process.stdin.once("drain", resolve);
45855
+ if (this._process.stdin.write(json2)) resolve2();
45856
+ else this._process.stdin.once("drain", resolve2);
45691
45857
  });
45692
45858
  }
45693
45859
  };
@@ -45702,12 +45868,12 @@ function extractTemplateVarNames(value) {
45702
45868
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
45703
45869
  var FAILED_TRANSPORT_CLEANUP_TIMEOUT_MS = 1e3;
45704
45870
  function withTimeout(promise2, timeoutMs, message) {
45705
- return new Promise((resolve, reject) => {
45871
+ return new Promise((resolve2, reject) => {
45706
45872
  const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
45707
45873
  promise2.then(
45708
45874
  (value) => {
45709
45875
  clearTimeout(timer);
45710
- resolve(value);
45876
+ resolve2(value);
45711
45877
  },
45712
45878
  (err) => {
45713
45879
  clearTimeout(timer);
@@ -45930,18 +46096,18 @@ var McpToolRegistry = class {
45930
46096
  };
45931
46097
 
45932
46098
  // src/lib/oauth/store.ts
45933
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
45934
- import { join as join2 } from "node:path";
46099
+ import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
46100
+ import { join as join3 } from "node:path";
45935
46101
  function oauthDir(homeDir) {
45936
- return join2(homeDir, ".trellis", "mcp", "oauth");
46102
+ return join3(homeDir, ".trellis", "mcp", "oauth");
45937
46103
  }
45938
46104
  function tokenPath(homeDir, serverName) {
45939
46105
  assertSafeServerName(serverName);
45940
- return join2(oauthDir(homeDir), `${serverName}.json`);
46106
+ return join3(oauthDir(homeDir), `${serverName}.json`);
45941
46107
  }
45942
46108
  function lockPath(homeDir, serverName) {
45943
46109
  assertSafeServerName(serverName);
45944
- return join2(oauthDir(homeDir), `${serverName}.lock`);
46110
+ return join3(oauthDir(homeDir), `${serverName}.lock`);
45945
46111
  }
45946
46112
  function assertSafeServerName(serverName) {
45947
46113
  if (serverName === "" || serverName === "." || serverName === ".." || /[/\\]/.test(serverName) || serverName.startsWith(".")) {
@@ -45950,9 +46116,9 @@ function assertSafeServerName(serverName) {
45950
46116
  }
45951
46117
  function readToken(homeDir, serverName) {
45952
46118
  const path = tokenPath(homeDir, serverName);
45953
- if (!existsSync3(path)) return void 0;
46119
+ if (!existsSync4(path)) return void 0;
45954
46120
  try {
45955
- return JSON.parse(readFileSync3(path, "utf-8"));
46121
+ return JSON.parse(readFileSync4(path, "utf-8"));
45956
46122
  } catch {
45957
46123
  return void 0;
45958
46124
  }
@@ -45974,8 +46140,8 @@ function isExpired(token, skewMs = 6e4, now = Date.now()) {
45974
46140
  }
45975
46141
 
45976
46142
  // src/lib/oauth/lock.ts
45977
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync, closeSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
45978
- import { dirname as dirname2 } from "node:path";
46143
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, openSync, closeSync, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
46144
+ import { dirname as dirname3 } from "node:path";
45979
46145
  var STALE_AFTER_MS = 6e4;
45980
46146
  var POLL_INTERVAL_MS = 50;
45981
46147
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -45990,7 +46156,7 @@ function processAlive(pid) {
45990
46156
  function isStale(path, now) {
45991
46157
  let contents;
45992
46158
  try {
45993
- contents = JSON.parse(readFileSync4(path, "utf-8"));
46159
+ contents = JSON.parse(readFileSync5(path, "utf-8"));
45994
46160
  } catch {
45995
46161
  return true;
45996
46162
  }
@@ -46003,7 +46169,7 @@ async function withServerLock(homeDir, serverName, fn, opts = {}) {
46003
46169
  const now = opts.now ?? Date.now;
46004
46170
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
46005
46171
  const deadline = now() + timeoutMs;
46006
- mkdirSync4(dirname2(path), { recursive: true, mode: 448 });
46172
+ mkdirSync4(dirname3(path), { recursive: true, mode: 448 });
46007
46173
  for (; ; ) {
46008
46174
  try {
46009
46175
  const fd = openSync(path, "wx", 384);
@@ -46015,14 +46181,14 @@ async function withServerLock(homeDir, serverName, fn, opts = {}) {
46015
46181
  break;
46016
46182
  } catch (err) {
46017
46183
  if (err.code !== "EEXIST") throw err;
46018
- if (existsSync4(path) && isStale(path, now())) {
46184
+ if (existsSync5(path) && isStale(path, now())) {
46019
46185
  rmSync2(path, { force: true });
46020
46186
  continue;
46021
46187
  }
46022
46188
  if (now() >= deadline) {
46023
46189
  throw new Error(`timed out waiting for the OAuth refresh lock on "${serverName}" (${path})`);
46024
46190
  }
46025
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
46191
+ await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL_MS));
46026
46192
  }
46027
46193
  }
46028
46194
  try {