agent-trellis 0.14.1 → 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 (55) 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/zcode.d.ts +12 -1
  7. package/dist/adapters/zcode.js +7 -6
  8. package/dist/cli.js +38 -5
  9. package/dist/commands/doctor.d.ts +9 -1
  10. package/dist/commands/doctor.js +43 -6
  11. package/dist/commands/init.d.ts +6 -0
  12. package/dist/commands/init.js +24 -0
  13. package/dist/commands/mcp.d.ts +44 -1
  14. package/dist/commands/mcp.js +145 -33
  15. package/dist/commands/migrate.d.ts +12 -0
  16. package/dist/commands/migrate.js +8 -4
  17. package/dist/commands/migrateWorkspace.d.ts +40 -0
  18. package/dist/commands/migrateWorkspace.js +197 -0
  19. package/dist/commands/rollback.js +65 -35
  20. package/dist/commands/skill.d.ts +14 -0
  21. package/dist/commands/skill.js +71 -0
  22. package/dist/commands/sync.d.ts +16 -1
  23. package/dist/commands/sync.js +48 -15
  24. package/dist/core/adapter.d.ts +14 -0
  25. package/dist/core/canonical.d.ts +20 -34
  26. package/dist/core/canonical.js +18 -92
  27. package/dist/core/mcpServerDef.d.ts +39 -0
  28. package/dist/core/mcpServerDef.js +93 -0
  29. package/dist/core/types.d.ts +39 -4
  30. package/dist/lib/mcpMigrateRead.d.ts +25 -0
  31. package/dist/lib/mcpMigrateRead.js +9 -2
  32. package/dist/lib/skillProvider.js +18 -5
  33. package/dist/lib/workspace.d.ts +39 -0
  34. package/dist/lib/workspace.js +158 -0
  35. package/dist/lib/workspaceConfig.d.ts +45 -0
  36. package/dist/lib/workspaceConfig.js +98 -0
  37. package/dist/lib/workspaceDoctor.d.ts +44 -0
  38. package/dist/lib/workspaceDoctor.js +94 -0
  39. package/dist/lib/workspaceFiles.d.ts +38 -0
  40. package/dist/lib/workspaceFiles.js +264 -0
  41. package/dist/lib/workspaceMcp.d.ts +57 -0
  42. package/dist/lib/workspaceMcp.js +277 -0
  43. package/dist/lib/workspaceMerge.d.ts +37 -0
  44. package/dist/lib/workspaceMerge.js +98 -0
  45. package/dist/lib/workspaceMigrate.d.ts +118 -0
  46. package/dist/lib/workspaceMigrate.js +423 -0
  47. package/dist/pi-bridge/bundle.js +258 -96
  48. package/docs/architecture.md +111 -49
  49. package/docs/getting-started.md +86 -14
  50. package/docs/mcp-oauth-matrix.md +12 -1
  51. package/docs/overview.zh-CN.md +17 -0
  52. package/docs/research.md +28 -6
  53. package/docs/roadmap.md +17 -5
  54. package/package.json +1 -1
  55. 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,69 +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?, client_name? }`);
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
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, client_name`);
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
7966
  }
7967
7967
  if (client_name !== void 0 && typeof client_name !== "string") {
7968
- throw new Error(`mcp/servers.yaml: "${serverName}" auth.client_name must be a string`);
7968
+ throw new Error(`${source}: "${serverName}" auth.client_name must be a string`);
7969
7969
  }
7970
7970
  const out = { kind: "oauth" };
7971
- 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`);
7972
7972
  if (owner !== void 0) out.owner = owner;
7973
7973
  if (typeof client_id === "string") out.clientId = client_id;
7974
7974
  if (typeof client_secret_env === "string") out.clientSecretEnv = client_secret_env;
7975
7975
  if (typeof client_name === "string") out.clientName = client_name;
7976
7976
  return out;
7977
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
7978
8133
  function trellisRoot(homeDir) {
7979
- return join(homeDir, ".trellis");
8134
+ return join2(homeDir, ".trellis");
7980
8135
  }
7981
8136
  function listMarkdownFiles(dir) {
7982
8137
  let entries;
7983
8138
  try {
7984
- entries = readdirSync(dir);
8139
+ entries = readdirSync2(dir);
7985
8140
  } catch {
7986
8141
  return [];
7987
8142
  }
7988
- 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) }));
7989
8144
  }
7990
8145
  function listSkillDirs(dir) {
7991
8146
  let entries;
7992
8147
  try {
7993
- entries = readdirSync(dir);
8148
+ entries = readdirSync2(dir);
7994
8149
  } catch {
7995
8150
  return [];
7996
8151
  }
7997
8152
  const skills = [];
7998
8153
  for (const name of entries) {
7999
- const skillDir = join(dir, name);
8154
+ const skillDir = join2(dir, name);
8000
8155
  try {
8001
- if (statSync(skillDir).isDirectory()) {
8156
+ if (statSync2(skillDir).isDirectory()) {
8002
8157
  skills.push({ name, dir: skillDir });
8003
8158
  }
8004
8159
  } catch {
@@ -8007,17 +8162,17 @@ function listSkillDirs(dir) {
8007
8162
  return skills;
8008
8163
  }
8009
8164
  function loadScopeYaml(path) {
8010
- if (!existsSync2(path)) {
8165
+ if (!existsSync3(path)) {
8011
8166
  return {};
8012
8167
  }
8013
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8"));
8168
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8"));
8014
8169
  return parsed ?? {};
8015
8170
  }
8016
8171
  function loadServersYaml(path) {
8017
- if (!existsSync2(path)) {
8172
+ if (!existsSync3(path)) {
8018
8173
  return { servers: {}, knownHostInjected: [] };
8019
8174
  }
8020
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8175
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8021
8176
  const servers = Object.fromEntries(Object.entries(parsed.servers ?? {}).map(([name, def]) => [name, fromServerDefYaml(name, def)]));
8022
8177
  return {
8023
8178
  servers,
@@ -8058,10 +8213,10 @@ function fromGatewayYaml(gateway) {
8058
8213
  return out;
8059
8214
  }
8060
8215
  function loadSecretsPolicyYaml(path, homeDir) {
8061
- if (!existsSync2(path)) {
8216
+ if (!existsSync3(path)) {
8062
8217
  return { allowedVars: [], rejectPatterns: [] };
8063
8218
  }
8064
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8219
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8065
8220
  return {
8066
8221
  allowedVars: parsed.allowed_vars ?? [],
8067
8222
  rejectPatterns: (parsed.reject_patterns ?? []).map((pattern) => new RegExp(pattern)),
@@ -8069,10 +8224,10 @@ function loadSecretsPolicyYaml(path, homeDir) {
8069
8224
  };
8070
8225
  }
8071
8226
  function loadManagedYaml(path, diagnostics) {
8072
- if (!existsSync2(path)) {
8227
+ if (!existsSync3(path)) {
8073
8228
  return [];
8074
8229
  }
8075
- const parsed = (0, import_yaml.parse)(readFileSync2(path, "utf-8")) ?? {};
8230
+ const parsed = (0, import_yaml.parse)(readFileSync3(path, "utf-8")) ?? {};
8076
8231
  const raw = parsed.agents ?? [];
8077
8232
  const valid = [];
8078
8233
  for (const id of raw) {
@@ -8110,27 +8265,29 @@ function assertAuthMetadataClean(mcp, policy) {
8110
8265
  }
8111
8266
  }
8112
8267
  }
8113
- function loadCanonicalSource(homeDir = homedir()) {
8268
+ function loadCanonicalSource(arg = {}) {
8269
+ const options = typeof arg === "string" ? { homeDir: arg } : arg;
8270
+ const homeDir = options.homeDir ?? homedir();
8114
8271
  const root = trellisRoot(homeDir);
8115
- if (!existsSync2(root)) {
8272
+ if (!existsSync3(root)) {
8116
8273
  throw new Error(
8117
8274
  `No canonical source at ${root}. Run trellis init before trellis sync.`
8118
8275
  );
8119
8276
  }
8120
8277
  const diagnostics = [];
8121
- const managedAgents = loadManagedYaml(join(root, "managed.yaml"), diagnostics);
8122
- const scopeYaml = loadScopeYaml(join(root, "scope.yaml"));
8123
- 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"));
8124
8281
  const knownSkillNames = new Set(skillDirs.map((s) => s.name));
8125
8282
  const skills = skillDirs.map(({ name, dir }) => ({ name, dir, scope: scopeFor(scopeYaml.skills, name) }));
8126
- const agentFiles = listMarkdownFiles(join(root, "agents"));
8283
+ const agentFiles = listMarkdownFiles(join2(root, "agents"));
8127
8284
  const knownAgentProfileNames = new Set(agentFiles.map((a) => a.name));
8128
8285
  const agents = agentFiles.map(({ name, file: file2 }) => ({
8129
8286
  name,
8130
8287
  file: file2,
8131
8288
  scope: scopeFor(scopeYaml.agents, name)
8132
8289
  }));
8133
- const memoryFiles = listMarkdownFiles(join(root, "memories"));
8290
+ const memoryFiles = listMarkdownFiles(join2(root, "memories"));
8134
8291
  const knownMemoryNames = new Set(memoryFiles.map((m) => m.name));
8135
8292
  const memories = memoryFiles.map(({ name, file: file2 }) => ({
8136
8293
  name,
@@ -8151,11 +8308,11 @@ function loadCanonicalSource(homeDir = homedir()) {
8151
8308
  }
8152
8309
  }
8153
8310
  }
8154
- const mcp = loadServersYaml(join(root, "mcp", "servers.yaml"));
8155
- 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);
8156
8313
  assertAuthMetadataClean(mcp, secretsPolicy);
8157
- return {
8158
- instructionsFile: join(root, "agents.md"),
8314
+ const source = {
8315
+ instructionsFile: join2(root, "agents.md"),
8159
8316
  managedAgents,
8160
8317
  skills,
8161
8318
  agents,
@@ -8164,6 +8321,11 @@ function loadCanonicalSource(homeDir = homedir()) {
8164
8321
  secretsPolicy,
8165
8322
  diagnostics
8166
8323
  };
8324
+ if (options.workspaceDir) {
8325
+ const layer = loadWorkspaceLayer(options.workspaceDir, secretsPolicy);
8326
+ return mergeWorkspaceLayer(source, layer);
8327
+ }
8328
+ return source;
8167
8329
  }
8168
8330
 
8169
8331
  // src/core/adapter.ts
@@ -13468,7 +13630,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
13468
13630
  var NONE = 0;
13469
13631
  var ASSUMED = 1;
13470
13632
  var PROVEN = 2;
13471
- function isRecursive(inst, stack, resolve) {
13633
+ function isRecursive(inst, stack, resolve2) {
13472
13634
  const cached2 = recursive.get(inst);
13473
13635
  if (cached2 !== void 0)
13474
13636
  return cached2 ? PROVEN : NONE;
@@ -13478,7 +13640,7 @@ function isRecursive(inst, stack, resolve) {
13478
13640
  let result = NONE;
13479
13641
  const check2 = (child) => {
13480
13642
  if (result !== PROVEN && child?._zod) {
13481
- const answer = isRecursive(child, stack, resolve);
13643
+ const answer = isRecursive(child, stack, resolve2);
13482
13644
  if (answer > result)
13483
13645
  result = answer;
13484
13646
  }
@@ -13489,7 +13651,7 @@ function isRecursive(inst, stack, resolve) {
13489
13651
  const desc = Object.getOwnPropertyDescriptor(sh, key);
13490
13652
  if (spread && !desc.enumerable)
13491
13653
  continue;
13492
- 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;
13493
13655
  if (child > answer)
13494
13656
  answer = child;
13495
13657
  }
@@ -13553,7 +13715,7 @@ function isRecursive(inst, stack, resolve) {
13553
13715
  break;
13554
13716
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
13555
13717
  case "lazy": {
13556
- const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
13718
+ const inner = def._cachedInner ?? (resolve2 ? inst._zod.innerType : void 0);
13557
13719
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
13558
13720
  break;
13559
13721
  }
@@ -32475,14 +32637,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
32475
32637
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
32476
32638
  }
32477
32639
  function sleep(ms, signal) {
32478
- return new Promise((resolve, reject) => {
32640
+ return new Promise((resolve2, reject) => {
32479
32641
  if (signal?.aborted) {
32480
32642
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
32481
32643
  return;
32482
32644
  }
32483
32645
  const timer = setTimeout(() => {
32484
32646
  signal?.removeEventListener("abort", onAbort);
32485
- resolve();
32647
+ resolve2();
32486
32648
  }, ms);
32487
32649
  const onAbort = () => {
32488
32650
  clearTimeout(timer);
@@ -33326,7 +33488,7 @@ var Protocol = class {
33326
33488
  const flowStartedAt = Date.now();
33327
33489
  let onAbort;
33328
33490
  let cleanupMessageId;
33329
- return new Promise((resolve, reject) => {
33491
+ return new Promise((resolve2, reject) => {
33330
33492
  const earlyReject = (error62) => {
33331
33493
  reject(error62);
33332
33494
  };
@@ -33395,7 +33557,7 @@ var Protocol = class {
33395
33557
  }
33396
33558
  if (decoded.kind === "invalid") return reject(decoded.error);
33397
33559
  if (decoded.kind === "input_required") {
33398
- if (options?.allowInputRequired === true) return resolve(manualInputRequiredValue(decoded));
33560
+ if (options?.allowInputRequired === true) return resolve2(manualInputRequiredValue(decoded));
33399
33561
  const flow = {
33400
33562
  codec: codec2,
33401
33563
  request,
@@ -33407,11 +33569,11 @@ var Protocol = class {
33407
33569
  params
33408
33570
  }, resultSchema, legOptions)
33409
33571
  };
33410
- return resolve(this._resolveNonCompleteResult(decoded, flow));
33572
+ return resolve2(this._resolveNonCompleteResult(decoded, flow));
33411
33573
  }
33412
33574
  const result = decoded.result;
33413
33575
  validateStandardSchema(resultSchema, result).then((parseResult) => {
33414
- if (parseResult.success) resolve(parseResult.data);
33576
+ if (parseResult.success) resolve2(parseResult.data);
33415
33577
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
33416
33578
  }, reject);
33417
33579
  });
@@ -36428,7 +36590,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
36428
36590
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
36429
36591
  const schOrFunc = root.refs[ref];
36430
36592
  if (schOrFunc) return schOrFunc;
36431
- let _sch = resolve.call(this, root, ref);
36593
+ let _sch = resolve2.call(this, root, ref);
36432
36594
  if (_sch === void 0) {
36433
36595
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
36434
36596
  const { schemaId } = this.opts;
@@ -36454,7 +36616,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
36454
36616
  function sameSchemaEnv(s1, s2) {
36455
36617
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
36456
36618
  }
36457
- function resolve(root, ref) {
36619
+ function resolve2(root, ref) {
36458
36620
  let sch;
36459
36621
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
36460
36622
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -36904,7 +37066,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36904
37066
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
36905
37067
  return uri;
36906
37068
  }
36907
- function resolve(baseURI, relativeURI, options) {
37069
+ function resolve2(baseURI, relativeURI, options) {
36908
37070
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
36909
37071
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
36910
37072
  schemelessOptions.skipEscape = true;
@@ -37078,7 +37240,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
37078
37240
  const fastUri = {
37079
37241
  SCHEMES,
37080
37242
  normalize,
37081
- resolve,
37243
+ resolve: resolve2,
37082
37244
  resolveComponent,
37083
37245
  equal,
37084
37246
  serialize,
@@ -42943,14 +43105,14 @@ var ProbeWindow = class ProbeWindow2 {
42943
43105
  */
42944
43106
  async exchange(buildRequest, timeoutMs) {
42945
43107
  const id = `server-discover-probe-${++this._probeCounter}`;
42946
- return new Promise((resolve) => {
43108
+ return new Promise((resolve2) => {
42947
43109
  let settled = false;
42948
43110
  const settle2 = (reply) => {
42949
43111
  if (settled) return;
42950
43112
  settled = true;
42951
43113
  clearTimeout(timer);
42952
43114
  if (this._pending?.id === id) this._pending = void 0;
42953
- resolve(reply);
43115
+ resolve2(reply);
42954
43116
  };
42955
43117
  const timer = setTimeout(() => settle2({ kind: "timeout" }), timeoutMs);
42956
43118
  this._pending = {
@@ -44164,13 +44326,13 @@ var Client = class extends Protocol {
44164
44326
  let onCallerAbort;
44165
44327
  let resolveOpening;
44166
44328
  let rejectOpening;
44167
- const opening = new Promise((resolve, reject) => {
44168
- resolveOpening = resolve;
44329
+ const opening = new Promise((resolve2, reject) => {
44330
+ resolveOpening = resolve2;
44169
44331
  rejectOpening = reject;
44170
44332
  });
44171
44333
  let resolveClosed;
44172
- const closed = new Promise((resolve) => {
44173
- resolveClosed = resolve;
44334
+ const closed = new Promise((resolve2) => {
44335
+ resolveClosed = resolve2;
44174
44336
  });
44175
44337
  const settle2 = (outcome) => {
44176
44338
  if (state === "closed") return;
@@ -44726,7 +44888,7 @@ var SSEClientTransport = class {
44726
44888
  const eventSourceFetch = this._eventSourceInit?.fetch;
44727
44889
  const fetchImpl = this._redirects(eventSourceFetch ? this._dpop?.(eventSourceFetch) ?? eventSourceFetch : this._fetch ?? fetch);
44728
44890
  let redirect;
44729
- return new Promise((resolve, reject) => {
44891
+ return new Promise((resolve2, reject) => {
44730
44892
  this._eventSource = new EventSource(this._url.href, {
44731
44893
  ...this._eventSourceInit,
44732
44894
  fetch: async (url2, init) => {
@@ -44760,7 +44922,7 @@ var SSEClientTransport = class {
44760
44922
  response,
44761
44923
  serverUrl: this._url,
44762
44924
  fetchFn: this._fetchWithInit
44763
- }).then(() => this._startOrAuth().then(resolve, reject), (error$2) => {
44925
+ }).then(() => this._startOrAuth().then(resolve2, reject), (error$2) => {
44764
44926
  this._connectAuthRetried = false;
44765
44927
  markAuthSeamEscape(error$2);
44766
44928
  this.onerror?.(error$2);
@@ -44798,7 +44960,7 @@ var SSEClientTransport = class {
44798
44960
  this.close();
44799
44961
  return;
44800
44962
  }
44801
- resolve();
44963
+ resolve2();
44802
44964
  });
44803
44965
  this._eventSource.onmessage = (event) => {
44804
44966
  const messageEvent = event;
@@ -45543,7 +45705,7 @@ var StdioClientTransport = class {
45543
45705
  */
45544
45706
  async start() {
45545
45707
  if (this._process) throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
45546
- return new Promise((resolve, reject) => {
45708
+ return new Promise((resolve2, reject) => {
45547
45709
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
45548
45710
  env: {
45549
45711
  ...getDefaultEnvironment(),
@@ -45563,7 +45725,7 @@ var StdioClientTransport = class {
45563
45725
  this.onerror?.(error62);
45564
45726
  });
45565
45727
  this._process.on("spawn", () => {
45566
- resolve();
45728
+ resolve2();
45567
45729
  });
45568
45730
  this._process.on("close", (_code) => {
45569
45731
  this._process = void 0;
@@ -45628,7 +45790,7 @@ var StdioClientTransport = class {
45628
45790
  const proc = this._process;
45629
45791
  this._process = void 0;
45630
45792
  if (proc && proc.exitCode === null && proc.signalCode === null) {
45631
- const exited = new Promise((resolve) => proc.once("exit", () => resolve()));
45793
+ const exited = new Promise((resolve2) => proc.once("exit", () => resolve2()));
45632
45794
  try {
45633
45795
  proc.stdin?.end();
45634
45796
  } catch {
@@ -45637,7 +45799,7 @@ var StdioClientTransport = class {
45637
45799
  proc.kill("SIGTERM");
45638
45800
  } catch {
45639
45801
  }
45640
- await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1e3).unref())]);
45802
+ await Promise.race([exited, new Promise((resolve2) => setTimeout(resolve2, 1e3).unref())]);
45641
45803
  if (proc.exitCode === null && proc.signalCode === null) try {
45642
45804
  proc.kill("SIGKILL");
45643
45805
  } catch {
@@ -45662,22 +45824,22 @@ var StdioClientTransport = class {
45662
45824
  if (this._process) {
45663
45825
  const processToClose = this._process;
45664
45826
  this._process = void 0;
45665
- const closePromise = new Promise((resolve) => {
45827
+ const closePromise = new Promise((resolve2) => {
45666
45828
  processToClose.once("close", () => {
45667
- resolve();
45829
+ resolve2();
45668
45830
  });
45669
45831
  });
45670
45832
  try {
45671
45833
  processToClose.stdin?.end();
45672
45834
  } catch {
45673
45835
  }
45674
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
45836
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
45675
45837
  if (processToClose.exitCode === null) {
45676
45838
  try {
45677
45839
  processToClose.kill("SIGTERM");
45678
45840
  } catch {
45679
45841
  }
45680
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
45842
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
45681
45843
  }
45682
45844
  if (processToClose.exitCode === null) try {
45683
45845
  processToClose.kill("SIGKILL");
@@ -45687,11 +45849,11 @@ var StdioClientTransport = class {
45687
45849
  this._readBuffer.clear();
45688
45850
  }
45689
45851
  send(message) {
45690
- return new Promise((resolve) => {
45852
+ return new Promise((resolve2) => {
45691
45853
  if (!this._process?.stdin) throw new SdkError(SdkErrorCode.NotConnected, "Not connected");
45692
45854
  const json2 = serializeMessage(message);
45693
- if (this._process.stdin.write(json2)) resolve();
45694
- else this._process.stdin.once("drain", resolve);
45855
+ if (this._process.stdin.write(json2)) resolve2();
45856
+ else this._process.stdin.once("drain", resolve2);
45695
45857
  });
45696
45858
  }
45697
45859
  };
@@ -45706,12 +45868,12 @@ function extractTemplateVarNames(value) {
45706
45868
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
45707
45869
  var FAILED_TRANSPORT_CLEANUP_TIMEOUT_MS = 1e3;
45708
45870
  function withTimeout(promise2, timeoutMs, message) {
45709
- return new Promise((resolve, reject) => {
45871
+ return new Promise((resolve2, reject) => {
45710
45872
  const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
45711
45873
  promise2.then(
45712
45874
  (value) => {
45713
45875
  clearTimeout(timer);
45714
- resolve(value);
45876
+ resolve2(value);
45715
45877
  },
45716
45878
  (err) => {
45717
45879
  clearTimeout(timer);
@@ -45934,18 +46096,18 @@ var McpToolRegistry = class {
45934
46096
  };
45935
46097
 
45936
46098
  // src/lib/oauth/store.ts
45937
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
45938
- 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";
45939
46101
  function oauthDir(homeDir) {
45940
- return join2(homeDir, ".trellis", "mcp", "oauth");
46102
+ return join3(homeDir, ".trellis", "mcp", "oauth");
45941
46103
  }
45942
46104
  function tokenPath(homeDir, serverName) {
45943
46105
  assertSafeServerName(serverName);
45944
- return join2(oauthDir(homeDir), `${serverName}.json`);
46106
+ return join3(oauthDir(homeDir), `${serverName}.json`);
45945
46107
  }
45946
46108
  function lockPath(homeDir, serverName) {
45947
46109
  assertSafeServerName(serverName);
45948
- return join2(oauthDir(homeDir), `${serverName}.lock`);
46110
+ return join3(oauthDir(homeDir), `${serverName}.lock`);
45949
46111
  }
45950
46112
  function assertSafeServerName(serverName) {
45951
46113
  if (serverName === "" || serverName === "." || serverName === ".." || /[/\\]/.test(serverName) || serverName.startsWith(".")) {
@@ -45954,9 +46116,9 @@ function assertSafeServerName(serverName) {
45954
46116
  }
45955
46117
  function readToken(homeDir, serverName) {
45956
46118
  const path = tokenPath(homeDir, serverName);
45957
- if (!existsSync3(path)) return void 0;
46119
+ if (!existsSync4(path)) return void 0;
45958
46120
  try {
45959
- return JSON.parse(readFileSync3(path, "utf-8"));
46121
+ return JSON.parse(readFileSync4(path, "utf-8"));
45960
46122
  } catch {
45961
46123
  return void 0;
45962
46124
  }
@@ -45978,8 +46140,8 @@ function isExpired(token, skewMs = 6e4, now = Date.now()) {
45978
46140
  }
45979
46141
 
45980
46142
  // src/lib/oauth/lock.ts
45981
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync, closeSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
45982
- 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";
45983
46145
  var STALE_AFTER_MS = 6e4;
45984
46146
  var POLL_INTERVAL_MS = 50;
45985
46147
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -45994,7 +46156,7 @@ function processAlive(pid) {
45994
46156
  function isStale(path, now) {
45995
46157
  let contents;
45996
46158
  try {
45997
- contents = JSON.parse(readFileSync4(path, "utf-8"));
46159
+ contents = JSON.parse(readFileSync5(path, "utf-8"));
45998
46160
  } catch {
45999
46161
  return true;
46000
46162
  }
@@ -46007,7 +46169,7 @@ async function withServerLock(homeDir, serverName, fn, opts = {}) {
46007
46169
  const now = opts.now ?? Date.now;
46008
46170
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
46009
46171
  const deadline = now() + timeoutMs;
46010
- mkdirSync4(dirname2(path), { recursive: true, mode: 448 });
46172
+ mkdirSync4(dirname3(path), { recursive: true, mode: 448 });
46011
46173
  for (; ; ) {
46012
46174
  try {
46013
46175
  const fd = openSync(path, "wx", 384);
@@ -46019,14 +46181,14 @@ async function withServerLock(homeDir, serverName, fn, opts = {}) {
46019
46181
  break;
46020
46182
  } catch (err) {
46021
46183
  if (err.code !== "EEXIST") throw err;
46022
- if (existsSync4(path) && isStale(path, now())) {
46184
+ if (existsSync5(path) && isStale(path, now())) {
46023
46185
  rmSync2(path, { force: true });
46024
46186
  continue;
46025
46187
  }
46026
46188
  if (now() >= deadline) {
46027
46189
  throw new Error(`timed out waiting for the OAuth refresh lock on "${serverName}" (${path})`);
46028
46190
  }
46029
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
46191
+ await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL_MS));
46030
46192
  }
46031
46193
  }
46032
46194
  try {