norn-cli 3.0.0 → 3.1.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/dist/cli.js CHANGED
@@ -16439,7 +16439,7 @@ var require_form_data = __commonJS({
16439
16439
  var parseUrl = require("url").parse;
16440
16440
  var fs31 = require("fs");
16441
16441
  var Stream6 = require("stream").Stream;
16442
- var crypto9 = require("crypto");
16442
+ var crypto10 = require("crypto");
16443
16443
  var mime = require_mime_types();
16444
16444
  var asynckit = require_asynckit();
16445
16445
  var setToStringTag = require_es_set_tostringtag();
@@ -16645,7 +16645,7 @@ var require_form_data = __commonJS({
16645
16645
  return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]);
16646
16646
  };
16647
16647
  FormData5.prototype._generateBoundary = function() {
16648
- this._boundary = "--------------------------" + crypto9.randomBytes(12).toString("hex");
16648
+ this._boundary = "--------------------------" + crypto10.randomBytes(12).toString("hex");
16649
16649
  };
16650
16650
  FormData5.prototype.getLengthSync = function() {
16651
16651
  var knownLength = this._overheadLength + this._valueLength;
@@ -19957,7 +19957,7 @@ var require_cert_signatures = __commonJS({
19957
19957
  var require_sasl = __commonJS({
19958
19958
  "node_modules/pg/lib/crypto/sasl.js"(exports2, module2) {
19959
19959
  "use strict";
19960
- var crypto9 = require_utils3();
19960
+ var crypto10 = require_utils3();
19961
19961
  var { signatureAlgorithmHashFromCertificate } = require_cert_signatures();
19962
19962
  function startSession(mechanisms, stream4) {
19963
19963
  const candidates = ["SCRAM-SHA-256"];
@@ -19969,7 +19969,7 @@ var require_sasl = __commonJS({
19969
19969
  if (mechanism === "SCRAM-SHA-256-PLUS" && typeof stream4.getPeerCertificate !== "function") {
19970
19970
  throw new Error("SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate");
19971
19971
  }
19972
- const clientNonce = crypto9.randomBytes(18).toString("base64");
19972
+ const clientNonce = crypto10.randomBytes(18).toString("base64");
19973
19973
  const gs2Header = mechanism === "SCRAM-SHA-256-PLUS" ? "p=tls-server-end-point" : stream4 ? "y" : "n";
19974
19974
  return {
19975
19975
  mechanism,
@@ -20004,20 +20004,20 @@ var require_sasl = __commonJS({
20004
20004
  const peerCert = stream4.getPeerCertificate().raw;
20005
20005
  let hashName = signatureAlgorithmHashFromCertificate(peerCert);
20006
20006
  if (hashName === "MD5" || hashName === "SHA-1") hashName = "SHA-256";
20007
- const certHash = await crypto9.hashByName(hashName, peerCert);
20007
+ const certHash = await crypto10.hashByName(hashName, peerCert);
20008
20008
  const bindingData = Buffer.concat([Buffer.from("p=tls-server-end-point,,"), Buffer.from(certHash)]);
20009
20009
  channelBinding = bindingData.toString("base64");
20010
20010
  }
20011
20011
  const clientFinalMessageWithoutProof = "c=" + channelBinding + ",r=" + sv.nonce;
20012
20012
  const authMessage = clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
20013
20013
  const saltBytes = Buffer.from(sv.salt, "base64");
20014
- const saltedPassword = await crypto9.deriveKey(password, saltBytes, sv.iteration);
20015
- const clientKey = await crypto9.hmacSha256(saltedPassword, "Client Key");
20016
- const storedKey = await crypto9.sha256(clientKey);
20017
- const clientSignature = await crypto9.hmacSha256(storedKey, authMessage);
20014
+ const saltedPassword = await crypto10.deriveKey(password, saltBytes, sv.iteration);
20015
+ const clientKey = await crypto10.hmacSha256(saltedPassword, "Client Key");
20016
+ const storedKey = await crypto10.sha256(clientKey);
20017
+ const clientSignature = await crypto10.hmacSha256(storedKey, authMessage);
20018
20018
  const clientProof = xorBuffers(Buffer.from(clientKey), Buffer.from(clientSignature)).toString("base64");
20019
- const serverKey = await crypto9.hmacSha256(saltedPassword, "Server Key");
20020
- const serverSignatureBytes = await crypto9.hmacSha256(serverKey, authMessage);
20019
+ const serverKey = await crypto10.hmacSha256(saltedPassword, "Server Key");
20020
+ const serverSignatureBytes = await crypto10.hmacSha256(serverKey, authMessage);
20021
20021
  session.message = "SASLResponse";
20022
20022
  session.serverSignature = Buffer.from(serverSignatureBytes).toString("base64");
20023
20023
  session.response = clientFinalMessageWithoutProof + ",p=" + clientProof;
@@ -22185,7 +22185,7 @@ var require_client = __commonJS({
22185
22185
  var Query2 = require_query();
22186
22186
  var defaults5 = require_defaults2();
22187
22187
  var Connection2 = require_connection();
22188
- var crypto9 = require_utils3();
22188
+ var crypto10 = require_utils3();
22189
22189
  var activeQueryDeprecationNotice = nodeUtils.deprecate(
22190
22190
  () => {
22191
22191
  },
@@ -22420,7 +22420,7 @@ var require_client = __commonJS({
22420
22420
  _handleAuthMD5Password(msg) {
22421
22421
  this._getPassword(async () => {
22422
22422
  try {
22423
- const hashedPassword = await crypto9.postgresMd5PasswordHash(this.user, this.password, msg.salt);
22423
+ const hashedPassword = await crypto10.postgresMd5PasswordHash(this.user, this.password, msg.salt);
22424
22424
  this.connection.password(hashedPassword);
22425
22425
  } catch (e2) {
22426
22426
  this.emit("error", e2);
@@ -38205,14 +38205,14 @@ var require_buffer_equal_constant_time = __commonJS({
38205
38205
  var require_jwa = __commonJS({
38206
38206
  "node_modules/jwa/index.js"(exports2, module2) {
38207
38207
  var Buffer5 = require_safe_buffer().Buffer;
38208
- var crypto9 = require("crypto");
38208
+ var crypto10 = require("crypto");
38209
38209
  var formatEcdsa = require_ecdsa_sig_formatter();
38210
38210
  var util4 = require("util");
38211
38211
  var MSG_INVALID_ALGORITHM = '"%s" is not a valid algorithm.\n Supported algorithms are:\n "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512" and "none".';
38212
38212
  var MSG_INVALID_SECRET = "secret must be a string or buffer";
38213
38213
  var MSG_INVALID_VERIFIER_KEY = "key must be a string or a buffer";
38214
38214
  var MSG_INVALID_SIGNER_KEY = "key must be a string, a buffer or an object";
38215
- var supportsKeyObjects = typeof crypto9.createPublicKey === "function";
38215
+ var supportsKeyObjects = typeof crypto10.createPublicKey === "function";
38216
38216
  if (supportsKeyObjects) {
38217
38217
  MSG_INVALID_VERIFIER_KEY += " or a KeyObject";
38218
38218
  MSG_INVALID_SECRET += "or a KeyObject";
@@ -38302,17 +38302,17 @@ var require_jwa = __commonJS({
38302
38302
  return function sign(thing, secret) {
38303
38303
  checkIsSecretKey(secret);
38304
38304
  thing = normalizeInput(thing);
38305
- var hmac = crypto9.createHmac("sha" + bits, secret);
38305
+ var hmac = crypto10.createHmac("sha" + bits, secret);
38306
38306
  var sig = (hmac.update(thing), hmac.digest("base64"));
38307
38307
  return fromBase64(sig);
38308
38308
  };
38309
38309
  }
38310
38310
  var bufferEqual;
38311
- var timingSafeEqual = "timingSafeEqual" in crypto9 ? function timingSafeEqual2(a, b) {
38311
+ var timingSafeEqual = "timingSafeEqual" in crypto10 ? function timingSafeEqual2(a, b) {
38312
38312
  if (a.byteLength !== b.byteLength) {
38313
38313
  return false;
38314
38314
  }
38315
- return crypto9.timingSafeEqual(a, b);
38315
+ return crypto10.timingSafeEqual(a, b);
38316
38316
  } : function timingSafeEqual2(a, b) {
38317
38317
  if (!bufferEqual) {
38318
38318
  bufferEqual = require_buffer_equal_constant_time();
@@ -38329,7 +38329,7 @@ var require_jwa = __commonJS({
38329
38329
  return function sign(thing, privateKey) {
38330
38330
  checkIsPrivateKey(privateKey);
38331
38331
  thing = normalizeInput(thing);
38332
- var signer = crypto9.createSign("RSA-SHA" + bits);
38332
+ var signer = crypto10.createSign("RSA-SHA" + bits);
38333
38333
  var sig = (signer.update(thing), signer.sign(privateKey, "base64"));
38334
38334
  return fromBase64(sig);
38335
38335
  };
@@ -38339,7 +38339,7 @@ var require_jwa = __commonJS({
38339
38339
  checkIsPublicKey(publicKey);
38340
38340
  thing = normalizeInput(thing);
38341
38341
  signature = toBase64(signature);
38342
- var verifier = crypto9.createVerify("RSA-SHA" + bits);
38342
+ var verifier = crypto10.createVerify("RSA-SHA" + bits);
38343
38343
  verifier.update(thing);
38344
38344
  return verifier.verify(publicKey, signature, "base64");
38345
38345
  };
@@ -38348,11 +38348,11 @@ var require_jwa = __commonJS({
38348
38348
  return function sign(thing, privateKey) {
38349
38349
  checkIsPrivateKey(privateKey);
38350
38350
  thing = normalizeInput(thing);
38351
- var signer = crypto9.createSign("RSA-SHA" + bits);
38351
+ var signer = crypto10.createSign("RSA-SHA" + bits);
38352
38352
  var sig = (signer.update(thing), signer.sign({
38353
38353
  key: privateKey,
38354
- padding: crypto9.constants.RSA_PKCS1_PSS_PADDING,
38355
- saltLength: crypto9.constants.RSA_PSS_SALTLEN_DIGEST
38354
+ padding: crypto10.constants.RSA_PKCS1_PSS_PADDING,
38355
+ saltLength: crypto10.constants.RSA_PSS_SALTLEN_DIGEST
38356
38356
  }, "base64"));
38357
38357
  return fromBase64(sig);
38358
38358
  };
@@ -38362,12 +38362,12 @@ var require_jwa = __commonJS({
38362
38362
  checkIsPublicKey(publicKey);
38363
38363
  thing = normalizeInput(thing);
38364
38364
  signature = toBase64(signature);
38365
- var verifier = crypto9.createVerify("RSA-SHA" + bits);
38365
+ var verifier = crypto10.createVerify("RSA-SHA" + bits);
38366
38366
  verifier.update(thing);
38367
38367
  return verifier.verify({
38368
38368
  key: publicKey,
38369
- padding: crypto9.constants.RSA_PKCS1_PSS_PADDING,
38370
- saltLength: crypto9.constants.RSA_PSS_SALTLEN_DIGEST
38369
+ padding: crypto10.constants.RSA_PKCS1_PSS_PADDING,
38370
+ saltLength: crypto10.constants.RSA_PSS_SALTLEN_DIGEST
38371
38371
  }, signature, "base64");
38372
38372
  };
38373
38373
  }
@@ -41653,7 +41653,7 @@ var require_msal_node = __commonJS({
41653
41653
  var http5 = require("http");
41654
41654
  var https5 = require("https");
41655
41655
  var uuid3 = (init_esm_node(), __toCommonJS(esm_node_exports));
41656
- var crypto9 = require("crypto");
41656
+ var crypto10 = require("crypto");
41657
41657
  var msalCommon = require_lib4();
41658
41658
  var jwt2 = require_jsonwebtoken();
41659
41659
  var fs31 = require("fs");
@@ -48697,7 +48697,7 @@ Headers: ${JSON.stringify(headers)}`
48697
48697
  * @param buffer
48698
48698
  */
48699
48699
  sha256(buffer) {
48700
- return crypto9.createHash(Hash.SHA256).update(buffer).digest();
48700
+ return crypto10.createHash(Hash.SHA256).update(buffer).digest();
48701
48701
  }
48702
48702
  };
48703
48703
  var PkceGenerator = class {
@@ -48720,7 +48720,7 @@ Headers: ${JSON.stringify(headers)}`
48720
48720
  const charArr = [];
48721
48721
  const maxNumber = 256 - 256 % CharSet.CV_CHARSET.length;
48722
48722
  while (charArr.length <= RANDOM_OCTET_SIZE) {
48723
- const byte = crypto9.randomBytes(1)[0];
48723
+ const byte = crypto10.randomBytes(1)[0];
48724
48724
  if (byte >= maxNumber) {
48725
48725
  continue;
48726
48726
  }
@@ -67089,17 +67089,17 @@ var require_md4 = __commonJS({
67089
67089
  return method;
67090
67090
  };
67091
67091
  var nodeWrap = function(method) {
67092
- var crypto9 = require("crypto");
67092
+ var crypto10 = require("crypto");
67093
67093
  var Buffer5 = require("buffer").Buffer;
67094
67094
  var nodeMethod = function(message) {
67095
67095
  if (typeof message === "string") {
67096
- return crypto9.createHash("md4").update(message, "utf8").digest("hex");
67096
+ return crypto10.createHash("md4").update(message, "utf8").digest("hex");
67097
67097
  } else if (ARRAY_BUFFER && message instanceof ArrayBuffer) {
67098
67098
  message = new Uint8Array(message);
67099
67099
  } else if (message.length === void 0) {
67100
67100
  return method(message);
67101
67101
  }
67102
- return crypto9.createHash("md4").update(new Buffer5(message)).digest("hex");
67102
+ return crypto10.createHash("md4").update(new Buffer5(message)).digest("hex");
67103
67103
  };
67104
67104
  return nodeMethod;
67105
67105
  };
@@ -67432,7 +67432,7 @@ var require_ntlm_payload = __commonJS({
67432
67432
  });
67433
67433
  exports2.default = void 0;
67434
67434
  var _writableTrackingBuffer = _interopRequireDefault(require_writable_tracking_buffer());
67435
- var crypto9 = _interopRequireWildcard(require("crypto"));
67435
+ var crypto10 = _interopRequireWildcard(require("crypto"));
67436
67436
  var _jsMd = _interopRequireDefault(require_md4());
67437
67437
  function _interopRequireWildcard(e2, t2) {
67438
67438
  if ("function" == typeof WeakMap) var r2 = /* @__PURE__ */ new WeakMap(), n = /* @__PURE__ */ new WeakMap();
@@ -67569,7 +67569,7 @@ var require_ntlm_payload = __commonJS({
67569
67569
  return Buffer.from(_jsMd.default.arrayBuffer(unicodeString));
67570
67570
  }
67571
67571
  hmacMD5(data, key) {
67572
- return crypto9.createHmac("MD5", key).update(data).digest();
67572
+ return crypto10.createHmac("MD5", key).update(data).digest();
67573
67573
  }
67574
67574
  };
67575
67575
  var _default2 = exports2.default = NTLMResponsePayload;
@@ -101313,13 +101313,13 @@ var uuid42;
101313
101313
  var init_uuid = __esm({
101314
101314
  "node_modules/@anthropic-ai/sdk/internal/utils/uuid.mjs"() {
101315
101315
  uuid42 = function() {
101316
- const { crypto: crypto9 } = globalThis;
101317
- if (crypto9?.randomUUID) {
101318
- uuid42 = crypto9.randomUUID.bind(crypto9);
101319
- return crypto9.randomUUID();
101316
+ const { crypto: crypto10 } = globalThis;
101317
+ if (crypto10?.randomUUID) {
101318
+ uuid42 = crypto10.randomUUID.bind(crypto10);
101319
+ return crypto10.randomUUID();
101320
101320
  }
101321
101321
  const u8 = new Uint8Array(1);
101322
- const randomByte = crypto9 ? () => crypto9.getRandomValues(u8)[0] : () => Math.random() * 255 & 255;
101322
+ const randomByte = crypto10 ? () => crypto10.getRandomValues(u8)[0] : () => Math.random() * 255 & 255;
101323
101323
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (+c ^ randomByte() & 15 >> +c / 4).toString(16));
101324
101324
  };
101325
101325
  }
@@ -107496,7 +107496,7 @@ async function findRg() {
107496
107496
  }
107497
107497
  return null;
107498
107498
  }
107499
- var fs17, fssync2, path19, cp, crypto7, readline, _BashSession_instances, _BashSession_proc, _BashSession_buf, _BashSession_truncated, _BashSession_closed, _BashSession_waiting, _BashSession_append, BASH_OUTPUT_LIMIT, BASH_DEFAULT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, GREP_OUTPUT_LIMIT, GREP_MAX_LINE_LENGTH, GLOB_RESULT_LIMIT, BashTimeoutError, ANSI_RE, fsGlob, BashSession, WALK_MAX_DEPTH, WALK_MAX_ENTRIES;
107499
+ var fs17, fssync2, path19, cp, crypto8, readline, _BashSession_instances, _BashSession_proc, _BashSession_buf, _BashSession_truncated, _BashSession_closed, _BashSession_waiting, _BashSession_append, BASH_OUTPUT_LIMIT, BASH_DEFAULT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, GREP_OUTPUT_LIMIT, GREP_MAX_LINE_LENGTH, GLOB_RESULT_LIMIT, BashTimeoutError, ANSI_RE, fsGlob, BashSession, WALK_MAX_DEPTH, WALK_MAX_ENTRIES;
107500
107500
  var init_node = __esm({
107501
107501
  "node_modules/@anthropic-ai/sdk/tools/agent-toolset/node.mjs"() {
107502
107502
  init_tslib();
@@ -107504,7 +107504,7 @@ var init_node = __esm({
107504
107504
  fssync2 = __toESM(require("node:fs"), 1);
107505
107505
  path19 = __toESM(require("node:path"), 1);
107506
107506
  cp = __toESM(require("node:child_process"), 1);
107507
- crypto7 = __toESM(require("node:crypto"), 1);
107507
+ crypto8 = __toESM(require("node:crypto"), 1);
107508
107508
  readline = __toESM(require("node:readline"), 1);
107509
107509
  init_error();
107510
107510
  init_ToolError();
@@ -107570,7 +107570,7 @@ var init_node = __esm({
107570
107570
  signal?.throwIfAborted();
107571
107571
  __classPrivateFieldSet2(this, _BashSession_buf, "", "f");
107572
107572
  __classPrivateFieldSet2(this, _BashSession_truncated, false, "f");
107573
- const sentinel3 = `__ANT_CMD_${crypto7.randomUUID()}_DONE__`;
107573
+ const sentinel3 = `__ANT_CMD_${crypto8.randomUUID()}_DONE__`;
107574
107574
  const sentinelSplit = `${sentinel3.slice(0, 8)}''${sentinel3.slice(8)}`;
107575
107575
  const wrapped = `{ ${command}
107576
107576
  } </dev/null 2>&1; printf '\\n${sentinelSplit}%d\\n' $?
@@ -124335,22 +124335,22 @@ var require_crypto2 = __commonJS({
124335
124335
  "use strict";
124336
124336
  Object.defineProperty(exports2, "__esModule", { value: true });
124337
124337
  exports2.NodeCrypto = void 0;
124338
- var crypto9 = require("crypto");
124338
+ var crypto10 = require("crypto");
124339
124339
  var NodeCrypto = class {
124340
124340
  async sha256DigestBase64(str2) {
124341
- return crypto9.createHash("sha256").update(str2).digest("base64");
124341
+ return crypto10.createHash("sha256").update(str2).digest("base64");
124342
124342
  }
124343
124343
  randomBytesBase64(count) {
124344
- return crypto9.randomBytes(count).toString("base64");
124344
+ return crypto10.randomBytes(count).toString("base64");
124345
124345
  }
124346
124346
  async verify(pubkey, data, signature) {
124347
- const verifier = crypto9.createVerify("RSA-SHA256");
124347
+ const verifier = crypto10.createVerify("RSA-SHA256");
124348
124348
  verifier.update(data);
124349
124349
  verifier.end();
124350
124350
  return verifier.verify(pubkey, signature, "base64");
124351
124351
  }
124352
124352
  async sign(privateKey, data) {
124353
- const signer = crypto9.createSign("RSA-SHA256");
124353
+ const signer = crypto10.createSign("RSA-SHA256");
124354
124354
  signer.update(data);
124355
124355
  signer.end();
124356
124356
  return signer.sign(privateKey, "base64");
@@ -124368,7 +124368,7 @@ var require_crypto2 = __commonJS({
124368
124368
  * string in hexadecimal encoding.
124369
124369
  */
124370
124370
  async sha256DigestHex(str2) {
124371
- return crypto9.createHash("sha256").update(str2).digest("hex");
124371
+ return crypto10.createHash("sha256").update(str2).digest("hex");
124372
124372
  }
124373
124373
  /**
124374
124374
  * Computes the HMAC hash of a message using the provided crypto key and the
@@ -124380,7 +124380,7 @@ var require_crypto2 = __commonJS({
124380
124380
  */
124381
124381
  async signWithHmacSha256(key, msg) {
124382
124382
  const cryptoKey = typeof key === "string" ? key : toBuffer(key);
124383
- return toArrayBuffer(crypto9.createHmac("sha256", cryptoKey).update(msg).digest());
124383
+ return toArrayBuffer(crypto10.createHmac("sha256", cryptoKey).update(msg).digest());
124384
124384
  }
124385
124385
  };
124386
124386
  exports2.NodeCrypto = NodeCrypto;
@@ -125071,10 +125071,10 @@ var require_oauth2client = __commonJS({
125071
125071
  * https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2-codeVerifier.js
125072
125072
  */
125073
125073
  async generateCodeVerifierAsync() {
125074
- const crypto9 = (0, crypto_1.createCrypto)();
125075
- const randomString2 = crypto9.randomBytesBase64(96);
125074
+ const crypto10 = (0, crypto_1.createCrypto)();
125075
+ const randomString2 = crypto10.randomBytesBase64(96);
125076
125076
  const codeVerifier = randomString2.replace(/\+/g, "~").replace(/=/g, "_").replace(/\//g, "-");
125077
- const unencodedCodeChallenge = await crypto9.sha256DigestBase64(codeVerifier);
125077
+ const unencodedCodeChallenge = await crypto10.sha256DigestBase64(codeVerifier);
125078
125078
  const codeChallenge = unencodedCodeChallenge.split("=")[0].replace(/\+/g, "-").replace(/\//g, "_");
125079
125079
  return { codeVerifier, codeChallenge };
125080
125080
  }
@@ -125515,7 +125515,7 @@ var require_oauth2client = __commonJS({
125515
125515
  * @return Returns a promise resolving to LoginTicket on verification.
125516
125516
  */
125517
125517
  async verifySignedJwtWithCertsAsync(jwt2, certs, requiredAudience, issuers, maxExpiry) {
125518
- const crypto9 = (0, crypto_1.createCrypto)();
125518
+ const crypto10 = (0, crypto_1.createCrypto)();
125519
125519
  if (!maxExpiry) {
125520
125520
  maxExpiry = _OAuth2Client.DEFAULT_MAX_TOKEN_LIFETIME_SECS_;
125521
125521
  }
@@ -125528,7 +125528,7 @@ var require_oauth2client = __commonJS({
125528
125528
  let envelope;
125529
125529
  let payload;
125530
125530
  try {
125531
- envelope = JSON.parse(crypto9.decodeBase64StringUtf8(segments[0]));
125531
+ envelope = JSON.parse(crypto10.decodeBase64StringUtf8(segments[0]));
125532
125532
  } catch (err) {
125533
125533
  if (err instanceof Error) {
125534
125534
  err.message = `Can't parse token envelope: ${segments[0]}': ${err.message}`;
@@ -125539,7 +125539,7 @@ var require_oauth2client = __commonJS({
125539
125539
  throw new Error("Can't parse token envelope: " + segments[0]);
125540
125540
  }
125541
125541
  try {
125542
- payload = JSON.parse(crypto9.decodeBase64StringUtf8(segments[1]));
125542
+ payload = JSON.parse(crypto10.decodeBase64StringUtf8(segments[1]));
125543
125543
  } catch (err) {
125544
125544
  if (err instanceof Error) {
125545
125545
  err.message = `Can't parse token payload '${segments[0]}`;
@@ -125556,7 +125556,7 @@ var require_oauth2client = __commonJS({
125556
125556
  if (envelope.alg === "ES256") {
125557
125557
  signature = formatEcdsa.joseToDer(signature, "ES256").toString("base64");
125558
125558
  }
125559
- const verified = await crypto9.verify(cert, signed, signature);
125559
+ const verified = await crypto10.verify(cert, signed, signature);
125560
125560
  if (!verified) {
125561
125561
  throw new Error("Invalid token signature: " + jwt2);
125562
125562
  }
@@ -128126,14 +128126,14 @@ var require_awsrequestsigner = __commonJS({
128126
128126
  }
128127
128127
  };
128128
128128
  exports2.AwsRequestSigner = AwsRequestSigner;
128129
- async function sign(crypto9, key, msg) {
128130
- return await crypto9.signWithHmacSha256(key, msg);
128131
- }
128132
- async function getSigningKey(crypto9, key, dateStamp, region, serviceName) {
128133
- const kDate = await sign(crypto9, `AWS4${key}`, dateStamp);
128134
- const kRegion = await sign(crypto9, kDate, region);
128135
- const kService = await sign(crypto9, kRegion, serviceName);
128136
- const kSigning = await sign(crypto9, kService, "aws4_request");
128129
+ async function sign(crypto10, key, msg) {
128130
+ return await crypto10.signWithHmacSha256(key, msg);
128131
+ }
128132
+ async function getSigningKey(crypto10, key, dateStamp, region, serviceName) {
128133
+ const kDate = await sign(crypto10, `AWS4${key}`, dateStamp);
128134
+ const kRegion = await sign(crypto10, kDate, region);
128135
+ const kService = await sign(crypto10, kRegion, serviceName);
128136
+ const kSigning = await sign(crypto10, kService, "aws4_request");
128137
128137
  return kSigning;
128138
128138
  }
128139
128139
  async function generateAuthenticationHeaderMap(options) {
@@ -129099,7 +129099,7 @@ var require_gdchclient = __commonJS({
129099
129099
  "use strict";
129100
129100
  Object.defineProperty(exports2, "__esModule", { value: true });
129101
129101
  exports2.GdchClient = exports2.GDCH_SERVICE_ACCOUNT_TYPE = void 0;
129102
- var crypto9 = require("crypto");
129102
+ var crypto10 = require("crypto");
129103
129103
  var fs31 = require("fs");
129104
129104
  var https5 = require("https");
129105
129105
  var oauth2client_1 = require_oauth2client();
@@ -129290,7 +129290,7 @@ var require_gdchclient = __commonJS({
129290
129290
  const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
129291
129291
  const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
129292
129292
  const signingInput = `${encodedHeader}.${encodedPayload}`;
129293
- const signature = crypto9.sign("sha256", Buffer.from(signingInput), {
129293
+ const signature = crypto10.sign("sha256", Buffer.from(signingInput), {
129294
129294
  key: this.privateKey,
129295
129295
  dsaEncoding: "ieee-p1363"
129296
129296
  });
@@ -130153,24 +130153,24 @@ var require_googleauth = __commonJS({
130153
130153
  const signed = await client.sign(data);
130154
130154
  return signed.signedBlob;
130155
130155
  }
130156
- const crypto9 = (0, crypto_1.createCrypto)();
130156
+ const crypto10 = (0, crypto_1.createCrypto)();
130157
130157
  if (client instanceof jwtclient_1.JWT && client.key) {
130158
- const sign = await crypto9.sign(client.key, data);
130158
+ const sign = await crypto10.sign(client.key, data);
130159
130159
  return sign;
130160
130160
  }
130161
130161
  const creds = await this.getCredentials();
130162
130162
  if (!creds.client_email) {
130163
130163
  throw new Error("Cannot sign data without `client_email`.");
130164
130164
  }
130165
- return this.signBlob(crypto9, creds.client_email, data, endpoint);
130165
+ return this.signBlob(crypto10, creds.client_email, data, endpoint);
130166
130166
  }
130167
- async signBlob(crypto9, emailOrUniqueId, data, endpoint) {
130167
+ async signBlob(crypto10, emailOrUniqueId, data, endpoint) {
130168
130168
  const url3 = new URL(endpoint + `${emailOrUniqueId}:signBlob`);
130169
130169
  const res = await this.request({
130170
130170
  method: "POST",
130171
130171
  url: url3.href,
130172
130172
  data: {
130173
- payload: crypto9.encodeBase64StringUtf8(data)
130173
+ payload: crypto10.encodeBase64StringUtf8(data)
130174
130174
  },
130175
130175
  retry: true,
130176
130176
  retryConfig: {
@@ -132825,7 +132825,7 @@ var require_websocket = __commonJS({
132825
132825
  var http5 = require("http");
132826
132826
  var net = require("net");
132827
132827
  var tls = require("tls");
132828
- var { randomBytes: randomBytes2, createHash: createHash7 } = require("crypto");
132828
+ var { randomBytes: randomBytes2, createHash: createHash8 } = require("crypto");
132829
132829
  var { Duplex, Readable: Readable4 } = require("stream");
132830
132830
  var { URL: URL3 } = require("url");
132831
132831
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -133493,7 +133493,7 @@ var require_websocket = __commonJS({
133493
133493
  abortHandshake(websocket, socket, "Invalid Upgrade header");
133494
133494
  return;
133495
133495
  }
133496
- const digest = createHash7("sha1").update(key + GUID).digest("base64");
133496
+ const digest = createHash8("sha1").update(key + GUID).digest("base64");
133497
133497
  if (res.headers["sec-websocket-accept"] !== digest) {
133498
133498
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
133499
133499
  return;
@@ -133862,7 +133862,7 @@ var require_websocket_server = __commonJS({
133862
133862
  var EventEmitter2 = require("events");
133863
133863
  var http5 = require("http");
133864
133864
  var { Duplex } = require("stream");
133865
- var { createHash: createHash7 } = require("crypto");
133865
+ var { createHash: createHash8 } = require("crypto");
133866
133866
  var extension2 = require_extension();
133867
133867
  var PerMessageDeflate2 = require_permessage_deflate();
133868
133868
  var subprotocol2 = require_subprotocol();
@@ -134169,7 +134169,7 @@ var require_websocket_server = __commonJS({
134169
134169
  );
134170
134170
  }
134171
134171
  if (this._state > RUNNING) return abortHandshake(socket, 503);
134172
- const digest = createHash7("sha1").update(key + GUID).digest("base64");
134172
+ const digest = createHash8("sha1").update(key + GUID).digest("base64");
134173
134173
  const headers = [
134174
134174
  "HTTP/1.1 101 Switching Protocols",
134175
134175
  "Upgrade: websocket",
@@ -157481,7 +157481,7 @@ var fsPromises = __toESM(require("fs/promises"));
157481
157481
  var path32 = __toESM(require("path"));
157482
157482
 
157483
157483
  // src/parser.ts
157484
- var path3 = __toESM(require("path"));
157484
+ var path4 = __toESM(require("path"));
157485
157485
 
157486
157486
  // src/quotedString.ts
157487
157487
  function decodeQuotedStringLiteral(literal2) {
@@ -158653,6 +158653,49 @@ var IDENTIFIER = "[a-zA-Z_][a-zA-Z0-9_]*";
158653
158653
  var MODEL_PROVIDERS = ["anthropic", "google", "local", "openai"];
158654
158654
  var MODEL_PROVIDER = new RegExp(`^(${MODEL_PROVIDERS.join("|")})$`, "i");
158655
158655
  var MODEL_DIRECTIVES = ["provider", "name", "apiKey", "baseUrl"];
158656
+ var MCP_DIRECTIVES = {
158657
+ transport: { transport: "both", repeatable: false },
158658
+ command: { transport: "stdio", repeatable: false },
158659
+ cwd: { transport: "stdio", repeatable: false },
158660
+ env: { transport: "stdio", repeatable: true },
158661
+ url: { transport: "http", repeatable: false },
158662
+ header: { transport: "http", repeatable: true },
158663
+ timeout: { transport: "http", repeatable: false },
158664
+ session: { transport: "both", repeatable: false }
158665
+ };
158666
+ var MCP_DIRECTIVE_NAMES = Object.keys(MCP_DIRECTIVES);
158667
+ var MCP_SESSION_SCOPES = ["run", "agent", "call"];
158668
+ function splitCommandParts(value) {
158669
+ const parts = [];
158670
+ const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g;
158671
+ let match2;
158672
+ while ((match2 = pattern.exec(value)) !== null) {
158673
+ parts.push(match2[1] ?? match2[2] ?? match2[3]);
158674
+ }
158675
+ return parts;
158676
+ }
158677
+ function splitNamedDirective(value) {
158678
+ const match2 = value.trim().match(/^(\S+)\s+(.+)$/);
158679
+ if (!match2) {
158680
+ return void 0;
158681
+ }
158682
+ return { name: match2[1], value: match2[2].trim() };
158683
+ }
158684
+ var HTTP_HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
158685
+ function splitHeaderDirective(value) {
158686
+ const colonForm = value.trim().match(/^([^\s:]+)\s*:\s*(.+)$/);
158687
+ if (colonForm) {
158688
+ return { name: colonForm[1], value: colonForm[2].trim() };
158689
+ }
158690
+ return splitNamedDirective(value);
158691
+ }
158692
+ var LIMIT_DIRECTIVES = {
158693
+ max_tokens: "maxTokens",
158694
+ max_depth: "maxDepth",
158695
+ max_invocations: "maxInvocations",
158696
+ max_turns: "maxTurns",
158697
+ contract_retries: "contractRetries"
158698
+ };
158656
158699
  function parseError(lineNumber, message, code) {
158657
158700
  return { lineNumber, message, code, blocking: true };
158658
158701
  }
@@ -158790,6 +158833,38 @@ function validateAgentScope(scope, options = {}) {
158790
158833
  const checkUnknownReferences = options.checkUnknownReferences ?? true;
158791
158834
  const reportedMissingDescriptions = /* @__PURE__ */ new Set();
158792
158835
  const checkedAcceptsRoots = /* @__PURE__ */ new Set();
158836
+ if (options.configuredMcpAliases) {
158837
+ const configured = options.configuredMcpAliases;
158838
+ const resolvable = (agent, alias) => agent.mcpServers?.has(alias.toLowerCase()) === true || configured.has(alias.toLowerCase());
158839
+ for (const agent of scope.values()) {
158840
+ for (const tool of agent.tools) {
158841
+ if (resolvable(agent, tool.mcpAlias)) {
158842
+ continue;
158843
+ }
158844
+ errors.push({
158845
+ ...parseError(
158846
+ tool.lineNumber,
158847
+ `Tool '${tool.name}' uses MCP alias '${tool.mcpAlias}', which is not declared in this sidecar and not found in norn.config.json mcp.servers. Declare it with 'mcp ${tool.mcpAlias} ... end mcp' or add it to the config.`,
158848
+ "agent-tool-mcp-undeclared"
158849
+ ),
158850
+ sourcePath: agent.sourcePath
158851
+ });
158852
+ }
158853
+ for (const grant of agent.grantedMcpServers ?? []) {
158854
+ if (resolvable(agent, grant.alias)) {
158855
+ continue;
158856
+ }
158857
+ errors.push({
158858
+ ...parseError(
158859
+ grant.lineNumber,
158860
+ `Agent '${agent.name}' grants MCP server '${grant.alias}', which is not declared in this sidecar and not found in norn.config.json mcp.servers. Declare it with 'mcp ${grant.alias} ... end mcp' or add it to the config.`,
158861
+ "agent-mcp-grant-unknown"
158862
+ ),
158863
+ sourcePath: agent.sourcePath
158864
+ });
158865
+ }
158866
+ }
158867
+ }
158793
158868
  for (const caller of scope.values()) {
158794
158869
  for (const reference of caller.callableAgents) {
158795
158870
  const target = scope.get(reference.name.toLowerCase());
@@ -158854,13 +158929,137 @@ function validateAgentScope(scope, options = {}) {
158854
158929
  function parseNornAgentFile(content, sourcePath) {
158855
158930
  const lines = content.split(/\r?\n/);
158856
158931
  const models = /* @__PURE__ */ new Map();
158857
- const mcpAliases = /* @__PURE__ */ new Map();
158932
+ const mcpServers = /* @__PURE__ */ new Map();
158858
158933
  const agents = [];
158859
158934
  const errors = [];
158860
158935
  const agentNames = /* @__PURE__ */ new Map();
158861
158936
  let pending;
158862
158937
  let pendingModel;
158938
+ let pendingMcp;
158863
158939
  let sawAgent = false;
158940
+ const reportBareMcpAlias = (mcp) => {
158941
+ errors.push(parseError(
158942
+ mcp.lineNumber,
158943
+ `'mcp ${mcp.alias}' no longer declares a server. Either declare it here:
158944
+ mcp ${mcp.alias}
158945
+ transport stdio
158946
+ command <command> <args\u2026>
158947
+ end mcp
158948
+ \u2026or delete this line and grant it from the agent that uses it \u2014 an alias with no block resolves through norn.config.json mcp.servers.`,
158949
+ "agent-mcp-bare-alias-removed"
158950
+ ));
158951
+ };
158952
+ const finishMcp = (lineNumber) => {
158953
+ if (!pendingMcp) {
158954
+ errors.push(parseError(lineNumber, "Unexpected 'end mcp' without a matching mcp block.", "agent-mcp-end-unexpected"));
158955
+ return;
158956
+ }
158957
+ const mcp = pendingMcp;
158958
+ pendingMcp = void 0;
158959
+ const transport = mcp.values.transport;
158960
+ const templatedTransport = Boolean(transport?.value.includes("{{"));
158961
+ let resolvedTransport;
158962
+ if (!transport) {
158963
+ errors.push(parseError(
158964
+ mcp.lineNumber,
158965
+ `MCP server '${mcp.alias}' requires a transport directive. Expected stdio or http.`,
158966
+ "agent-mcp-transport-missing"
158967
+ ));
158968
+ mcp.invalid = true;
158969
+ } else if (!templatedTransport) {
158970
+ const lowered = transport.value.toLowerCase();
158971
+ if (lowered !== "stdio" && lowered !== "http") {
158972
+ errors.push(parseError(
158973
+ transport.lineNumber,
158974
+ `Unknown transport '${transport.value}' in MCP server '${mcp.alias}'. Expected stdio or http.`,
158975
+ "agent-mcp-transport-invalid"
158976
+ ));
158977
+ mcp.invalid = true;
158978
+ } else {
158979
+ resolvedTransport = lowered;
158980
+ }
158981
+ }
158982
+ if (resolvedTransport) {
158983
+ for (const directive of MCP_DIRECTIVE_NAMES) {
158984
+ const allowed = MCP_DIRECTIVES[directive].transport;
158985
+ if (allowed === "both" || allowed === resolvedTransport) {
158986
+ continue;
158987
+ }
158988
+ const declaredLines = directive === "env" ? mcp.envTemplates.map((entry) => entry.lineNumber) : directive === "header" ? mcp.headerTemplates.map((entry) => entry.lineNumber) : mcp.values[directive] ? [mcp.values[directive].lineNumber] : [];
158989
+ for (const declaredLine of declaredLines) {
158990
+ errors.push(parseError(
158991
+ declaredLine,
158992
+ `'${directive}' is not allowed under transport ${resolvedTransport} in MCP server '${mcp.alias}'. It belongs to transport ${allowed}.`,
158993
+ "agent-mcp-directive-not-allowed"
158994
+ ));
158995
+ mcp.invalid = true;
158996
+ }
158997
+ }
158998
+ if (resolvedTransport === "stdio" && mcp.commandTemplates.length === 0) {
158999
+ errors.push(parseError(
159000
+ mcp.lineNumber,
159001
+ `MCP server '${mcp.alias}' uses transport stdio and requires a command directive.`,
159002
+ "agent-mcp-command-missing"
159003
+ ));
159004
+ mcp.invalid = true;
159005
+ }
159006
+ if (resolvedTransport === "http" && !mcp.values.url) {
159007
+ errors.push(parseError(
159008
+ mcp.lineNumber,
159009
+ `MCP server '${mcp.alias}' uses transport http and requires a url directive. Norn dials the endpoint; it never starts the server.`,
159010
+ "agent-mcp-url-missing"
159011
+ ));
159012
+ mcp.invalid = true;
159013
+ }
159014
+ }
159015
+ let timeoutMs;
159016
+ const timeout = mcp.values.timeout;
159017
+ if (timeout) {
159018
+ const parsedTimeout = /^\d+$/.test(timeout.value) ? Number(timeout.value) : Number.NaN;
159019
+ if (!Number.isSafeInteger(parsedTimeout) || parsedTimeout <= 0) {
159020
+ errors.push(parseError(
159021
+ timeout.lineNumber,
159022
+ `timeout in MCP server '${mcp.alias}' requires a positive integer of milliseconds.`,
159023
+ "agent-mcp-timeout-invalid"
159024
+ ));
159025
+ mcp.invalid = true;
159026
+ } else {
159027
+ timeoutMs = parsedTimeout;
159028
+ }
159029
+ }
159030
+ let session = "run";
159031
+ const sessionValue = mcp.values.session;
159032
+ if (sessionValue) {
159033
+ const lowered = sessionValue.value.toLowerCase();
159034
+ if (!MCP_SESSION_SCOPES.includes(lowered)) {
159035
+ errors.push(parseError(
159036
+ sessionValue.lineNumber,
159037
+ `Unknown session lifetime '${sessionValue.value}' in MCP server '${mcp.alias}'. Expected one of: ${MCP_SESSION_SCOPES.join(", ")}.`,
159038
+ "agent-mcp-session-invalid"
159039
+ ));
159040
+ mcp.invalid = true;
159041
+ } else {
159042
+ session = lowered;
159043
+ }
159044
+ }
159045
+ if (mcp.invalid) {
159046
+ return;
159047
+ }
159048
+ mcpServers.set(mcp.alias.toLowerCase(), {
159049
+ alias: mcp.alias,
159050
+ transportTemplate: transport.value,
159051
+ commandTemplates: mcp.commandTemplates,
159052
+ ...mcp.values.cwd ? { cwdTemplate: mcp.values.cwd.value } : {},
159053
+ ...mcp.envTemplates.length > 0 ? { envTemplates: mcp.envTemplates } : {},
159054
+ ...mcp.values.url ? { urlTemplate: mcp.values.url.value } : {},
159055
+ ...mcp.headerTemplates.length > 0 ? { headerTemplates: mcp.headerTemplates } : {},
159056
+ ...timeoutMs !== void 0 ? { timeoutMs } : {},
159057
+ session,
159058
+ lineNumber: mcp.lineNumber,
159059
+ endLineNumber: lineNumber,
159060
+ sourcePath
159061
+ });
159062
+ };
158864
159063
  const finishModel = (lineNumber) => {
158865
159064
  if (!pendingModel) {
158866
159065
  errors.push(parseError(lineNumber, "Unexpected 'end model' without a matching model block.", "agent-model-end-unexpected"));
@@ -158943,15 +159142,19 @@ function parseNornAgentFile(content, sourcePath) {
158943
159142
  }
158944
159143
  }
158945
159144
  }
158946
- for (const tool of pending.tools) {
158947
- if (!mcpAliases.has(tool.mcpAlias.toLowerCase())) {
158948
- errors.push(parseError(
158949
- tool.lineNumber,
158950
- `Tool '${tool.name}' uses undeclared MCP alias '${tool.mcpAlias}'. Add 'mcp ${tool.mcpAlias}' at file scope.`,
158951
- "agent-tool-mcp-undeclared"
158952
- ));
158953
- pending.invalid = true;
159145
+ for (const grant of pending.grantedMcpServers) {
159146
+ const narrowed = pending.tools.filter(
159147
+ (tool) => tool.mcpAlias.toLowerCase() === grant.alias.toLowerCase()
159148
+ );
159149
+ if (narrowed.length === 0) {
159150
+ continue;
158954
159151
  }
159152
+ errors.push(parseError(
159153
+ grant.lineNumber,
159154
+ `Agent '${pending.name}' grants all of MCP server '${grant.alias}' and also names ${narrowed.map((tool) => `'${tool.name}'`).join(", ")} from it. Keep one: 'mcp ${grant.alias}' for every tool it advertises, or 'tools' for exactly those.`,
159155
+ "agent-mcp-grant-redundant"
159156
+ ));
159157
+ pending.invalid = true;
158955
159158
  }
158956
159159
  if (!pending.invalid && model && pending.systemPrompt) {
158957
159160
  agents.push({
@@ -158963,12 +159166,15 @@ function parseNornAgentFile(content, sourcePath) {
158963
159166
  descriptionFile: pending.description?.file,
158964
159167
  systemPromptFile: pending.systemPrompt.file,
158965
159168
  tools: pending.tools,
159169
+ ...pending.grantedMcpServers.length > 0 ? { grantedMcpServers: pending.grantedMcpServers } : {},
158966
159170
  callableAgents: pending.callableAgents,
159171
+ mcpServers,
158967
159172
  accepts: pending.accepts,
158968
159173
  returns: pending.returns,
158969
159174
  maxTokens: pending.maxTokens?.value,
158970
159175
  maxDepth: pending.maxDepth?.value,
158971
159176
  maxInvocations: pending.maxInvocations?.value,
159177
+ maxTurns: pending.maxTurns?.value,
158972
159178
  contractRetries: pending.contractRetries?.value,
158973
159179
  sourcePath,
158974
159180
  lineNumber: pending.lineNumber,
@@ -158981,6 +159187,101 @@ function parseNornAgentFile(content, sourcePath) {
158981
159187
  const rawLine = lines[index];
158982
159188
  const withoutComment = stripInlineComment(rawLine);
158983
159189
  const trimmed = withoutComment.trim();
159190
+ if (pendingMcp) {
159191
+ if (!trimmed) {
159192
+ continue;
159193
+ }
159194
+ if (/^end\s+mcp$/i.test(trimmed)) {
159195
+ finishMcp(index);
159196
+ continue;
159197
+ }
159198
+ const mcpDirectiveMatch = trimmed.match(
159199
+ new RegExp(`^(${MCP_DIRECTIVE_NAMES.join("|")})\\b\\s*(.*)$`, "i")
159200
+ );
159201
+ if (mcpDirectiveMatch) {
159202
+ const directive = MCP_DIRECTIVE_NAMES.find(
159203
+ (candidate) => candidate === mcpDirectiveMatch[1].toLowerCase()
159204
+ );
159205
+ const rawValue = mcpDirectiveMatch[2].trim();
159206
+ pendingMcp.sawDirective = true;
159207
+ if (directive === "env" || directive === "header") {
159208
+ const named = directive === "header" ? splitHeaderDirective(rawValue) : splitNamedDirective(rawValue);
159209
+ if (!named) {
159210
+ errors.push(parseError(
159211
+ index,
159212
+ `${directive} in MCP server '${pendingMcp.alias}' requires ${directive === "env" ? "<NAME> <value>" : "<Name>: <value>"}.`,
159213
+ `agent-mcp-${directive}-malformed`
159214
+ ));
159215
+ pendingMcp.invalid = true;
159216
+ continue;
159217
+ }
159218
+ if (directive === "header" && !HTTP_HEADER_NAME.test(named.name)) {
159219
+ errors.push(parseError(
159220
+ index,
159221
+ `Header name '${named.name}' in MCP server '${pendingMcp.alias}' is not a valid HTTP header name. Use letters, digits, and -_.`,
159222
+ "agent-mcp-header-name-invalid"
159223
+ ));
159224
+ pendingMcp.invalid = true;
159225
+ continue;
159226
+ }
159227
+ const entry = { name: named.name, valueTemplate: named.value, lineNumber: index };
159228
+ if (directive === "env") {
159229
+ pendingMcp.envTemplates.push(entry);
159230
+ } else {
159231
+ pendingMcp.headerTemplates.push(entry);
159232
+ }
159233
+ continue;
159234
+ }
159235
+ if (pendingMcp.values[directive]) {
159236
+ errors.push(parseError(
159237
+ index,
159238
+ `MCP server '${pendingMcp.alias}' declares ${directive} more than once.`,
159239
+ "agent-mcp-directive-duplicate"
159240
+ ));
159241
+ pendingMcp.invalid = true;
159242
+ continue;
159243
+ }
159244
+ if (!rawValue) {
159245
+ errors.push(parseError(
159246
+ index,
159247
+ `MCP server '${pendingMcp.alias}' declares ${directive} with no value.`,
159248
+ "agent-mcp-directive-empty"
159249
+ ));
159250
+ pendingMcp.invalid = true;
159251
+ continue;
159252
+ }
159253
+ pendingMcp.values[directive] = {
159254
+ value: directive === "command" ? rawValue : normalizeDirectiveValue(rawValue),
159255
+ lineNumber: index
159256
+ };
159257
+ if (directive === "command") {
159258
+ pendingMcp.commandTemplates = splitCommandParts(rawValue);
159259
+ }
159260
+ continue;
159261
+ }
159262
+ if (!pendingMcp.sawDirective) {
159263
+ reportBareMcpAlias(pendingMcp);
159264
+ pendingMcp = void 0;
159265
+ index--;
159266
+ continue;
159267
+ }
159268
+ if (/^mcp\b/i.test(trimmed)) {
159269
+ errors.push(parseError(index, `Nested mcp blocks are not allowed (inside '${pendingMcp.alias}').`, "agent-mcp-nested"));
159270
+ } else if (/^(agent|model)\b/i.test(trimmed)) {
159271
+ errors.push(parseError(pendingMcp.lineNumber, `MCP server '${pendingMcp.alias}' is missing 'end mcp'.`, "agent-mcp-unterminated"));
159272
+ pendingMcp = void 0;
159273
+ index--;
159274
+ continue;
159275
+ } else {
159276
+ errors.push(parseError(
159277
+ index,
159278
+ `Unknown directive in MCP server '${pendingMcp.alias}': '${trimmed}'. Expected ${MCP_DIRECTIVE_NAMES.join(", ")}, or 'end mcp'.`,
159279
+ "agent-mcp-directive-unknown"
159280
+ ));
159281
+ }
159282
+ pendingMcp.invalid = true;
159283
+ continue;
159284
+ }
158984
159285
  if (pendingModel) {
158985
159286
  if (!trimmed) {
158986
159287
  continue;
@@ -159075,17 +159376,30 @@ end model`,
159075
159376
  }
159076
159377
  const mcpMatch = trimmed.match(new RegExp(`^mcp\\s+(${IDENTIFIER})$`, "i"));
159077
159378
  if (mcpMatch) {
159379
+ const alias = mcpMatch[1];
159380
+ let invalid = false;
159078
159381
  if (sawAgent) {
159079
- errors.push(parseError(index, "MCP aliases must be declared before the first agent block.", "agent-mcp-not-at-top"));
159080
- continue;
159382
+ errors.push(parseError(index, "MCP server blocks must be declared before the first agent block.", "agent-mcp-not-at-top"));
159383
+ invalid = true;
159081
159384
  }
159082
- const alias = mcpMatch[1];
159083
- const lowerAlias = alias.toLowerCase();
159084
- if (mcpAliases.has(lowerAlias)) {
159385
+ if (mcpServers.has(alias.toLowerCase())) {
159085
159386
  errors.push(parseError(index, `Duplicate MCP alias '${alias}'.`, "agent-mcp-duplicate"));
159086
- } else {
159087
- mcpAliases.set(lowerAlias, alias);
159387
+ invalid = true;
159088
159388
  }
159389
+ pendingMcp = {
159390
+ alias,
159391
+ lineNumber: index,
159392
+ values: {},
159393
+ commandTemplates: [],
159394
+ envTemplates: [],
159395
+ headerTemplates: [],
159396
+ sawDirective: false,
159397
+ invalid
159398
+ };
159399
+ continue;
159400
+ }
159401
+ if (/^end\s+mcp$/i.test(trimmed)) {
159402
+ finishMcp(index);
159089
159403
  continue;
159090
159404
  }
159091
159405
  const agentMatch = trimmed.match(new RegExp(`^agent\\s+(${IDENTIFIER})$`, "i"));
@@ -159105,6 +159419,7 @@ end model`,
159105
159419
  lineNumber: index,
159106
159420
  endLineNumber: index,
159107
159421
  tools: [],
159422
+ grantedMcpServers: [],
159108
159423
  callableAgents: [],
159109
159424
  invalid
159110
159425
  };
@@ -159117,7 +159432,7 @@ end model`,
159117
159432
  if (/^model\b/i.test(trimmed)) {
159118
159433
  errors.push(parseError(index, "Malformed model declaration. Expected: model <Alias>, then provider/name/apiKey directives, then end model.", "agent-model-malformed"));
159119
159434
  } else if (/^mcp\b/i.test(trimmed)) {
159120
- errors.push(parseError(index, "Malformed MCP declaration. Expected: mcp <alias>.", "agent-mcp-malformed"));
159435
+ errors.push(parseError(index, "Malformed MCP declaration. Expected: mcp <Alias>, then transport and its directives, then end mcp.", "agent-mcp-malformed"));
159121
159436
  } else if (/^agent\b/i.test(trimmed)) {
159122
159437
  errors.push(parseError(index, "Malformed agent declaration. Expected: agent <Name>.", "agent-declaration-malformed"));
159123
159438
  } else {
@@ -159163,6 +159478,35 @@ end model`,
159163
159478
  }
159164
159479
  continue;
159165
159480
  }
159481
+ const grantMatch = trimmed.match(/^mcp(?:\s+(.*))?$/i);
159482
+ if (grantMatch) {
159483
+ const rawValue = (grantMatch[1] ?? "").trim();
159484
+ const tokens = rawValue ? rawValue.split(",").map((part) => part.trim()) : [];
159485
+ if (tokens.length === 0 || tokens.some((token) => !new RegExp(`^${IDENTIFIER}$`).test(token))) {
159486
+ errors.push(parseError(
159487
+ index,
159488
+ `Invalid MCP server grant '${trimmed}' in agent '${pending.name}'. Expected: mcp <Alias>[, <Alias>\u2026].`,
159489
+ "agent-mcp-grant-invalid"
159490
+ ));
159491
+ pending.invalid = true;
159492
+ continue;
159493
+ }
159494
+ const granted = new Set(pending.grantedMcpServers.map((grant) => grant.alias.toLowerCase()));
159495
+ for (const alias of tokens) {
159496
+ if (granted.has(alias.toLowerCase())) {
159497
+ errors.push(parseError(
159498
+ index,
159499
+ `Agent '${pending.name}' grants MCP server '${alias}' more than once.`,
159500
+ "agent-mcp-grant-duplicate"
159501
+ ));
159502
+ pending.invalid = true;
159503
+ continue;
159504
+ }
159505
+ granted.add(alias.toLowerCase());
159506
+ pending.grantedMcpServers.push({ alias, lineNumber: index });
159507
+ }
159508
+ continue;
159509
+ }
159166
159510
  const toolsMatch = trimmed.match(/^tools(?:\s+(.*))?$/i);
159167
159511
  if (toolsMatch) {
159168
159512
  const parsedTools = parseToolReferences(toolsMatch[1] ?? "", index, errors);
@@ -159205,58 +159549,27 @@ end model`,
159205
159549
  }
159206
159550
  continue;
159207
159551
  }
159208
- const limitMatch = trimmed.match(/^(max_tokens|max_depth|max_invocations|contract_retries)(?:\s+(.*))?$/i);
159552
+ const limitMatch = trimmed.match(
159553
+ new RegExp(`^(${Object.keys(LIMIT_DIRECTIVES).join("|")})(?:\\s+(.*))?$`, "i")
159554
+ );
159209
159555
  if (limitMatch) {
159210
159556
  const directive = limitMatch[1].toLowerCase();
159557
+ const field = LIMIT_DIRECTIVES[directive];
159211
159558
  const allowZero = directive === "contract_retries";
159212
- const duplicateCode = `agent-${directive.replace(/_/g, "-")}-duplicate`;
159213
- const duplicateMessage = `Agent '${pending.name}' declares ${directive} more than once.`;
159214
- if (directive === "max_tokens") {
159215
- if (pending.maxTokens) {
159216
- errors.push(parseError(index, duplicateMessage, duplicateCode));
159217
- pending.invalid = true;
159218
- } else {
159219
- const value = parseLimitValue(directive, limitMatch[2], index, errors, allowZero);
159220
- if (value === void 0) {
159221
- pending.invalid = true;
159222
- } else {
159223
- pending.maxTokens = { value, lineNumber: index };
159224
- }
159225
- }
159226
- } else if (directive === "max_depth") {
159227
- if (pending.maxDepth) {
159228
- errors.push(parseError(index, duplicateMessage, duplicateCode));
159229
- pending.invalid = true;
159230
- } else {
159231
- const value = parseLimitValue(directive, limitMatch[2], index, errors, allowZero);
159232
- if (value === void 0) {
159233
- pending.invalid = true;
159234
- } else {
159235
- pending.maxDepth = { value, lineNumber: index };
159236
- }
159237
- }
159238
- } else if (directive === "max_invocations") {
159239
- if (pending.maxInvocations) {
159240
- errors.push(parseError(index, duplicateMessage, duplicateCode));
159241
- pending.invalid = true;
159242
- } else {
159243
- const value = parseLimitValue(directive, limitMatch[2], index, errors, allowZero);
159244
- if (value === void 0) {
159245
- pending.invalid = true;
159246
- } else {
159247
- pending.maxInvocations = { value, lineNumber: index };
159248
- }
159249
- }
159250
- } else if (pending.contractRetries) {
159251
- errors.push(parseError(index, duplicateMessage, duplicateCode));
159559
+ if (pending[field]) {
159560
+ errors.push(parseError(
159561
+ index,
159562
+ `Agent '${pending.name}' declares ${directive} more than once.`,
159563
+ `agent-${directive.replace(/_/g, "-")}-duplicate`
159564
+ ));
159565
+ pending.invalid = true;
159566
+ continue;
159567
+ }
159568
+ const value = parseLimitValue(directive, limitMatch[2], index, errors, allowZero);
159569
+ if (value === void 0) {
159252
159570
  pending.invalid = true;
159253
159571
  } else {
159254
- const value = parseLimitValue(directive, limitMatch[2], index, errors, allowZero);
159255
- if (value === void 0) {
159256
- pending.invalid = true;
159257
- } else {
159258
- pending.contractRetries = { value, lineNumber: index };
159259
- }
159572
+ pending[field] = { value, lineNumber: index };
159260
159573
  }
159261
159574
  continue;
159262
159575
  }
@@ -159353,7 +159666,7 @@ end model`,
159353
159666
  errors.push(parseError(index, 'Malformed describe block. Expected: describe "..." (multi-line allowed) or describe file <path>.', "agent-describe-malformed"));
159354
159667
  } else if (/^system\b/i.test(trimmed)) {
159355
159668
  errors.push(parseError(index, 'Malformed system prompt. Expected: system "..." (multi-line allowed) or system file <path>.', "agent-system-malformed"));
159356
- } else if (/^(accepts|returns|tools|agents|model|max_tokens|max_depth|max_invocations|contract_retries)\b/i.test(trimmed)) {
159669
+ } else if (new RegExp(`^(accepts|returns|tools|agents|model|${Object.keys(LIMIT_DIRECTIVES).join("|")})\\b`, "i").test(trimmed)) {
159357
159670
  errors.push(parseError(index, `Malformed agent directive: '${trimmed}'.`, "agent-directive-malformed"));
159358
159671
  } else if (/^agent\b/i.test(trimmed)) {
159359
159672
  errors.push(parseError(index, `Nested agent blocks are not allowed (inside '${pending.name}').`, "agent-nested"));
@@ -159368,6 +159681,13 @@ end model`,
159368
159681
  if (pendingModel) {
159369
159682
  errors.push(parseError(pendingModel.lineNumber, `Model '${pendingModel.alias}' is missing 'end model'.`, "agent-model-end-missing"));
159370
159683
  }
159684
+ if (pendingMcp) {
159685
+ if (pendingMcp.sawDirective) {
159686
+ errors.push(parseError(pendingMcp.lineNumber, `MCP server '${pendingMcp.alias}' is missing 'end mcp'.`, "agent-mcp-unterminated"));
159687
+ } else {
159688
+ reportBareMcpAlias(pendingMcp);
159689
+ }
159690
+ }
159371
159691
  if (models.size === 0) {
159372
159692
  errors.push(parseError(0, "A .nornagent file must declare at least one model block.", "agent-model-required"));
159373
159693
  }
@@ -159379,7 +159699,199 @@ end model`,
159379
159699
  const { sourcePath: _sourcePath, ...parseValidationError } = validationError;
159380
159700
  errors.push(parseValidationError);
159381
159701
  }
159382
- return { sourcePath, models, mcpAliases, agents, errors };
159702
+ return { sourcePath, models, mcpServers, agents, errors };
159703
+ }
159704
+
159705
+ // src/nornConfig.ts
159706
+ var fs3 = __toESM(require("fs"));
159707
+ var path3 = __toESM(require("path"));
159708
+ var NORN_CONFIG_FILENAME = "norn.config.json";
159709
+ function getSearchDirectory(startPath) {
159710
+ if (!startPath) {
159711
+ return process.cwd();
159712
+ }
159713
+ const absolute = path3.resolve(startPath);
159714
+ try {
159715
+ const stats = fs3.statSync(absolute);
159716
+ return stats.isDirectory() ? absolute : path3.dirname(absolute);
159717
+ } catch {
159718
+ return path3.extname(absolute) ? path3.dirname(absolute) : absolute;
159719
+ }
159720
+ }
159721
+ function findNearestConfigFile(startPath, fileName) {
159722
+ let currentDir = getSearchDirectory(startPath);
159723
+ while (true) {
159724
+ const candidate = path3.join(currentDir, fileName);
159725
+ if (fs3.existsSync(candidate)) {
159726
+ return candidate;
159727
+ }
159728
+ const parentDir = path3.dirname(currentDir);
159729
+ if (parentDir === currentDir) {
159730
+ return void 0;
159731
+ }
159732
+ currentDir = parentDir;
159733
+ }
159734
+ }
159735
+ function parseJsonFile(filePath, validator, label) {
159736
+ let raw;
159737
+ try {
159738
+ raw = fs3.readFileSync(filePath, "utf-8");
159739
+ } catch (error2) {
159740
+ throw new Error(`Failed to read ${label}: ${error2 instanceof Error ? error2.message : String(error2)}`);
159741
+ }
159742
+ let parsed;
159743
+ try {
159744
+ parsed = JSON.parse(raw);
159745
+ } catch (error2) {
159746
+ throw new Error(`Invalid JSON in ${label}: ${error2 instanceof Error ? error2.message : String(error2)}`);
159747
+ }
159748
+ if (!validator(parsed)) {
159749
+ throw new Error(`Invalid ${label} structure`);
159750
+ }
159751
+ return parsed;
159752
+ }
159753
+ function isObjectRecord(value) {
159754
+ return !!value && typeof value === "object" && !Array.isArray(value);
159755
+ }
159756
+ function isStringRecord(value) {
159757
+ return isObjectRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
159758
+ }
159759
+ function isKnownSection(value) {
159760
+ return value === void 0 || isObjectRecord(value);
159761
+ }
159762
+ function isCommentField(value) {
159763
+ return typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string");
159764
+ }
159765
+ function isPositiveInteger(value) {
159766
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
159767
+ }
159768
+ function isNonNegativeInteger(value) {
159769
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
159770
+ }
159771
+ var AGENT_LIMIT_CONFIG_KEYS = /* @__PURE__ */ new Set([
159772
+ "_comment",
159773
+ "max_tokens",
159774
+ "max_depth",
159775
+ "max_invocations",
159776
+ "max_turns",
159777
+ "contract_retries"
159778
+ ]);
159779
+ function hasOnlyKeys(value, allowedKeys) {
159780
+ return Object.keys(value).every((key) => allowedKeys.has(key));
159781
+ }
159782
+ function hasValidNornAgentLimits(value) {
159783
+ return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
159784
+ }
159785
+ function isNornAgentProviderConfig(value) {
159786
+ if (!isObjectRecord(value)) {
159787
+ return false;
159788
+ }
159789
+ if (value._comment !== void 0 && !isCommentField(value._comment)) {
159790
+ return false;
159791
+ }
159792
+ return hasOnlyKeys(value, AGENT_LIMIT_CONFIG_KEYS) && hasValidNornAgentLimits(value);
159793
+ }
159794
+ function isNornAgentRecordingConfig(value) {
159795
+ if (!isObjectRecord(value)) {
159796
+ return false;
159797
+ }
159798
+ if (value._comment !== void 0 && !isCommentField(value._comment)) {
159799
+ return false;
159800
+ }
159801
+ return hasOnlyKeys(value, /* @__PURE__ */ new Set(["_comment", "enabled"])) && (value.enabled === void 0 || typeof value.enabled === "boolean");
159802
+ }
159803
+ var AGENT_PROVIDER_NAMES = /* @__PURE__ */ new Set(["openai", "anthropic", "google", "local"]);
159804
+ function isNornAgentsConfig(value) {
159805
+ if (value === void 0) {
159806
+ return true;
159807
+ }
159808
+ if (!isObjectRecord(value)) {
159809
+ return false;
159810
+ }
159811
+ if (value._comment !== void 0 && !isCommentField(value._comment)) {
159812
+ return false;
159813
+ }
159814
+ const agentConfigKeys = /* @__PURE__ */ new Set([...AGENT_LIMIT_CONFIG_KEYS, "providers", "recording"]);
159815
+ if (!hasOnlyKeys(value, agentConfigKeys) || !hasValidNornAgentLimits(value)) {
159816
+ return false;
159817
+ }
159818
+ if (!isNornAgentRecordingConfig(value.recording) && value.recording !== void 0) {
159819
+ return false;
159820
+ }
159821
+ const providers = value.providers;
159822
+ if (!isObjectRecord(providers)) {
159823
+ return providers === void 0;
159824
+ }
159825
+ return Object.entries(providers).every(
159826
+ ([provider, config2]) => AGENT_PROVIDER_NAMES.has(provider) && isNornAgentProviderConfig(config2)
159827
+ );
159828
+ }
159829
+ function isNornHttpConfig(value) {
159830
+ if (value === void 0) {
159831
+ return true;
159832
+ }
159833
+ if (!isObjectRecord(value)) {
159834
+ return false;
159835
+ }
159836
+ if (value._comment !== void 0 && typeof value._comment !== "string" && (!Array.isArray(value._comment) || !value._comment.every((item) => typeof item === "string"))) {
159837
+ return false;
159838
+ }
159839
+ return value.timeoutMs === void 0 || typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0;
159840
+ }
159841
+ function isNornContractsConfig(value) {
159842
+ if (value === void 0) {
159843
+ return true;
159844
+ }
159845
+ if (!isObjectRecord(value)) {
159846
+ return false;
159847
+ }
159848
+ return value.mode === void 0 || value.mode === "auto" || value.mode === "off";
159849
+ }
159850
+ function isNornProjectConfig(value) {
159851
+ if (!isObjectRecord(value)) {
159852
+ return false;
159853
+ }
159854
+ if (value.version !== 1) {
159855
+ return false;
159856
+ }
159857
+ return isNornHttpConfig(value.http) && isKnownSection(value.sql) && isKnownSection(value.mcp) && isNornContractsConfig(value.contracts) && isNornAgentsConfig(value.agents);
159858
+ }
159859
+ function loadNornConfig(startPath) {
159860
+ const filePath = findNearestConfigFile(startPath, NORN_CONFIG_FILENAME);
159861
+ if (!filePath) {
159862
+ throw new Error(`Could not find ${NORN_CONFIG_FILENAME}`);
159863
+ }
159864
+ return {
159865
+ filePath,
159866
+ config: parseJsonFile(filePath, isNornProjectConfig, NORN_CONFIG_FILENAME)
159867
+ };
159868
+ }
159869
+ function loadNornConfigSection(startPath, sectionName, validator) {
159870
+ const { filePath, config: config2 } = loadNornConfig(startPath);
159871
+ const section = config2[sectionName];
159872
+ if (section === void 0) {
159873
+ throw new Error(`Could not find ${sectionName} section in ${NORN_CONFIG_FILENAME}`);
159874
+ }
159875
+ if (!validator(section)) {
159876
+ throw new Error(`Invalid ${sectionName} section in ${NORN_CONFIG_FILENAME}`);
159877
+ }
159878
+ return { filePath, section };
159879
+ }
159880
+ function loadConfiguredMcpAliases(startPath) {
159881
+ const aliases = /* @__PURE__ */ new Set();
159882
+ try {
159883
+ const { config: config2 } = loadNornConfig(startPath);
159884
+ const mcp = config2.mcp;
159885
+ if (!isObjectRecord(mcp) || !isObjectRecord(mcp.servers)) {
159886
+ return aliases;
159887
+ }
159888
+ for (const alias of Object.keys(mcp.servers)) {
159889
+ aliases.add(alias.toLowerCase());
159890
+ }
159891
+ } catch {
159892
+ return aliases;
159893
+ }
159894
+ return aliases;
159383
159895
  }
159384
159896
 
159385
159897
  // src/pathAccess.ts
@@ -159705,7 +160217,11 @@ function extractImports(text) {
159705
160217
  }
159706
160218
  return imports;
159707
160219
  }
159708
- async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__PURE__ */ new Set(), importStack = /* @__PURE__ */ new Set(), sourceFilePath = path3.resolve(baseDir, "__norn_root__.norn"), sqlImportedPathsBySource = /* @__PURE__ */ new Map(), sqlOperationsBySource = /* @__PURE__ */ new Map(), agentImportedPathsBySource = /* @__PURE__ */ new Map(), agentDefinitionsBySource = /* @__PURE__ */ new Map()) {
160220
+ function sameMcpServerDeclaration(left, right) {
160221
+ const named = (entries) => (entries ?? []).map((entry) => `${entry.name}=${entry.valueTemplate}`).join("\0");
160222
+ return left.transportTemplate === right.transportTemplate && left.commandTemplates.join("\0") === right.commandTemplates.join("\0") && left.cwdTemplate === right.cwdTemplate && left.urlTemplate === right.urlTemplate && left.timeoutMs === right.timeoutMs && left.session === right.session && named(left.envTemplates) === named(right.envTemplates) && named(left.headerTemplates) === named(right.headerTemplates);
160223
+ }
160224
+ async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__PURE__ */ new Set(), importStack = /* @__PURE__ */ new Set(), sourceFilePath = path4.resolve(baseDir, "__norn_root__.norn"), sqlImportedPathsBySource = /* @__PURE__ */ new Map(), sqlOperationsBySource = /* @__PURE__ */ new Map(), agentImportedPathsBySource = /* @__PURE__ */ new Map(), agentDefinitionsBySource = /* @__PURE__ */ new Map(), mcpServersBySource = /* @__PURE__ */ new Map()) {
159709
160225
  const imports = extractImports(text);
159710
160226
  const errors = [];
159711
160227
  const importedContents = [];
@@ -159725,11 +160241,14 @@ async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__
159725
160241
  if (!agentDefinitionsBySource.has(sourceFilePath)) {
159726
160242
  agentDefinitionsBySource.set(sourceFilePath, /* @__PURE__ */ new Map());
159727
160243
  }
160244
+ if (!mcpServersBySource.has(sourceFilePath)) {
160245
+ mcpServersBySource.set(sourceFilePath, /* @__PURE__ */ new Map());
160246
+ }
159728
160247
  if (!agentImportedPathsBySource.has(sourceFilePath)) {
159729
160248
  agentImportedPathsBySource.set(sourceFilePath, /* @__PURE__ */ new Set());
159730
160249
  }
159731
160250
  for (const imp of imports) {
159732
- const absolutePath = path3.resolve(baseDir, imp.path);
160251
+ const absolutePath = path4.resolve(baseDir, imp.path);
159733
160252
  if (imp.path.endsWith(".nornsql")) {
159734
160253
  const importedSqlPaths = sqlImportedPathsBySource.get(sourceFilePath);
159735
160254
  if (importedSqlPaths.has(absolutePath)) {
@@ -159791,6 +160310,22 @@ async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__
159791
160310
  blocking: true
159792
160311
  });
159793
160312
  }
160313
+ const mcpScope = mcpServersBySource.get(sourceFilePath);
160314
+ for (const [alias, declaration] of parsedAgents.mcpServers) {
160315
+ const existing = mcpScope.get(alias);
160316
+ if (!existing) {
160317
+ mcpScope.set(alias, declaration);
160318
+ continue;
160319
+ }
160320
+ if (existing.sourcePath !== declaration.sourcePath && !sameMcpServerDeclaration(existing, declaration)) {
160321
+ errors.push({
160322
+ path: imp.path,
160323
+ error: `MCP alias '${declaration.alias}' is declared differently by '${existing.sourcePath}' and '${declaration.sourcePath}'. A \`run mcp\` step in this file cannot tell which one it means; rename one of them.`,
160324
+ lineNumber: imp.lineNumber,
160325
+ blocking: true
160326
+ });
160327
+ }
160328
+ }
159794
160329
  const scope = agentDefinitionsBySource.get(sourceFilePath);
159795
160330
  for (const agent of parsedAgents.agents) {
159796
160331
  const lowerName = agent.name.toLowerCase();
@@ -159863,7 +160398,7 @@ async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__
159863
160398
  }
159864
160399
  continue;
159865
160400
  }
159866
- const importDir = path3.dirname(absolutePath);
160401
+ const importDir = path4.dirname(absolutePath);
159867
160402
  const nestedResult = await resolveImports(
159868
160403
  content,
159869
160404
  importDir,
@@ -159874,7 +160409,8 @@ async function resolveImports(text, baseDir, readFile4, alreadyImported = /* @__
159874
160409
  sqlImportedPathsBySource,
159875
160410
  sqlOperationsBySource,
159876
160411
  agentImportedPathsBySource,
159877
- agentDefinitionsBySource
160412
+ agentDefinitionsBySource,
160413
+ mcpServersBySource
159878
160414
  );
159879
160415
  errors.push(...nestedResult.errors);
159880
160416
  resolvedPaths.push(...nestedResult.resolvedPaths);
@@ -159961,10 +160497,13 @@ end sequence`);
159961
160497
  }
159962
160498
  const directAgentScope = agentDefinitionsBySource.get(sourceFilePath);
159963
160499
  const directAgentImportsByPath = new Map(
159964
- imports.filter((imp) => imp.path.toLowerCase().endsWith(".nornagent")).map((imp) => [path3.resolve(baseDir, imp.path), imp])
160500
+ imports.filter((imp) => imp.path.toLowerCase().endsWith(".nornagent")).map((imp) => [path4.resolve(baseDir, imp.path), imp])
159965
160501
  );
159966
160502
  for (const validationError of validateAgentScope(directAgentScope, {
159967
- skipSameSourceChecks: true
160503
+ skipSameSourceChecks: true,
160504
+ // Resolved against the importing `.norn`, because that is the file whose nearest
160505
+ // config a run would actually consult.
160506
+ configuredMcpAliases: loadConfiguredMcpAliases(sourceFilePath)
159968
160507
  })) {
159969
160508
  const sourceImport = directAgentImportsByPath.get(validationError.sourcePath);
159970
160509
  errors.push({
@@ -159982,7 +160521,8 @@ end sequence`);
159982
160521
  endpoints,
159983
160522
  sequenceSources,
159984
160523
  sqlOperationsBySource,
159985
- agentDefinitionsBySource
160524
+ agentDefinitionsBySource,
160525
+ mcpServersBySource
159986
160526
  };
159987
160527
  }
159988
160528
  function extractSequencesFromText(text) {
@@ -166150,7 +166690,7 @@ var path27 = __toESM(require("path"));
166150
166690
 
166151
166691
  // src/scriptRunner.ts
166152
166692
  var import_child_process = require("child_process");
166153
- var path4 = __toESM(require("path"));
166693
+ var path5 = __toESM(require("path"));
166154
166694
  var pwshAvailable = null;
166155
166695
  function stripAnsiCodes(str2) {
166156
166696
  return str2.replace(/\x1B(?:\[[0-9;]*[a-zA-Z]|\][^\x07]*(?:\x07|\x1B\\)|[a-zA-Z])/g, "");
@@ -166342,7 +166882,7 @@ function isRunCommand(line2) {
166342
166882
  }
166343
166883
  async function runScript(type, scriptPath, args, workingDir, variables = {}, captureVar) {
166344
166884
  const startTime = Date.now();
166345
- const resolvedPath = path4.isAbsolute(scriptPath) ? scriptPath : path4.resolve(workingDir, scriptPath);
166885
+ const resolvedPath = path5.isAbsolute(scriptPath) ? scriptPath : path5.resolve(workingDir, scriptPath);
166346
166886
  let command;
166347
166887
  let cmdArgs;
166348
166888
  switch (type) {
@@ -166939,8 +167479,8 @@ function substituteAssertionDisplayTemplates(expression, variables) {
166939
167479
  }
166940
167480
 
166941
167481
  // src/jsonFileReader.ts
166942
- var fs3 = __toESM(require("fs"));
166943
- var path5 = __toESM(require("path"));
167482
+ var fs4 = __toESM(require("fs"));
167483
+ var path6 = __toESM(require("path"));
166944
167484
  function isJsonCommand(line2) {
166945
167485
  const trimmed = line2.trim();
166946
167486
  return /^var\s+[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*run\s+readJson\s+/i.test(trimmed);
@@ -166961,16 +167501,16 @@ function parseJsonCommand(line2) {
166961
167501
  };
166962
167502
  }
166963
167503
  function readJsonFile(filePath, workingDir) {
166964
- const resolvedPath = path5.isAbsolute(filePath) ? filePath : path5.resolve(workingDir || process.cwd(), filePath);
167504
+ const resolvedPath = path6.isAbsolute(filePath) ? filePath : path6.resolve(workingDir || process.cwd(), filePath);
166965
167505
  try {
166966
- if (!fs3.existsSync(resolvedPath)) {
167506
+ if (!fs4.existsSync(resolvedPath)) {
166967
167507
  return {
166968
167508
  success: false,
166969
167509
  error: `File not found: ${resolvedPath}`,
166970
167510
  filePath: resolvedPath
166971
167511
  };
166972
167512
  }
166973
- const content = fs3.readFileSync(resolvedPath, "utf-8");
167513
+ const content = fs4.readFileSync(resolvedPath, "utf-8");
166974
167514
  const data = JSON.parse(content);
166975
167515
  return {
166976
167516
  success: true,
@@ -167278,181 +167818,6 @@ function validatePreparedRequest(parsed, context) {
167278
167818
  // src/sqlConfig.ts
167279
167819
  var path9 = __toESM(require("path"));
167280
167820
 
167281
- // src/nornConfig.ts
167282
- var fs4 = __toESM(require("fs"));
167283
- var path6 = __toESM(require("path"));
167284
- var NORN_CONFIG_FILENAME = "norn.config.json";
167285
- function getSearchDirectory(startPath) {
167286
- if (!startPath) {
167287
- return process.cwd();
167288
- }
167289
- const absolute = path6.resolve(startPath);
167290
- try {
167291
- const stats = fs4.statSync(absolute);
167292
- return stats.isDirectory() ? absolute : path6.dirname(absolute);
167293
- } catch {
167294
- return path6.extname(absolute) ? path6.dirname(absolute) : absolute;
167295
- }
167296
- }
167297
- function findNearestConfigFile(startPath, fileName) {
167298
- let currentDir = getSearchDirectory(startPath);
167299
- while (true) {
167300
- const candidate = path6.join(currentDir, fileName);
167301
- if (fs4.existsSync(candidate)) {
167302
- return candidate;
167303
- }
167304
- const parentDir = path6.dirname(currentDir);
167305
- if (parentDir === currentDir) {
167306
- return void 0;
167307
- }
167308
- currentDir = parentDir;
167309
- }
167310
- }
167311
- function parseJsonFile(filePath, validator, label) {
167312
- let raw;
167313
- try {
167314
- raw = fs4.readFileSync(filePath, "utf-8");
167315
- } catch (error2) {
167316
- throw new Error(`Failed to read ${label}: ${error2 instanceof Error ? error2.message : String(error2)}`);
167317
- }
167318
- let parsed;
167319
- try {
167320
- parsed = JSON.parse(raw);
167321
- } catch (error2) {
167322
- throw new Error(`Invalid JSON in ${label}: ${error2 instanceof Error ? error2.message : String(error2)}`);
167323
- }
167324
- if (!validator(parsed)) {
167325
- throw new Error(`Invalid ${label} structure`);
167326
- }
167327
- return parsed;
167328
- }
167329
- function isObjectRecord(value) {
167330
- return !!value && typeof value === "object" && !Array.isArray(value);
167331
- }
167332
- function isStringRecord(value) {
167333
- return isObjectRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
167334
- }
167335
- function isKnownSection(value) {
167336
- return value === void 0 || isObjectRecord(value);
167337
- }
167338
- function isCommentField(value) {
167339
- return typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string");
167340
- }
167341
- function isPositiveInteger(value) {
167342
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
167343
- }
167344
- function isNonNegativeInteger(value) {
167345
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
167346
- }
167347
- var AGENT_LIMIT_CONFIG_KEYS = /* @__PURE__ */ new Set([
167348
- "_comment",
167349
- "max_tokens",
167350
- "max_depth",
167351
- "max_invocations",
167352
- "contract_retries"
167353
- ]);
167354
- function hasOnlyKeys(value, allowedKeys) {
167355
- return Object.keys(value).every((key) => allowedKeys.has(key));
167356
- }
167357
- function hasValidNornAgentLimits(value) {
167358
- return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
167359
- }
167360
- function isNornAgentProviderConfig(value) {
167361
- if (!isObjectRecord(value)) {
167362
- return false;
167363
- }
167364
- if (value._comment !== void 0 && !isCommentField(value._comment)) {
167365
- return false;
167366
- }
167367
- return hasOnlyKeys(value, AGENT_LIMIT_CONFIG_KEYS) && hasValidNornAgentLimits(value);
167368
- }
167369
- function isNornAgentRecordingConfig(value) {
167370
- if (!isObjectRecord(value)) {
167371
- return false;
167372
- }
167373
- if (value._comment !== void 0 && !isCommentField(value._comment)) {
167374
- return false;
167375
- }
167376
- return hasOnlyKeys(value, /* @__PURE__ */ new Set(["_comment", "enabled"])) && (value.enabled === void 0 || typeof value.enabled === "boolean");
167377
- }
167378
- var AGENT_PROVIDER_NAMES = /* @__PURE__ */ new Set(["openai", "anthropic", "google", "local"]);
167379
- function isNornAgentsConfig(value) {
167380
- if (value === void 0) {
167381
- return true;
167382
- }
167383
- if (!isObjectRecord(value)) {
167384
- return false;
167385
- }
167386
- if (value._comment !== void 0 && !isCommentField(value._comment)) {
167387
- return false;
167388
- }
167389
- const agentConfigKeys = /* @__PURE__ */ new Set([...AGENT_LIMIT_CONFIG_KEYS, "providers", "recording"]);
167390
- if (!hasOnlyKeys(value, agentConfigKeys) || !hasValidNornAgentLimits(value)) {
167391
- return false;
167392
- }
167393
- if (!isNornAgentRecordingConfig(value.recording) && value.recording !== void 0) {
167394
- return false;
167395
- }
167396
- const providers = value.providers;
167397
- if (!isObjectRecord(providers)) {
167398
- return providers === void 0;
167399
- }
167400
- return Object.entries(providers).every(
167401
- ([provider, config2]) => AGENT_PROVIDER_NAMES.has(provider) && isNornAgentProviderConfig(config2)
167402
- );
167403
- }
167404
- function isNornHttpConfig(value) {
167405
- if (value === void 0) {
167406
- return true;
167407
- }
167408
- if (!isObjectRecord(value)) {
167409
- return false;
167410
- }
167411
- if (value._comment !== void 0 && typeof value._comment !== "string" && (!Array.isArray(value._comment) || !value._comment.every((item) => typeof item === "string"))) {
167412
- return false;
167413
- }
167414
- return value.timeoutMs === void 0 || typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0;
167415
- }
167416
- function isNornContractsConfig(value) {
167417
- if (value === void 0) {
167418
- return true;
167419
- }
167420
- if (!isObjectRecord(value)) {
167421
- return false;
167422
- }
167423
- return value.mode === void 0 || value.mode === "auto" || value.mode === "off";
167424
- }
167425
- function isNornProjectConfig(value) {
167426
- if (!isObjectRecord(value)) {
167427
- return false;
167428
- }
167429
- if (value.version !== 1) {
167430
- return false;
167431
- }
167432
- return isNornHttpConfig(value.http) && isKnownSection(value.sql) && isKnownSection(value.mcp) && isNornContractsConfig(value.contracts) && isNornAgentsConfig(value.agents);
167433
- }
167434
- function loadNornConfig(startPath) {
167435
- const filePath = findNearestConfigFile(startPath, NORN_CONFIG_FILENAME);
167436
- if (!filePath) {
167437
- throw new Error(`Could not find ${NORN_CONFIG_FILENAME}`);
167438
- }
167439
- return {
167440
- filePath,
167441
- config: parseJsonFile(filePath, isNornProjectConfig, NORN_CONFIG_FILENAME)
167442
- };
167443
- }
167444
- function loadNornConfigSection(startPath, sectionName, validator) {
167445
- const { filePath, config: config2 } = loadNornConfig(startPath);
167446
- const section = config2[sectionName];
167447
- if (section === void 0) {
167448
- throw new Error(`Could not find ${sectionName} section in ${NORN_CONFIG_FILENAME}`);
167449
- }
167450
- if (!validator(section)) {
167451
- throw new Error(`Invalid ${sectionName} section in ${NORN_CONFIG_FILENAME}`);
167452
- }
167453
- return { filePath, section };
167454
- }
167455
-
167456
167821
  // src/sqlBuiltInAdapters.ts
167457
167822
  var fs10 = __toESM(require("fs"));
167458
167823
  var path8 = __toESM(require("path"));
@@ -184000,6 +184365,9 @@ var StreamableHTTPClientTransport = class {
184000
184365
  }
184001
184366
  };
184002
184367
 
184368
+ // src/mcpServerScope.ts
184369
+ var crypto7 = __toESM(require("crypto"));
184370
+
184003
184371
  // src/mcpConfig.ts
184004
184372
  var path10 = __toESM(require("path"));
184005
184373
  function isRawMcpStdioServerConfig(value) {
@@ -184055,8 +184423,8 @@ function resolveTemplateString(value, envVariables) {
184055
184423
  const scopedVariables = attachEnvironmentScope({}, envVariables);
184056
184424
  return substituteVariables(value, scopedVariables);
184057
184425
  }
184058
- function resolveCommandParts2(configPath, command, envVariables) {
184059
- const configDir = path10.dirname(configPath);
184426
+ function resolveCommandParts2(originPath, command, envVariables) {
184427
+ const configDir = path10.dirname(originPath);
184060
184428
  return command.map((part, index) => {
184061
184429
  const resolvedPart = resolveTemplateString(part, envVariables);
184062
184430
  if (!resolvedPart) {
@@ -184140,11 +184508,117 @@ function resolveMcpServer(startPath, alias, envVariables) {
184140
184508
  const { filePath, config: config2 } = loadResolvedNornMcpProjectConfig(startPath, envVariables);
184141
184509
  const server = config2.servers[alias];
184142
184510
  if (!server) {
184143
- throw new Error(`Server alias '${alias}' was not found in ${NORN_CONFIG_FILENAME} mcp.servers`);
184511
+ throw new Error(
184512
+ `Server alias '${alias}' was not declared by an 'mcp ${alias} ... end mcp' block in the .nornagent sidecar and was not found in ${NORN_CONFIG_FILENAME} mcp.servers`
184513
+ );
184144
184514
  }
184145
184515
  return { filePath, server };
184146
184516
  }
184147
184517
 
184518
+ // src/mcpServerScope.ts
184519
+ function getMcpOriginPath(origin2) {
184520
+ return origin2.kind === "sidecar" ? origin2.sourcePath : origin2.filePath;
184521
+ }
184522
+ function resolveTemplate(template, declaration, directive, envVariables) {
184523
+ const scoped = attachEnvironmentScope({}, envVariables ?? {});
184524
+ const resolved = substituteVariables(template, scoped).replace(/\{\{\$env\.([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g, (match2, name) => {
184525
+ const value = process.env[name];
184526
+ return value !== void 0 && value.trim() !== "" ? value : match2;
184527
+ }).trim();
184528
+ const unresolved = resolved.match(/\{\{([^}]+)\}\}/);
184529
+ if (unresolved) {
184530
+ throw new Error(
184531
+ `Could not resolve ${directive} for MCP server '${declaration.alias}': '${unresolved[1].trim()}' is not defined. Add it to .nornenv (select it with --env) or export it in the process environment.`
184532
+ );
184533
+ }
184534
+ return resolved;
184535
+ }
184536
+ function resolveNamedTemplates(entries, declaration, directive, envVariables) {
184537
+ if (!entries || entries.length === 0) {
184538
+ return void 0;
184539
+ }
184540
+ const resolved = {};
184541
+ for (const entry of entries) {
184542
+ resolved[entry.name] = resolveTemplate(
184543
+ entry.valueTemplate,
184544
+ declaration,
184545
+ `${directive} ${entry.name}`,
184546
+ envVariables
184547
+ );
184548
+ }
184549
+ return resolved;
184550
+ }
184551
+ function resolveMcpServerDeclaration(declaration, envVariables) {
184552
+ const transport = resolveTemplate(declaration.transportTemplate, declaration, "transport", envVariables).toLowerCase();
184553
+ if (transport === "stdio") {
184554
+ const command = declaration.commandTemplates.map(
184555
+ (part) => resolveTemplate(part, declaration, "command", envVariables)
184556
+ );
184557
+ return {
184558
+ transport: "stdio",
184559
+ // Reuse the config path's own resolution so a bare name stays a PATH lookup and a
184560
+ // relative one resolves against the declaring file — here, the sidecar.
184561
+ command: resolveCommandParts2(declaration.sourcePath, command),
184562
+ cwd: resolveRelativeDirectory(
184563
+ declaration.sourcePath,
184564
+ declaration.cwdTemplate === void 0 ? void 0 : resolveTemplate(declaration.cwdTemplate, declaration, "cwd", envVariables)
184565
+ ),
184566
+ env: resolveNamedTemplates(declaration.envTemplates, declaration, "env", envVariables)
184567
+ };
184568
+ }
184569
+ if (transport === "http") {
184570
+ return {
184571
+ transport: "http",
184572
+ url: resolveTemplate(declaration.urlTemplate ?? "", declaration, "url", envVariables),
184573
+ headers: resolveNamedTemplates(declaration.headerTemplates, declaration, "header", envVariables),
184574
+ timeoutMs: declaration.timeoutMs
184575
+ };
184576
+ }
184577
+ throw new Error(
184578
+ `MCP server '${declaration.alias}' resolved transport '${transport}'. Expected stdio or http.`
184579
+ );
184580
+ }
184581
+ function resolveScopeKey(session, options) {
184582
+ if (session === "run") {
184583
+ return "";
184584
+ }
184585
+ const invocationId = options.invocationId;
184586
+ if (!invocationId) {
184587
+ return "";
184588
+ }
184589
+ if (session === "agent") {
184590
+ return invocationId;
184591
+ }
184592
+ return options.callIndex === void 0 ? invocationId : `${invocationId}#${options.callIndex}`;
184593
+ }
184594
+ function resolveMcpTarget(alias, options) {
184595
+ const declaration = options.scope?.get(alias.toLowerCase());
184596
+ const { server, origin: origin2, session } = declaration ? {
184597
+ server: resolveMcpServerDeclaration(declaration, options.envVariables),
184598
+ origin: { kind: "sidecar", sourcePath: declaration.sourcePath },
184599
+ session: declaration.session
184600
+ } : (() => {
184601
+ const resolved = resolveMcpServer(options.startPath, alias, options.envVariables);
184602
+ return {
184603
+ server: resolved.server,
184604
+ origin: { kind: "config", filePath: resolved.filePath },
184605
+ // A config alias has no `session` directive, and inventing a config key for it
184606
+ // belongs to whoever needs it, not here.
184607
+ session: "run"
184608
+ };
184609
+ })();
184610
+ const scopeKey = resolveScopeKey(session, options);
184611
+ const configHash = crypto7.createHash("sha256").update(JSON.stringify(server)).digest("hex").slice(0, 16);
184612
+ return {
184613
+ alias,
184614
+ sessionKey: `${getMcpOriginPath(origin2)}::${alias.toLowerCase()}::${configHash}::${scopeKey}`,
184615
+ server,
184616
+ origin: origin2,
184617
+ session,
184618
+ scopeKey
184619
+ };
184620
+ }
184621
+
184148
184622
  // src/mcpToolIntellisenseCache.ts
184149
184623
  var path12 = __toESM(require("path"));
184150
184624
 
@@ -184254,11 +184728,11 @@ function cloneSerializableValue(value) {
184254
184728
  // src/mcpToolIntellisenseCache.ts
184255
184729
  var CACHE_VERSION = 1;
184256
184730
  var CACHE_FILE = "mcp-tool-intellisense.json";
184257
- function getCachePathForConfig(configPath) {
184258
- return getNornCacheFilePath(path12.dirname(configPath), CACHE_FILE);
184731
+ function getCachePathForConfig(originPath) {
184732
+ return getNornCacheFilePath(path12.dirname(originPath), CACHE_FILE);
184259
184733
  }
184260
- function ensureCacheDirForConfig(configPath) {
184261
- return !!ensureNornCacheDir(path12.dirname(configPath));
184734
+ function ensureCacheDirForConfig(originPath) {
184735
+ return !!ensureNornCacheDir(path12.dirname(originPath));
184262
184736
  }
184263
184737
  function loadCacheForConfig(configPath) {
184264
184738
  return loadVersionedJsonCache({
@@ -184278,14 +184752,14 @@ function toCachedMcpTool(tool) {
184278
184752
  inputSchema: cloneSerializableValue(tool.inputSchema)
184279
184753
  };
184280
184754
  }
184281
- function saveMcpToolsForAlias(configPath, alias, tools) {
184282
- const cache = loadCacheForConfig(configPath);
184755
+ function saveMcpToolsForAlias(originPath, alias, tools) {
184756
+ const cache = loadCacheForConfig(originPath);
184283
184757
  cache.servers[alias.toLowerCase()] = {
184284
184758
  alias,
184285
184759
  cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
184286
184760
  tools: tools.map((tool) => toCachedMcpTool(tool))
184287
184761
  };
184288
- saveCacheForConfig(configPath, cache);
184762
+ saveCacheForConfig(originPath, cache);
184289
184763
  }
184290
184764
 
184291
184765
  // src/mcpClient.ts
@@ -184338,21 +184812,53 @@ function normalizeStructuredContent(value) {
184338
184812
  }
184339
184813
  var McpSessionManager = class {
184340
184814
  sessions = /* @__PURE__ */ new Map();
184341
- resolveSessionTarget(startPath, alias, envVariables) {
184342
- const { filePath, server } = resolveMcpServer(startPath, alias, envVariables);
184343
- return {
184344
- sessionKey: `${filePath}::${alias.toLowerCase()}`,
184345
- server
184346
- };
184815
+ /**
184816
+ * Say what could not be reached, and which of the two things it was.
184817
+ *
184818
+ * "fetch failed" is what the SDK gives back, and it tells the reader nothing they can act
184819
+ * on. Norn's job here ends at connecting, so a failure to connect must at least name the
184820
+ * target it dialled — and, for http, that Norn was never going to start it.
184821
+ */
184822
+ describeConnectFailure(target, error2) {
184823
+ const reason = error2 instanceof Error ? error2.message : String(error2);
184824
+ if (target.server.transport === "http") {
184825
+ return new Error(
184826
+ `Could not connect to MCP server '${target.alias}' at ${target.server.url}: ${reason}. Norn dials this endpoint; it never starts the server, so it must already be listening.`
184827
+ );
184828
+ }
184829
+ return new Error(
184830
+ `Could not start MCP server '${target.alias}' with command '${target.server.command[0]}': ${reason}.`
184831
+ );
184347
184832
  }
184348
- async createSession(server, sessionKey) {
184833
+ async createSession(target) {
184349
184834
  const client = createClient();
184835
+ const server = target.server;
184836
+ try {
184837
+ await this.connect(client, server);
184838
+ } catch (error2) {
184839
+ await Promise.allSettled([client.close()]);
184840
+ throw this.describeConnectFailure(target, error2);
184841
+ }
184842
+ const session = {
184843
+ client,
184844
+ config: server,
184845
+ toolsByName: /* @__PURE__ */ new Map(),
184846
+ scopeKey: target.scopeKey
184847
+ };
184848
+ this.sessions.set(target.sessionKey, session);
184849
+ return session;
184850
+ }
184851
+ async connect(client, server) {
184350
184852
  if (server.transport === "stdio") {
184351
184853
  const [command, ...args] = server.command;
184352
184854
  const transport = new StdioClientTransport({
184353
184855
  command,
184354
184856
  args,
184355
- cwd: server.cwd
184857
+ cwd: server.cwd,
184858
+ // The SDK's `env` option *replaces* the child environment, so a declared `env`
184859
+ // that is not merged over the default one drops PATH and the server never
184860
+ // launches. Declared values win over the inherited ones.
184861
+ ...server.env ? { env: { ...getDefaultEnvironment(), ...server.env } } : {}
184356
184862
  });
184357
184863
  await client.connect(transport);
184358
184864
  } else {
@@ -184361,24 +184867,41 @@ var McpSessionManager = class {
184361
184867
  });
184362
184868
  await client.connect(transport, server.timeoutMs ? { timeout: server.timeoutMs } : void 0);
184363
184869
  }
184364
- const session = {
184365
- client,
184366
- config: server,
184367
- toolsByName: /* @__PURE__ */ new Map()
184368
- };
184369
- this.sessions.set(sessionKey, session);
184370
- return session;
184371
184870
  }
184372
- async getSession(startPath, alias, envVariables) {
184373
- const target = this.resolveSessionTarget(startPath, alias, envVariables);
184871
+ async getSession(target) {
184374
184872
  const existing = this.sessions.get(target.sessionKey);
184375
184873
  if (existing) {
184376
184874
  return existing;
184377
184875
  }
184378
- return this.createSession(target.server, target.sessionKey);
184876
+ return this.createSession(target);
184877
+ }
184878
+ /**
184879
+ * Run one operation against a target's session, closing it afterwards when the server
184880
+ * declared `session call`.
184881
+ *
184882
+ * Every public entry point goes through this and every internal step takes the open
184883
+ * session, so a call-scoped server opens and closes exactly once per operation rather than
184884
+ * having its session pulled out from under the call that opened it.
184885
+ */
184886
+ async withSession(target, operation) {
184887
+ const session = await this.getSession(target);
184888
+ try {
184889
+ return await operation(session);
184890
+ } finally {
184891
+ if (target.session === "call") {
184892
+ await this.closeSession(target.sessionKey);
184893
+ }
184894
+ }
184895
+ }
184896
+ async closeSession(sessionKey) {
184897
+ const session = this.sessions.get(sessionKey);
184898
+ if (!session) {
184899
+ return;
184900
+ }
184901
+ this.sessions.delete(sessionKey);
184902
+ await Promise.allSettled([session.client.close()]);
184379
184903
  }
184380
- async listTools(startPath, alias, envVariables) {
184381
- const session = await this.getSession(startPath, alias, envVariables);
184904
+ async listToolsForSession(target, session) {
184382
184905
  const tools = [];
184383
184906
  let cursor;
184384
184907
  do {
@@ -184392,44 +184915,74 @@ var McpSessionManager = class {
184392
184915
  }
184393
184916
  cursor = response.nextCursor;
184394
184917
  } while (cursor);
184395
- const { filePath } = resolveMcpServer(startPath, alias, envVariables);
184396
- saveMcpToolsForAlias(filePath, alias, tools);
184918
+ saveMcpToolsForAlias(getMcpOriginPath(target.origin), target.alias, tools);
184397
184919
  return { tools };
184398
184920
  }
184399
- async getToolDefinition(startPath, alias, toolName2, envVariables) {
184400
- const session = await this.getSession(startPath, alias, envVariables);
184921
+ async getToolDefinitionForSession(target, session, toolName2) {
184401
184922
  const normalizedToolName = toolName2.toLowerCase();
184402
184923
  const cached2 = session.toolsByName.get(normalizedToolName);
184403
184924
  if (cached2) {
184404
184925
  return cached2;
184405
184926
  }
184406
- const listed = await this.listTools(startPath, alias, envVariables);
184927
+ const listed = await this.listToolsForSession(target, session);
184407
184928
  const resolved = listed.tools.find((tool) => tool.name.toLowerCase() === normalizedToolName);
184408
184929
  if (!resolved) {
184409
- throw new Error(`Tool '${toolName2}' was not found on MCP server '${alias}'.`);
184930
+ throw new Error(`Tool '${toolName2}' was not found on MCP server '${target.alias}'.`);
184410
184931
  }
184411
184932
  return resolved;
184412
184933
  }
184413
- async callTool(startPath, alias, toolName2, args, envVariables) {
184414
- const session = await this.getSession(startPath, alias, envVariables);
184415
- await this.getToolDefinition(startPath, alias, toolName2, envVariables);
184416
- const result = await session.client.callTool(
184417
- {
184418
- name: toolName2,
184419
- arguments: args
184420
- },
184421
- void 0,
184422
- session.config.transport === "http" && session.config.timeoutMs ? { timeout: session.config.timeoutMs } : void 0
184423
- );
184424
- const content = result.content || [];
184425
- return {
184426
- content,
184427
- structuredContent: normalizeStructuredContent(result.structuredContent),
184428
- isError: Boolean(result.isError),
184429
- text: extractTextContent(content),
184430
- server: alias,
184431
- tool: toolName2
184432
- };
184934
+ async listTools(target) {
184935
+ return this.withSession(target, (session) => this.listToolsForSession(target, session));
184936
+ }
184937
+ async getToolDefinition(target, toolName2) {
184938
+ return this.withSession(target, (session) => this.getToolDefinitionForSession(target, session, toolName2));
184939
+ }
184940
+ async callTool(target, toolName2, args) {
184941
+ return this.withSession(target, async (session) => {
184942
+ await this.getToolDefinitionForSession(target, session, toolName2);
184943
+ const result = await session.client.callTool(
184944
+ {
184945
+ name: toolName2,
184946
+ arguments: args
184947
+ },
184948
+ void 0,
184949
+ session.config.transport === "http" && session.config.timeoutMs ? { timeout: session.config.timeoutMs } : void 0
184950
+ );
184951
+ const content = result.content || [];
184952
+ return {
184953
+ content,
184954
+ structuredContent: normalizeStructuredContent(result.structuredContent),
184955
+ isError: Boolean(result.isError),
184956
+ text: extractTextContent(content),
184957
+ server: target.alias,
184958
+ tool: toolName2
184959
+ };
184960
+ });
184961
+ }
184962
+ /**
184963
+ * Close every session opened at one scope boundary.
184964
+ *
184965
+ * This is what stops a twelve-agent run from leaving twelve browsers running: an
184966
+ * `agent`-scoped session belongs to its hop and must die with it, including when the hop
184967
+ * threw. Run-scoped sessions carry an empty scope key and are untouched.
184968
+ */
184969
+ async closeScope(scopeKey) {
184970
+ if (!scopeKey) {
184971
+ return;
184972
+ }
184973
+ const closing = [];
184974
+ for (const [sessionKey, session] of [...this.sessions]) {
184975
+ if (session.scopeKey !== scopeKey && !session.scopeKey.startsWith(`${scopeKey}#`)) {
184976
+ continue;
184977
+ }
184978
+ this.sessions.delete(sessionKey);
184979
+ closing.push(session.client.close());
184980
+ }
184981
+ await Promise.allSettled(closing);
184982
+ }
184983
+ /** How many sessions are open. Exposed so lifetime is provable by count, not inspection. */
184984
+ get openSessionCount() {
184985
+ return this.sessions.size;
184433
184986
  }
184434
184987
  async closeAll() {
184435
184988
  const sessions = Array.from(this.sessions.values());
@@ -185261,6 +185814,50 @@ function parseJudgeStatement(line2, lineNumber = 0, options = {}) {
185261
185814
  })));
185262
185815
  }
185263
185816
 
185817
+ // src/agents/mcpServerGrants.ts
185818
+ async function expandAgentMcpServerGrants(agent, listToolNames) {
185819
+ const references = [];
185820
+ const grants = [];
185821
+ for (const grant of agent.grantedMcpServers ?? []) {
185822
+ const toolNames = await listToolNames(grant.alias, grant.lineNumber);
185823
+ if (toolNames.length === 0) {
185824
+ throw new Error(
185825
+ `MCP server '${grant.alias}' granted to agent '${agent.name}' advertises no tools. A whole-server grant must offer at least one tool; check the server is the one you meant, or grant named tools instead.`
185826
+ );
185827
+ }
185828
+ grants.push({ alias: grant.alias, toolNames: [...toolNames] });
185829
+ for (const toolName2 of toolNames) {
185830
+ references.push({
185831
+ name: `${grant.alias}.${toolName2}`,
185832
+ mcpAlias: grant.alias,
185833
+ toolName: toolName2,
185834
+ lineNumber: grant.lineNumber
185835
+ });
185836
+ }
185837
+ }
185838
+ return { references, grants };
185839
+ }
185840
+ function recordedGrantToolNames(grants, alias) {
185841
+ return grants?.find((grant) => grant.alias.toLowerCase() === alias.toLowerCase())?.toolNames;
185842
+ }
185843
+ function describeGrantToolDrift(alias, recorded, current) {
185844
+ const before = new Set(recorded.map((name) => name.toLowerCase()));
185845
+ const after = new Set(current.map((name) => name.toLowerCase()));
185846
+ const added = current.filter((name) => !before.has(name.toLowerCase()));
185847
+ const removed = recorded.filter((name) => !after.has(name.toLowerCase()));
185848
+ if (added.length === 0 && removed.length === 0) {
185849
+ return void 0;
185850
+ }
185851
+ const parts = [];
185852
+ if (added.length > 0) {
185853
+ parts.push(`added ${added.join(", ")}`);
185854
+ }
185855
+ if (removed.length > 0) {
185856
+ parts.push(`removed ${removed.join(", ")}`);
185857
+ }
185858
+ return `MCP server '${alias}' ${parts.join("; ")} since this run was recorded`;
185859
+ }
185860
+
185264
185861
  // src/agents/judgeContract.ts
185265
185862
  var JUDGE_RESULT_SCHEMA_PATH = "norn:judge-result";
185266
185863
  var JUDGE_INPUT_SCHEMA_PATH = "norn:judge-input";
@@ -185325,6 +185922,9 @@ function findJudgeAgentConflicts(agent) {
185325
185922
  if (agent.tools.length > 0) {
185326
185923
  conflicts.push(`declares tools (${agent.tools.map((tool) => tool.name).join(", ")})`);
185327
185924
  }
185925
+ if (agent.grantedMcpServers && agent.grantedMcpServers.length > 0) {
185926
+ conflicts.push(`grants MCP servers (${agent.grantedMcpServers.map((grant) => grant.alias).join(", ")})`);
185927
+ }
185328
185928
  if (agent.callableAgents.length > 0) {
185329
185929
  conflicts.push(`declares agents (${agent.callableAgents.map((reference) => reference.name).join(", ")})`);
185330
185930
  }
@@ -185338,6 +185938,7 @@ function buildJudgeDefinition(agent) {
185338
185938
 
185339
185939
  ${JUDGE_FRAMING_PROMPT}` : JUDGE_FRAMING_PROMPT,
185340
185940
  tools: [],
185941
+ grantedMcpServers: void 0,
185341
185942
  callableAgents: [],
185342
185943
  accepts: void 0,
185343
185944
  returns: void 0,
@@ -185430,7 +186031,16 @@ function describeUnmetExpectations(verdict) {
185430
186031
  var import_crypto6 = require("crypto");
185431
186032
  var PROVIDER_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
185432
186033
  var PROVIDER_NAME_MAX_LENGTH = 64;
185433
- var MAX_AGENT_PROVIDER_TURNS = 10;
186034
+ var AgentTurnLimitError = class extends Error {
186035
+ constructor(provider, maxTurns, toolCallCount, lastToolName) {
186036
+ super(`${provider} tool loop reached its ${maxTurns}-turn limit before the model finished.`);
186037
+ this.provider = provider;
186038
+ this.maxTurns = maxTurns;
186039
+ this.toolCallCount = toolCallCount;
186040
+ this.lastToolName = lastToolName;
186041
+ this.name = "AgentTurnLimitError";
186042
+ }
186043
+ };
185434
186044
  var AgentProviderRefusalError = class extends Error {
185435
186045
  constructor(message, rawText) {
185436
186046
  super(message);
@@ -185483,6 +186093,10 @@ function isAgentProviderMaxTokensRejection(error2) {
185483
186093
  function formatAgentProviderMaxTokensError(agentName, error2) {
185484
186094
  return `Agent '${agentName}' using provider '${error2.provider}' rejected configured max_tokens value ${error2.maxTokens}. Lower max_tokens for that agent or provider in norn.config.json. Provider response: ${error2.providerMessage}`;
185485
186095
  }
186096
+ function formatAgentTurnLimitError(agentName, outcome) {
186097
+ const lastTool = outcome.lastToolName ? `, last calling '${outcome.lastToolName}'` : "";
186098
+ return `Agent '${agentName}' reached its ${outcome.maxTurns}-turn limit after ${outcome.toolCallCount} tool call${outcome.toolCallCount === 1 ? "" : "s"}${lastTool}, and stopped before producing a final answer. Raise max_turns for this agent in ${outcome.sourcePath}:${outcome.lineNumber + 1}, or for every agent under 'agents' in norn.config.json. Each turn is a paid model call.`;
186099
+ }
185486
186100
  function agentInputToText(input2) {
185487
186101
  if (typeof input2 === "string") {
185488
186102
  return input2;
@@ -185712,7 +186326,7 @@ var AnthropicAgentAdapter = class {
185712
186326
  tool_choice: { type: "auto", disable_parallel_tool_use: true },
185713
186327
  output_config: outputConfig,
185714
186328
  thinking,
185715
- max_iterations: MAX_AGENT_PROVIDER_TURNS,
186329
+ max_iterations: request.maxTurns,
185716
186330
  stream: false
185717
186331
  });
185718
186332
  let latest;
@@ -185742,7 +186356,12 @@ var AnthropicAgentAdapter = class {
185742
186356
  throw new Error(`Anthropic model reached max_tokens (${request.maxTokens}) before completing the response.`);
185743
186357
  }
185744
186358
  if (finalMessage.stop_reason === "tool_use") {
185745
- throw new Error(`Anthropic tool loop exceeded ${MAX_AGENT_PROVIDER_TURNS} model turns.`);
186359
+ throw new AgentTurnLimitError(
186360
+ this.provider,
186361
+ request.maxTurns,
186362
+ toolCalls.length,
186363
+ toolCalls[toolCalls.length - 1]?.name
186364
+ );
185746
186365
  }
185747
186366
  if (finalMessage.stop_reason === "model_context_window_exceeded") {
185748
186367
  throw new Error("Anthropic model exceeded its context window before completing the response.");
@@ -185961,7 +186580,7 @@ var GoogleAgentAdapter = class {
185961
186580
  let inputTokens = 0;
185962
186581
  let outputTokens = 0;
185963
186582
  let resolvedModelId2 = request.modelId;
185964
- for (let turn = 0; turn < MAX_AGENT_PROVIDER_TURNS; turn++) {
186583
+ for (let turn = 0; turn < request.maxTurns; turn++) {
185965
186584
  const response = await client.models.generateContent({
185966
186585
  model: request.modelId,
185967
186586
  contents,
@@ -185993,8 +186612,13 @@ var GoogleAgentAdapter = class {
185993
186612
  "Google Gemini requested multiple tools in one model turn; parallel/fan-out tool calls are not supported."
185994
186613
  );
185995
186614
  }
185996
- if (turn === MAX_AGENT_PROVIDER_TURNS - 1) {
185997
- throw new Error(`Google Gemini tool loop exceeded ${MAX_AGENT_PROVIDER_TURNS} model turns.`);
186615
+ if (turn === request.maxTurns - 1) {
186616
+ throw new AgentTurnLimitError(
186617
+ this.provider,
186618
+ request.maxTurns,
186619
+ toolCalls.length,
186620
+ toolCalls[toolCalls.length - 1]?.name
186621
+ );
185998
186622
  }
185999
186623
  const modelContent = getCandidate(response)?.content;
186000
186624
  if (!modelContent) {
@@ -186036,7 +186660,12 @@ var GoogleAgentAdapter = class {
186036
186660
  }]
186037
186661
  });
186038
186662
  }
186039
- throw new Error(`Google Gemini tool loop exceeded ${MAX_AGENT_PROVIDER_TURNS} model turns.`);
186663
+ throw new AgentTurnLimitError(
186664
+ this.provider,
186665
+ request.maxTurns,
186666
+ toolCalls.length,
186667
+ toolCalls[toolCalls.length - 1]?.name
186668
+ );
186040
186669
  }
186041
186670
  };
186042
186671
 
@@ -186060,13 +186689,13 @@ function __classPrivateFieldGet3(receiver, state, kind, f3) {
186060
186689
 
186061
186690
  // node_modules/openai/internal/utils/uuid.mjs
186062
186691
  var uuid43 = function() {
186063
- const { crypto: crypto9 } = globalThis;
186064
- if (crypto9?.randomUUID) {
186065
- uuid43 = crypto9.randomUUID.bind(crypto9);
186066
- return crypto9.randomUUID();
186692
+ const { crypto: crypto10 } = globalThis;
186693
+ if (crypto10?.randomUUID) {
186694
+ uuid43 = crypto10.randomUUID.bind(crypto10);
186695
+ return crypto10.randomUUID();
186067
186696
  }
186068
186697
  const u8 = new Uint8Array(1);
186069
- const randomByte = crypto9 ? () => crypto9.getRandomValues(u8)[0] : () => Math.random() * 255 & 255;
186698
+ const randomByte = crypto10 ? () => crypto10.getRandomValues(u8)[0] : () => Math.random() * 255 & 255;
186070
186699
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => (+c ^ randomByte() & 15 >> +c / 4).toString(16));
186071
186700
  };
186072
186701
 
@@ -197113,7 +197742,7 @@ var OpenAIAgentAdapter = class {
197113
197742
  parallel_tool_calls: false,
197114
197743
  response_format: responseFormat,
197115
197744
  ...tokenLimit
197116
- }, { maxChatCompletions: MAX_AGENT_PROVIDER_TURNS });
197745
+ }, { maxChatCompletions: request.maxTurns });
197117
197746
  const [finalCompletion, usage] = await Promise.all([
197118
197747
  runner.finalChatCompletion(),
197119
197748
  runner.totalUsage()
@@ -197123,7 +197752,7 @@ var OpenAIAgentAdapter = class {
197123
197752
  throw new Error("OpenAI returned no completion choice.");
197124
197753
  }
197125
197754
  text = finalChoice.message.content || "";
197126
- this.assertCompleted(finalChoice.message, finalChoice.finish_reason, text, request.maxTokens);
197755
+ this.assertCompleted(finalChoice.message, finalChoice.finish_reason, text, request, toolCalls);
197127
197756
  resolvedModelId2 = finalCompletion.model || request.modelId;
197128
197757
  inputTokens = usage.prompt_tokens;
197129
197758
  outputTokens = usage.completion_tokens;
@@ -197139,7 +197768,7 @@ var OpenAIAgentAdapter = class {
197139
197768
  throw new Error("OpenAI returned no assistant message.");
197140
197769
  }
197141
197770
  text = message.content || "";
197142
- this.assertCompleted(message, completion.choices[0]?.finish_reason, text, request.maxTokens);
197771
+ this.assertCompleted(message, completion.choices[0]?.finish_reason, text, request, toolCalls);
197143
197772
  resolvedModelId2 = completion.model || request.modelId;
197144
197773
  inputTokens = completion.usage?.prompt_tokens ?? 0;
197145
197774
  outputTokens = completion.usage?.completion_tokens ?? 0;
@@ -197152,7 +197781,7 @@ var OpenAIAgentAdapter = class {
197152
197781
  resolvedModelId: resolvedModelId2
197153
197782
  };
197154
197783
  }
197155
- assertCompleted(message, finishReason, rawText, maxTokens) {
197784
+ assertCompleted(message, finishReason, rawText, request, toolCalls) {
197156
197785
  if (message.refusal) {
197157
197786
  const refusalText = rawText || message.refusal;
197158
197787
  throw new AgentProviderRefusalError(`OpenAI model refused the request: ${message.refusal}`, refusalText);
@@ -197161,10 +197790,15 @@ var OpenAIAgentAdapter = class {
197161
197790
  throw new AgentProviderRefusalError("OpenAI blocked the response through its content filter.", rawText);
197162
197791
  }
197163
197792
  if (finishReason === "length") {
197164
- throw new Error(`OpenAI model reached its ${maxTokens}-token completion limit before finishing.`);
197793
+ throw new Error(`OpenAI model reached its ${request.maxTokens}-token completion limit before finishing.`);
197165
197794
  }
197166
197795
  if (finishReason === "tool_calls" || (message.tool_calls?.length ?? 0) > 0) {
197167
- throw new Error(`OpenAI tool loop exceeded ${MAX_AGENT_PROVIDER_TURNS} model turns.`);
197796
+ throw new AgentTurnLimitError(
197797
+ this.provider,
197798
+ request.maxTurns,
197799
+ toolCalls.length,
197800
+ toolCalls[toolCalls.length - 1]?.name
197801
+ );
197168
197802
  }
197169
197803
  }
197170
197804
  };
@@ -197391,6 +198025,7 @@ var DEFAULT_AGENT_MAX_TOKENS = 16e3;
197391
198025
  var DEFAULT_LOCAL_AGENT_MAX_TOKENS = 4096;
197392
198026
  var DEFAULT_AGENT_MAX_DEPTH = 5;
197393
198027
  var DEFAULT_AGENT_MAX_INVOCATIONS = 25;
198028
+ var DEFAULT_AGENT_MAX_TURNS = 100;
197394
198029
  var DEFAULT_AGENT_CONTRACT_RETRIES = 2;
197395
198030
 
197396
198031
  // src/agents/agentConfig.ts
@@ -197402,6 +198037,7 @@ function resolveAgentRuntimeLimits(startPath, provider, agent) {
197402
198037
  maxTokens: agent.maxTokens ?? providerConfig?.max_tokens ?? loadedConfig?.max_tokens ?? defaultMaxTokens,
197403
198038
  maxDepth: agent.maxDepth ?? providerConfig?.max_depth ?? loadedConfig?.max_depth ?? DEFAULT_AGENT_MAX_DEPTH,
197404
198039
  maxInvocations: agent.maxInvocations ?? providerConfig?.max_invocations ?? loadedConfig?.max_invocations ?? DEFAULT_AGENT_MAX_INVOCATIONS,
198040
+ maxTurns: agent.maxTurns ?? providerConfig?.max_turns ?? loadedConfig?.max_turns ?? DEFAULT_AGENT_MAX_TURNS,
197405
198041
  contractRetries: agent.contractRetries ?? providerConfig?.contract_retries ?? loadedConfig?.contract_retries ?? DEFAULT_AGENT_CONTRACT_RETRIES
197406
198042
  };
197407
198043
  }
@@ -197758,14 +198394,43 @@ function providerSafeToolName(index, authoredName) {
197758
198394
  const safe = authoredName.replace(/[^a-zA-Z0-9_-]/g, "_");
197759
198395
  return `norn_${index + 1}_${safe}`.slice(0, 64);
197760
198396
  }
198397
+ var MCP_CONTENT_PAYLOAD_FIELDS = ["data", "blob"];
198398
+ function contentBlockPayloadField(block) {
198399
+ return MCP_CONTENT_PAYLOAD_FIELDS.find((field) => typeof block[field] === "string");
198400
+ }
198401
+ function summarizeBinaryContentBlock(block, payloadField) {
198402
+ const payload = block[payloadField];
198403
+ const rest = { ...block };
198404
+ delete rest[payloadField];
198405
+ return {
198406
+ ...rest,
198407
+ omitted: true,
198408
+ // Base64 expands three bytes to four characters; enough for "how big was that".
198409
+ approximateBytes: Math.floor(payload.length * 3 / 4)
198410
+ };
198411
+ }
197761
198412
  function getMcpAgentToolOutput(result) {
197762
198413
  if (result.structuredContent !== void 0) {
197763
- return result.structuredContent;
198414
+ return { output: result.structuredContent, warnings: [] };
197764
198415
  }
197765
- if (result.content.length > 0) {
197766
- return result.content;
198416
+ if (result.content.length === 0) {
198417
+ return { output: result.text, warnings: [] };
197767
198418
  }
197768
- return result.text;
198419
+ const warnings = [];
198420
+ const output2 = result.content.map((block) => {
198421
+ const payloadField = contentBlockPayloadField(block);
198422
+ if (!payloadField) {
198423
+ return block;
198424
+ }
198425
+ const summarized = summarizeBinaryContentBlock(block, payloadField);
198426
+ const kind = typeof block.type === "string" ? block.type : "binary";
198427
+ const mimeType = typeof block.mimeType === "string" ? ` (${block.mimeType})` : "";
198428
+ warnings.push(
198429
+ `MCP tool '${result.tool}' on server '${result.server}' returned ${kind} content${mimeType} of about ${summarized.approximateBytes} bytes, omitted rather than sent to the model as base64. Configure the server not to return binary content, for example Playwright MCP's --image-responses omit.`
198430
+ );
198431
+ return summarized;
198432
+ });
198433
+ return { output: output2, warnings };
197769
198434
  }
197770
198435
  function unwrapDefaultToolInput(input2) {
197771
198436
  return input2.input;
@@ -197846,7 +198511,16 @@ var AgentInvocationRunner = class {
197846
198511
  model: resolvedModel.displayName,
197847
198512
  accepts: agent.builtInContracts?.accepts?.displayPath ?? agent.accepts?.authoredPath,
197848
198513
  returns: agent.builtInContracts?.returns?.displayPath ?? agent.returns?.authoredPath,
197849
- tools: agent.tools.map((tool) => tool.name),
198514
+ // A grant is listed as the grant, not as the twenty-four tools it expands to.
198515
+ // `Browser.*` is the authored truth and stays put when the server changes, while
198516
+ // what was actually offered is already exact in `request.tools`, and what was
198517
+ // actually called is already in the hop's `kind: 'mcp'` tool records. This also
198518
+ // runs before any expansion exists, so a hop that failed early still has its
198519
+ // definition on the trace.
198520
+ tools: [
198521
+ ...agent.tools.map((tool) => tool.name),
198522
+ ...(agent.grantedMcpServers ?? []).map((grant) => `${grant.alias}.*`)
198523
+ ],
197850
198524
  agents: agent.callableAgents.map((reference) => reference.name),
197851
198525
  sourcePath: agent.sourcePath
197852
198526
  });
@@ -197860,7 +198534,7 @@ var AgentInvocationRunner = class {
197860
198534
  }
197861
198535
  }
197862
198536
  }
197863
- createRequestSnapshot(request, agent) {
198537
+ createRequestSnapshot(request, agent, mcpServerGrants) {
197864
198538
  const sourcePath = this.options.sourcePath ?? this.options.startPath;
197865
198539
  const tools = request.tools.map((tool) => ({
197866
198540
  name: tool.name,
@@ -197880,6 +198554,11 @@ var AgentInvocationRunner = class {
197880
198554
  ...request.outputSchema ? { outputSchema: cloneSnapshotValue(request.outputSchema, {}) } : {},
197881
198555
  ...request.outputSchemaName ? { outputSchemaName: request.outputSchemaName } : {},
197882
198556
  maxTokens: request.maxTokens,
198557
+ maxTurns: request.maxTurns,
198558
+ ...mcpServerGrants?.length ? { mcpServerGrants: mcpServerGrants.map((grant) => ({
198559
+ alias: grant.alias,
198560
+ toolNames: [...grant.toolNames]
198561
+ })) } : {},
197883
198562
  agentSourcePath: agent.sourcePath,
197884
198563
  agentLineNumber: agent.lineNumber,
197885
198564
  ...sourcePath ? { sourcePath } : {},
@@ -198032,7 +198711,7 @@ var AgentInvocationRunner = class {
198032
198711
  adapted.warnings
198033
198712
  );
198034
198713
  }
198035
- const providerTools = await this.buildProviderTools(
198714
+ const toolAssembly = await this.buildProviderTools(
198036
198715
  agent,
198037
198716
  hop,
198038
198717
  limits,
@@ -198044,6 +198723,7 @@ var AgentInvocationRunner = class {
198044
198723
  },
198045
198724
  () => fatalToolError
198046
198725
  );
198726
+ const providerTools = toolAssembly.tools;
198047
198727
  const outputSchemaName = `${agent.name}_return`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
198048
198728
  const providerRequest = {
198049
198729
  agent: agent.name,
@@ -198060,10 +198740,15 @@ var AgentInvocationRunner = class {
198060
198740
  outputSchema,
198061
198741
  outputSchemaName,
198062
198742
  maxTokens: limits.maxTokens,
198743
+ maxTurns: limits.maxTurns,
198063
198744
  environment: this.options.environment,
198064
198745
  ...credentials ? { credentials } : {}
198065
198746
  };
198066
- hop.request = this.createRequestSnapshot(providerRequest, agent);
198747
+ hop.request = this.createRequestSnapshot(
198748
+ providerRequest,
198749
+ agent,
198750
+ toolAssembly.mcpServerGrants
198751
+ );
198067
198752
  const adapter2 = this.options.providerRegistry.get(resolvedModel.provider);
198068
198753
  if (!adapter2) {
198069
198754
  throw new Error(`No provider adapter is registered for '${resolvedModel.provider}'.`);
@@ -198129,21 +198814,55 @@ var AgentInvocationRunner = class {
198129
198814
  const reportedError = fatalToolError ?? error2;
198130
198815
  if (reportedError instanceof AgentProviderMaxTokensError) {
198131
198816
  hop.error = formatAgentProviderMaxTokensError(agent.name, reportedError);
198817
+ } else if (reportedError instanceof AgentTurnLimitError) {
198818
+ hop.turnLimit = {
198819
+ maxTurns: reportedError.maxTurns,
198820
+ toolCallCount: reportedError.toolCallCount,
198821
+ ...reportedError.lastToolName ? { lastToolName: reportedError.lastToolName } : {},
198822
+ sourcePath: agent.sourcePath,
198823
+ lineNumber: agent.lineNumber
198824
+ };
198825
+ hop.error = formatAgentTurnLimitError(agent.name, hop.turnLimit);
198132
198826
  } else {
198133
198827
  hop.error = reportedError instanceof Error ? reportedError.message : String(reportedError);
198134
198828
  }
198135
198829
  if (reportedError instanceof AgentProviderRefusalError) {
198136
198830
  hop.output.text = reportedError.rawText;
198137
198831
  }
198832
+ } finally {
198833
+ await this.options.mcpSessionManager?.closeScope(invocationId);
198138
198834
  }
198139
198835
  hop.output.model = hop.output.model || resolvedModel?.displayName || "";
198140
198836
  hop.output.ms = hop.output.ms || Date.now() - startedAt;
198141
198837
  this.options.onInvocationProgress?.({ phase: "completed", hop });
198142
198838
  return hop;
198143
198839
  }
198840
+ /**
198841
+ * Resolve one MCP alias for one invocation.
198842
+ *
198843
+ * The agent carries its own sidecar's blocks, so an alias means whatever that document says
198844
+ * it means; anything it does not declare falls back to `norn.config.json`. The invocation id
198845
+ * and call index only matter to a server that asked for a narrower `session` lifetime.
198846
+ */
198847
+ resolveMcpTargetFor(agent, alias, invocationId, callIndex) {
198848
+ return resolveMcpTarget(alias, {
198849
+ scope: agent.mcpServers,
198850
+ startPath: this.options.startPath || process.cwd(),
198851
+ envVariables: this.options.environment,
198852
+ invocationId,
198853
+ ...callIndex === void 0 ? {} : { callIndex }
198854
+ });
198855
+ }
198144
198856
  async buildProviderTools(agent, parentHop, callerLimits, callerModel, ancestors, requestToolsOverride, onFatal, getFatal) {
198145
198857
  const providerTools = [];
198146
198858
  const childInvocationCounts = /* @__PURE__ */ new Map();
198859
+ const mcpCallCounts = /* @__PURE__ */ new Map();
198860
+ const nextMcpCallIndex = (alias) => {
198861
+ const key = alias.toLowerCase();
198862
+ const next = mcpCallCounts.get(key) ?? 0;
198863
+ mcpCallCounts.set(key, next + 1);
198864
+ return next;
198865
+ };
198147
198866
  const overrideKeys = /* @__PURE__ */ new Set();
198148
198867
  for (const tool of requestToolsOverride ?? []) {
198149
198868
  const key = `${tool.kind ?? ""}\0${tool.name.toLowerCase()}`;
@@ -198155,7 +198874,8 @@ var AgentInvocationRunner = class {
198155
198874
  overrideKeys.add(key);
198156
198875
  }
198157
198876
  let callerLimitBreaches = 0;
198158
- for (const reference of agent.tools) {
198877
+ const expansion = agent.grantedMcpServers?.length ? await this.expandServerGrants(agent, parentHop) : void 0;
198878
+ for (const reference of [...agent.tools, ...expansion?.references ?? []]) {
198159
198879
  const offeredOverride = requestToolsOverride?.find(
198160
198880
  (tool) => tool.kind === "mcp" && tool.name.toLowerCase() === reference.name.toLowerCase()
198161
198881
  );
@@ -198164,11 +198884,9 @@ var AgentInvocationRunner = class {
198164
198884
  `Agent graph changed at '${parentHop.hopPath}': current MCP tool '${reference.name}' was not offered by the recording.`
198165
198885
  );
198166
198886
  }
198167
- const definition = this.options.resolveMcpToolDefinition ? await this.options.resolveMcpToolDefinition(parentHop.hopPath, reference) : await this.options.mcpSessionManager.getToolDefinition(
198168
- this.options.startPath || process.cwd(),
198169
- reference.mcpAlias,
198170
- reference.toolName,
198171
- this.options.environment
198887
+ const definition = this.options.resolveMcpToolDefinition ? await this.options.resolveMcpToolDefinition(parentHop.hopPath, reference, agent) : await this.options.mcpSessionManager.getToolDefinition(
198888
+ this.resolveMcpTargetFor(agent, reference.mcpAlias, parentHop.invocationId),
198889
+ reference.toolName
198172
198890
  );
198173
198891
  if (!definition) {
198174
198892
  throw new Error(
@@ -198196,16 +198914,25 @@ var AgentInvocationRunner = class {
198196
198914
  return executionOverride.output;
198197
198915
  }
198198
198916
  const result = await this.options.mcpSessionManager.callTool(
198199
- this.options.startPath || process.cwd(),
198200
- reference.mcpAlias,
198917
+ this.resolveMcpTargetFor(
198918
+ agent,
198919
+ reference.mcpAlias,
198920
+ parentHop.invocationId,
198921
+ nextMcpCallIndex(reference.mcpAlias)
198922
+ ),
198201
198923
  definition.name,
198202
- toolInput,
198203
- this.options.environment
198924
+ toolInput
198204
198925
  );
198205
198926
  if (result.isError) {
198206
198927
  throw new Error(result.text || `MCP tool '${reference.name}' failed.`);
198207
198928
  }
198208
- return getMcpAgentToolOutput(result);
198929
+ const { output: output2, warnings } = getMcpAgentToolOutput(result);
198930
+ for (const warning of warnings) {
198931
+ if (!parentHop.warnings.includes(warning)) {
198932
+ parentHop.warnings.push(warning);
198933
+ }
198934
+ }
198935
+ return output2;
198209
198936
  }));
198210
198937
  }
198211
198938
  for (const reference of agent.callableAgents) {
@@ -198432,7 +199159,27 @@ var AgentInvocationRunner = class {
198432
199159
  );
198433
199160
  }
198434
199161
  }
198435
- return providerTools;
199162
+ return {
199163
+ tools: providerTools,
199164
+ ...expansion?.grants.length ? { mcpServerGrants: expansion.grants } : {}
199165
+ };
199166
+ }
199167
+ /**
199168
+ * Expand this agent's whole-server grants, live unless a caller supplied its own way.
199169
+ *
199170
+ * The built-in path lists the server through the same session the hop's tool calls will
199171
+ * use, so a `session agent` server is listed inside its own session rather than a stray one.
199172
+ */
199173
+ async expandServerGrants(agent, parentHop) {
199174
+ if (this.options.expandMcpServerGrants) {
199175
+ return this.options.expandMcpServerGrants(parentHop.hopPath, agent);
199176
+ }
199177
+ return expandAgentMcpServerGrants(agent, async (alias) => {
199178
+ const listed = await this.options.mcpSessionManager.listTools(
199179
+ this.resolveMcpTargetFor(agent, alias, parentHop.invocationId)
199180
+ );
199181
+ return listed.tools.map((tool) => tool.name);
199182
+ });
198436
199183
  }
198437
199184
  };
198438
199185
 
@@ -198446,16 +199193,16 @@ var fs22 = __toESM(require("fs"));
198446
199193
  var path25 = __toESM(require("path"));
198447
199194
 
198448
199195
  // src/secrets/crypto.ts
198449
- var crypto8 = __toESM(require("crypto"));
199196
+ var crypto9 = __toESM(require("crypto"));
198450
199197
  var ENCRYPTED_SECRET_PREFIX = "ENC[";
198451
199198
  var ENCRYPTED_SECRET_SUFFIX = "]";
198452
199199
  var ENCRYPTED_SECRET_VERSION = "NORN_AGE_V1";
198453
199200
  var encryptedSecretRegex = /^ENC\[([A-Z0-9_]+):kid=([a-zA-Z0-9._-]+):([A-Za-z0-9_-]+)\]$/;
198454
199201
  function deriveEncryptionKey(sharedKey) {
198455
- return crypto8.createHash("sha256").update(sharedKey, "utf8").digest();
199202
+ return crypto9.createHash("sha256").update(sharedKey, "utf8").digest();
198456
199203
  }
198457
199204
  function generateSharedSecretKey() {
198458
- return crypto8.randomBytes(32).toString("base64url");
199205
+ return crypto9.randomBytes(32).toString("base64url");
198459
199206
  }
198460
199207
  function isEncryptedSecretValue(value) {
198461
199208
  return value.trim().startsWith(ENCRYPTED_SECRET_PREFIX) && value.trim().endsWith(ENCRYPTED_SECRET_SUFFIX);
@@ -198480,8 +199227,8 @@ function parseEncryptedSecretValue(value) {
198480
199227
  }
198481
199228
  function encryptSecretValue(plaintext, sharedKey, kid) {
198482
199229
  const key = deriveEncryptionKey(sharedKey);
198483
- const iv = crypto8.randomBytes(12);
198484
- const cipher = crypto8.createCipheriv("aes-256-gcm", key, iv);
199230
+ const iv = crypto9.randomBytes(12);
199231
+ const cipher = crypto9.createCipheriv("aes-256-gcm", key, iv);
198485
199232
  const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
198486
199233
  const authTag = cipher.getAuthTag();
198487
199234
  const payload = Buffer.concat([iv, authTag, ciphertext]).toString("base64url");
@@ -198504,7 +199251,7 @@ function decryptSecretValue(encryptedValue, sharedKey) {
198504
199251
  const authTag = raw.subarray(12, 28);
198505
199252
  const ciphertext = raw.subarray(28);
198506
199253
  const key = deriveEncryptionKey(sharedKey);
198507
- const decipher = crypto8.createDecipheriv("aes-256-gcm", key, iv);
199254
+ const decipher = crypto9.createDecipheriv("aes-256-gcm", key, iv);
198508
199255
  decipher.setAuthTag(authTag);
198509
199256
  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
198510
199257
  return decrypted.toString("utf8");
@@ -199869,6 +200616,7 @@ function loadAgentRunRecording(recordingFilePath, options = {}) {
199869
200616
  }
199870
200617
 
199871
200618
  // src/sequenceRunner.ts
200619
+ var MCP_PREFLIGHT_SCOPE = "norn-agent-preflight";
199872
200620
  function indentMultiline(value, prefix = " ") {
199873
200621
  return value.split("\n").map((line2) => `${prefix}${line2}`).join("\n");
199874
200622
  }
@@ -201566,6 +202314,12 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
201566
202314
  }
201567
202315
  return sqlOperationsBySource.get(executionContext.filePath);
201568
202316
  };
202317
+ const getCurrentMcpScope = () => {
202318
+ if (!executionContext?.filePath || !executionContext.mcpServersBySource) {
202319
+ return void 0;
202320
+ }
202321
+ return executionContext.mcpServersBySource.get(executionContext.filePath);
202322
+ };
201569
202323
  const getCurrentAgentScope = () => {
201570
202324
  if (!executionContext?.filePath || !executionContext.agentDefinitionsBySource) {
201571
202325
  return void 0;
@@ -201590,6 +202344,11 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
201590
202344
  agentTrace.hops.push(...nestedTrace.hops);
201591
202345
  };
201592
202346
  const getCurrentMcpStartPath = () => executionContext?.filePath || workingDir || process.cwd();
202347
+ const resolveDeterministicMcpTarget = (alias) => resolveMcpTarget(alias, {
202348
+ ...getCurrentMcpScope() ? { scope: getCurrentMcpScope() } : {},
202349
+ startPath: getCurrentMcpStartPath(),
202350
+ envVariables: getRuntimeEnvironmentVariables(runtimeVariables)
202351
+ });
201593
202352
  let requestIndex = 0;
201594
202353
  const ifStack = [];
201595
202354
  const shouldSkip = () => ifStack.length > 0 && ifStack.some((v) => !v);
@@ -201695,18 +202454,41 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
201695
202454
  }
201696
202455
  try {
201697
202456
  const preflightedAgents = /* @__PURE__ */ new Set();
202457
+ const agentDefinitionKey = (definition) => `${definition.sourcePath}\0${definition.name.toLowerCase()}`;
202458
+ const mcpGrantExpansions = /* @__PURE__ */ new Map();
202459
+ const expandMcpGrantsLive = (definition) => expandAgentMcpServerGrants(definition, async (alias) => {
202460
+ const listed = await mcpSessionManager.listTools(resolveMcpTarget(alias, {
202461
+ scope: definition.mcpServers,
202462
+ startPath: getCurrentMcpStartPath(),
202463
+ envVariables: getRuntimeEnvironmentVariables(runtimeVariables),
202464
+ invocationId: MCP_PREFLIGHT_SCOPE
202465
+ }));
202466
+ return listed.tools.map((tool) => tool.name);
202467
+ });
201698
202468
  const preflightAgentGraph = async (definition, scope) => {
201699
- const definitionKey = `${definition.sourcePath}\0${definition.name.toLowerCase()}`;
202469
+ const definitionKey = agentDefinitionKey(definition);
201700
202470
  if (preflightedAgents.has(definitionKey)) {
201701
202471
  return;
201702
202472
  }
201703
202473
  preflightedAgents.add(definitionKey);
202474
+ if (definition.grantedMcpServers?.length) {
202475
+ mcpGrantExpansions.set(definitionKey, await expandMcpGrantsLive(definition));
202476
+ }
201704
202477
  for (const tool of definition.tools) {
201705
202478
  await mcpSessionManager.getToolDefinition(
201706
- getCurrentMcpStartPath(),
201707
- tool.mcpAlias,
201708
- tool.toolName,
201709
- getRuntimeEnvironmentVariables(runtimeVariables)
202479
+ resolveMcpTarget(tool.mcpAlias, {
202480
+ // The agent's own sidecar wins; an alias with no block there falls
202481
+ // back to the config nearest the running `.norn`.
202482
+ scope: definition.mcpServers,
202483
+ startPath: getCurrentMcpStartPath(),
202484
+ envVariables: getRuntimeEnvironmentVariables(runtimeVariables),
202485
+ // Preflight belongs to no invocation, so a server that asked to be
202486
+ // isolated per agent gets a session of its own here too — closed the
202487
+ // moment preflight ends, or the isolation it asked for would already
202488
+ // be broken by the check that proves the tool exists.
202489
+ invocationId: MCP_PREFLIGHT_SCOPE
202490
+ }),
202491
+ tool.toolName
201710
202492
  );
201711
202493
  }
201712
202494
  for (const reference of definition.callableAgents) {
@@ -201822,6 +202604,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
201822
202604
  try {
201823
202605
  await preflightAgentGraph(definition, scope);
201824
202606
  } catch (error2) {
202607
+ await mcpSessionManager.closeScope(MCP_PREFLIGHT_SCOPE);
201825
202608
  const message = `Agent '${definition.name}' tool preflight failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
201826
202609
  errors.push(message);
201827
202610
  await emitFailure(message, step);
@@ -201837,6 +202620,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
201837
202620
  agentTrace
201838
202621
  };
201839
202622
  }
202623
+ await mcpSessionManager.closeScope(MCP_PREFLIGHT_SCOPE);
201840
202624
  }
201841
202625
  for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
201842
202626
  const step = steps[stepIdx];
@@ -202129,6 +202913,7 @@ async function runSequenceWithJar(sequenceContent, fileVariables, cookieJar, wor
202129
202913
  sequenceName: executionContext?.sequenceName,
202130
202914
  runLineNumber: debugLocation.absoluteLine,
202131
202915
  rootInvocationIndex: rootInvocationIndex2,
202916
+ expandMcpServerGrants: (_hopPath, grantedAgent) => mcpGrantExpansions.get(agentDefinitionKey(grantedAgent)) ?? expandMcpGrantsLive(grantedAgent),
202132
202917
  onInvocationProgress: (agentEvent) => {
202133
202918
  const recordedEvent = cloneSerializableValue(agentEvent);
202134
202919
  if (recordedEvent) {
@@ -202263,6 +203048,7 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
202263
203048
  sequenceName: executionContext?.sequenceName,
202264
203049
  runLineNumber: debugLocation.absoluteLine,
202265
203050
  rootInvocationIndex: rootInvocationIndex2,
203051
+ expandMcpServerGrants: (_hopPath, grantedAgent) => mcpGrantExpansions.get(agentDefinitionKey(grantedAgent)) ?? expandMcpGrantsLive(grantedAgent),
202266
203052
  onInvocationProgress: (agentEvent) => {
202267
203053
  const recordedEvent = cloneSerializableValue(agentEvent);
202268
203054
  if (recordedEvent) {
@@ -202565,9 +203351,7 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
202565
203351
  }
202566
203352
  try {
202567
203353
  const listResult = await mcpSessionManager.listTools(
202568
- getCurrentMcpStartPath(),
202569
- parsed.serverAlias,
202570
- getRuntimeEnvironmentVariables(runtimeVariables)
203354
+ resolveDeterministicMcpTarget(parsed.serverAlias)
202571
203355
  );
202572
203356
  if (parsed.variableName) {
202573
203357
  runtimeVariables[parsed.variableName] = listResult.tools;
@@ -202635,10 +203419,8 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
202635
203419
  let toolDefinition;
202636
203420
  try {
202637
203421
  toolDefinition = await mcpSessionManager.getToolDefinition(
202638
- getCurrentMcpStartPath(),
202639
- parsed.serverAlias,
202640
- parsed.toolName,
202641
- getRuntimeEnvironmentVariables(runtimeVariables)
203422
+ resolveDeterministicMcpTarget(parsed.serverAlias),
203423
+ parsed.toolName
202642
203424
  );
202643
203425
  } catch (error2) {
202644
203426
  const message = `MCP call failed for '${parsed.serverAlias}.${parsed.toolName}': ${error2?.message || String(error2)}`;
@@ -202671,11 +203453,9 @@ ${describeUnmetExpectations(verdict).map((line2) => ` - ${line2}`).join("\n")}`
202671
203453
  }
202672
203454
  try {
202673
203455
  const callResult = await mcpSessionManager.callTool(
202674
- getCurrentMcpStartPath(),
202675
- parsed.serverAlias,
203456
+ resolveDeterministicMcpTarget(parsed.serverAlias),
202676
203457
  parsed.toolName,
202677
- boundArgs.params,
202678
- getRuntimeEnvironmentVariables(runtimeVariables)
203458
+ boundArgs.params
202679
203459
  );
202680
203460
  if (parsed.variableName) {
202681
203461
  runtimeVariables[parsed.variableName] = callResult;
@@ -202984,6 +203764,7 @@ ${indentMultiline(userMessage)}`;
202984
203764
  sequenceLocationIndex: executionContext?.sequenceLocationIndex,
202985
203765
  mcpSessionManager,
202986
203766
  agentDefinitionsBySource: executionContext?.agentDefinitionsBySource,
203767
+ mcpServersBySource: executionContext?.mcpServersBySource,
202987
203768
  agentProviderRegistry,
202988
203769
  agentWarningKeys,
202989
203770
  agentEventLog,
@@ -203816,6 +204597,13 @@ function redactAgentHopForJson(hop, redaction) {
203816
204597
  input: redactBody(hop.input, redaction),
203817
204598
  request: redactAgentRequestForJson(hop.request, redaction),
203818
204599
  error: hop.error ? redactString(hop.error, redaction) : void 0,
204600
+ // Rides the hop like the judge verdict does, so it needs the same treatment: its strings
204601
+ // are authored names and paths, redacted exactly as `agent` and `hopPath` are.
204602
+ turnLimit: hop.turnLimit ? {
204603
+ ...hop.turnLimit,
204604
+ sourcePath: redactString(hop.turnLimit.sourcePath, redaction),
204605
+ ...hop.turnLimit.lastToolName ? { lastToolName: redactString(hop.turnLimit.lastToolName, redaction) } : {}
204606
+ } : void 0,
203819
204607
  warnings: hop.warnings.map((warning) => redactString(warning, redaction)),
203820
204608
  accepts: redactAgentContractForJson(hop.accepts, redaction),
203821
204609
  returns: redactAgentContractForJson(hop.returns, redaction),
@@ -204103,7 +204891,15 @@ function formatAgentHopAtDepth(hop, options, displayDepth) {
204103
204891
  for (const warning of hop.warnings) {
204104
204892
  lines.push(`${bodyIndent}${colors.warning(`warning: ${redactString(warning, redaction)}`)}`);
204105
204893
  }
204106
- if (hop.error) {
204894
+ if (hop.turnLimit) {
204895
+ const lastTool = hop.turnLimit.lastToolName ? `, last ${redactString(hop.turnLimit.lastToolName, redaction)}` : "";
204896
+ lines.push(
204897
+ `${bodyIndent}${colors.warning(`\u23F1 turn limit reached \u2014 ${hop.turnLimit.maxTurns} turns, ${hop.turnLimit.toolCallCount} tool calls${lastTool}`)}`
204898
+ );
204899
+ lines.push(
204900
+ `${bodyIndent} ${colors.dim(`raise max_turns in ${redactString(hop.turnLimit.sourcePath, redaction)}:${hop.turnLimit.lineNumber + 1}`)}`
204901
+ );
204902
+ } else if (hop.error) {
204107
204903
  const recoveredContractFailure = hop.retries.some(
204108
204904
  (retry2) => retry2.corrected && (retry2.contract === hop.accepts || retry2.contract === hop.returns)
204109
204905
  );
@@ -206944,6 +207740,13 @@ function deriveAgentHopPresentation(hop) {
206944
207740
  success: hop.success,
206945
207741
  depth: hop.depth,
206946
207742
  cycle: hop.cycle,
207743
+ ...hop.turnLimit ? {
207744
+ turnLimit: {
207745
+ maxTurns: hop.turnLimit.maxTurns,
207746
+ toolCallCount: hop.turnLimit.toolCallCount,
207747
+ ...hop.turnLimit.lastToolName ? { lastToolName: hop.turnLimit.lastToolName } : {}
207748
+ }
207749
+ } : {},
206947
207750
  ...hop.error ? { error: hop.error } : {},
206948
207751
  warnings: [...hop.warnings],
206949
207752
  ...hop.judge ? { judge: buildAgentJudgePresentation(hop.judge) } : {},
@@ -207237,13 +208040,55 @@ var ReplayCoordinator = class {
207237
208040
  }))
207238
208041
  };
207239
208042
  }
207240
- async resolveMcpToolDefinition(hopPath, reference, startPath, environment, manager) {
208043
+ /**
208044
+ * Expand a whole-server grant without opening a session.
208045
+ *
208046
+ * Replay's contract is zero MCP side effects, so a replayed hop reads the tool names back
208047
+ * out of its own recording. A live tail lists for real and, because it now has both
208048
+ * numbers, is the one place that can say the server's tool set moved.
208049
+ */
208050
+ async expandMcpServerGrants(hopPath, agent, startPath, environment, manager, diagnostics) {
208051
+ if (this.isLiveInvocation(hopPath)) {
208052
+ const recorded = this.getRecordedHop(hopPath)?.request?.mcpServerGrants;
208053
+ return expandAgentMcpServerGrants(agent, async (alias) => {
208054
+ const listed = await manager.listTools(resolveMcpTarget(alias, {
208055
+ scope: agent.mcpServers,
208056
+ startPath,
208057
+ envVariables: environment
208058
+ }));
208059
+ const toolNames = listed.tools.map((tool) => tool.name);
208060
+ const before = recordedGrantToolNames(recorded, alias);
208061
+ const drift = before && describeGrantToolDrift(alias, before, toolNames);
208062
+ if (drift) {
208063
+ diagnostics.push(`${drift} at '${hopPath}'; hop-path replay remains valid.`);
208064
+ }
208065
+ return toolNames;
208066
+ });
208067
+ }
208068
+ const recordedRequest = this.getRecordedHop(hopPath)?.request;
208069
+ return expandAgentMcpServerGrants(agent, async (alias) => {
208070
+ const recorded = recordedGrantToolNames(recordedRequest?.mcpServerGrants, alias);
208071
+ if (recorded) {
208072
+ return recorded;
208073
+ }
208074
+ const offered = (recordedRequest?.tools ?? []).filter((tool) => tool.kind === "mcp" && tool.name.toLowerCase().startsWith(`${alias.toLowerCase()}.`)).map((tool) => tool.name.slice(alias.length + 1));
208075
+ if (offered.length === 0) {
208076
+ throw new Error(
208077
+ `Agent graph changed at '${hopPath}': agent '${agent.name}' grants MCP server '${alias}', which the recording did not offer.`
208078
+ );
208079
+ }
208080
+ return offered;
208081
+ });
208082
+ }
208083
+ async resolveMcpToolDefinition(hopPath, reference, agent, startPath, environment, manager) {
207241
208084
  if (this.isLiveInvocation(hopPath)) {
207242
208085
  const definition = await manager.getToolDefinition(
207243
- startPath,
207244
- reference.mcpAlias,
207245
- reference.toolName,
207246
- environment
208086
+ resolveMcpTarget(reference.mcpAlias, {
208087
+ scope: agent.mcpServers,
208088
+ startPath,
208089
+ envVariables: environment
208090
+ }),
208091
+ reference.toolName
207247
208092
  );
207248
208093
  return {
207249
208094
  name: definition.name,
@@ -207441,7 +208286,7 @@ function mergeDefinition(definitions, seen, definition) {
207441
208286
  seen.add(key);
207442
208287
  definitions.push(definition);
207443
208288
  }
207444
- function addPromptDiagnostics(recording, scopes, runtimeVariables, recordingFilePath, projectRoot, diagnostics) {
208289
+ function addSourceDriftDiagnostics(recording, scopes, runtimeVariables, recordingFilePath, projectRoot, diagnostics) {
207445
208290
  for (const hop of flattenAgentReplayHops(recording.trace.hops)) {
207446
208291
  if (!hop.request?.sourcePath) {
207447
208292
  continue;
@@ -207456,6 +208301,14 @@ function addPromptDiagnostics(recording, scopes, runtimeVariables, recordingFile
207456
208301
  continue;
207457
208302
  }
207458
208303
  const comparable = hop.returns?.path === JUDGE_RESULT_SCHEMA_PATH ? buildJudgeDefinition(definition) : definition;
208304
+ const recordedAliases = (hop.request.mcpServerGrants ?? []).map((grant) => grant.alias);
208305
+ const currentAliases = (comparable.grantedMcpServers ?? []).map((grant) => grant.alias);
208306
+ const sameGrants = recordedAliases.length === currentAliases.length && recordedAliases.every((alias) => currentAliases.some((current2) => current2.toLowerCase() === alias.toLowerCase()));
208307
+ if (!sameGrants) {
208308
+ diagnostics.push(
208309
+ `Whole-server grants changed at '${hop.hopPath || hop.agent}': recorded ${recordedAliases.join(", ") || "none"}, now ${currentAliases.join(", ") || "none"}; hop-path replay remains valid.`
208310
+ );
208311
+ }
207459
208312
  const current = substituteVariables(comparable.systemPrompt, runtimeVariables);
207460
208313
  if (/\{\{[^}]+\}\}/.test(current) || current === hop.request.systemPrompt) {
207461
208314
  continue;
@@ -207485,7 +208338,7 @@ async function replayAgentRun(options) {
207485
208338
  options.recording,
207486
208339
  options.projectRoot
207487
208340
  );
207488
- addPromptDiagnostics(
208341
+ addSourceDriftDiagnostics(
207489
208342
  options.recording,
207490
208343
  options.agentDefinitionsBySource,
207491
208344
  runtimeVariables,
@@ -207521,13 +208374,22 @@ async function replayAgentRun(options) {
207521
208374
  runLineNumber: recordedRoot.request?.runLineNumber,
207522
208375
  rootInvocationIndex: rootInvocationIndex(recordedRoot),
207523
208376
  resolveRequestOverride: (hopPath) => coordinator.resolveRequestOverride(hopPath),
207524
- resolveMcpToolDefinition: (hopPath, reference) => coordinator.resolveMcpToolDefinition(
208377
+ resolveMcpToolDefinition: (hopPath, reference, agent) => coordinator.resolveMcpToolDefinition(
207525
208378
  hopPath,
207526
208379
  reference,
208380
+ agent,
207527
208381
  rootSourcePath,
207528
208382
  options.environment,
207529
208383
  manager
207530
208384
  ),
208385
+ expandMcpServerGrants: (hopPath, agent) => coordinator.expandMcpServerGrants(
208386
+ hopPath,
208387
+ agent,
208388
+ rootSourcePath,
208389
+ options.environment,
208390
+ manager,
208391
+ diagnostics
208392
+ ),
207531
208393
  executeMcpToolOverride: (hopPath, reference, input2) => coordinator.executeMcpToolOverride(hopPath, reference, input2),
207532
208394
  onInvocationProgress: (event) => {
207533
208395
  generatedEvents.push(event);
@@ -208795,7 +209657,8 @@ ${fileContent}` : fileContent;
208795
209657
  filePath: targetSequenceSourcePath,
208796
209658
  sequenceName: targetSeq.name,
208797
209659
  environment: envValidationContext,
208798
- agentDefinitionsBySource: importResult.agentDefinitionsBySource
209660
+ agentDefinitionsBySource: importResult.agentDefinitionsBySource,
209661
+ mcpServersBySource: importResult.mcpServersBySource
208799
209662
  }
208800
209663
  );
208801
209664
  caseResult.name = `${targetSeq.name}${caseLabel}`;
@@ -208819,7 +209682,8 @@ ${fileContent}` : fileContent;
208819
209682
  filePath: targetSequenceSourcePath,
208820
209683
  sequenceName: targetSeq.name,
208821
209684
  environment: envValidationContext,
208822
- agentDefinitionsBySource: importResult.agentDefinitionsBySource
209685
+ agentDefinitionsBySource: importResult.agentDefinitionsBySource,
209686
+ mcpServersBySource: importResult.mcpServersBySource
208823
209687
  }
208824
209688
  );
208825
209689
  seqResult.name = targetSeq.name;
@@ -208946,7 +209810,8 @@ ${fileContent}` : fileContent;
208946
209810
  {
208947
209811
  filePath,
208948
209812
  environment: envValidationContext,
208949
- agentDefinitionsBySource: importResult.agentDefinitionsBySource
209813
+ agentDefinitionsBySource: importResult.agentDefinitionsBySource,
209814
+ mcpServersBySource: importResult.mcpServersBySource
208950
209815
  },
208951
209816
  options.output === "pretty" ? (sequenceName) => createAgentProgressCallback(sequenceName, colors, redaction2, options.verbose) : void 0
208952
209817
  );