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/NOW.md +70 -20
- package/README.md +74 -2
- package/demos/agent-workbench/norn.config.json +1 -0
- package/demos/mcp-ticket-testing/README.md +114 -0
- package/demos/mcp-ticket-testing/agents.nornagent +77 -0
- package/demos/mcp-ticket-testing/contracts/test-run.schema.json +31 -0
- package/demos/mcp-ticket-testing/expectations/proj-142.md +12 -0
- package/demos/mcp-ticket-testing/fixtures/proj-142.json +13 -0
- package/demos/mcp-ticket-testing/prompts/backend-tester.md +12 -0
- package/demos/mcp-ticket-testing/prompts/frontend-tester.md +14 -0
- package/demos/mcp-ticket-testing/prompts/reporter.md +10 -0
- package/demos/mcp-ticket-testing/servers/browser-server.js +133 -0
- package/demos/mcp-ticket-testing/servers/house-server.js +125 -0
- package/demos/mcp-ticket-testing/tickets.norn +32 -0
- package/dist/cli.js +1324 -459
- package/package.json +3 -3
- package/playground/ai.norn +8 -2
- package/playground/ai_orchastration.nornagent +20 -1
- package/playground/knowedge_base/nexus_system_prompt.md +1 -1
- package/schemas/norn.config.schema.json +12 -0
- package/CHANGELOG.md +0 -1529
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
|
|
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 = "--------------------------" +
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
20015
|
-
const clientKey = await
|
|
20016
|
-
const storedKey = await
|
|
20017
|
-
const clientSignature = await
|
|
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
|
|
20020
|
-
const serverSignatureBytes = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
38351
|
+
var signer = crypto10.createSign("RSA-SHA" + bits);
|
|
38352
38352
|
var sig = (signer.update(thing), signer.sign({
|
|
38353
38353
|
key: privateKey,
|
|
38354
|
-
padding:
|
|
38355
|
-
saltLength:
|
|
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 =
|
|
38365
|
+
var verifier = crypto10.createVerify("RSA-SHA" + bits);
|
|
38366
38366
|
verifier.update(thing);
|
|
38367
38367
|
return verifier.verify({
|
|
38368
38368
|
key: publicKey,
|
|
38369
|
-
padding:
|
|
38370
|
-
saltLength:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
101317
|
-
if (
|
|
101318
|
-
uuid42 =
|
|
101319
|
-
return
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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_${
|
|
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
|
|
124338
|
+
var crypto10 = require("crypto");
|
|
124339
124339
|
var NodeCrypto = class {
|
|
124340
124340
|
async sha256DigestBase64(str2) {
|
|
124341
|
-
return
|
|
124341
|
+
return crypto10.createHash("sha256").update(str2).digest("base64");
|
|
124342
124342
|
}
|
|
124343
124343
|
randomBytesBase64(count) {
|
|
124344
|
-
return
|
|
124344
|
+
return crypto10.randomBytes(count).toString("base64");
|
|
124345
124345
|
}
|
|
124346
124346
|
async verify(pubkey, data, signature) {
|
|
124347
|
-
const verifier =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
125075
|
-
const randomString2 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
128130
|
-
return await
|
|
128131
|
-
}
|
|
128132
|
-
async function getSigningKey(
|
|
128133
|
-
const kDate = await sign(
|
|
128134
|
-
const kRegion = await sign(
|
|
128135
|
-
const kService = await sign(
|
|
128136
|
-
const kSigning = await sign(
|
|
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
|
|
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 =
|
|
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
|
|
130156
|
+
const crypto10 = (0, crypto_1.createCrypto)();
|
|
130157
130157
|
if (client instanceof jwtclient_1.JWT && client.key) {
|
|
130158
|
-
const sign = await
|
|
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(
|
|
130165
|
+
return this.signBlob(crypto10, creds.client_email, data, endpoint);
|
|
130166
130166
|
}
|
|
130167
|
-
async signBlob(
|
|
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:
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
158947
|
-
|
|
158948
|
-
|
|
158949
|
-
|
|
158950
|
-
|
|
158951
|
-
|
|
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
|
|
159080
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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(
|
|
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
|
-
|
|
159213
|
-
|
|
159214
|
-
|
|
159215
|
-
|
|
159216
|
-
|
|
159217
|
-
|
|
159218
|
-
|
|
159219
|
-
|
|
159220
|
-
|
|
159221
|
-
|
|
159222
|
-
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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) => [
|
|
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
|
|
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 =
|
|
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
|
|
166943
|
-
var
|
|
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 =
|
|
167504
|
+
const resolvedPath = path6.isAbsolute(filePath) ? filePath : path6.resolve(workingDir || process.cwd(), filePath);
|
|
166965
167505
|
try {
|
|
166966
|
-
if (!
|
|
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 =
|
|
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(
|
|
184059
|
-
const configDir = path10.dirname(
|
|
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(
|
|
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(
|
|
184258
|
-
return getNornCacheFilePath(path12.dirname(
|
|
184731
|
+
function getCachePathForConfig(originPath) {
|
|
184732
|
+
return getNornCacheFilePath(path12.dirname(originPath), CACHE_FILE);
|
|
184259
184733
|
}
|
|
184260
|
-
function ensureCacheDirForConfig(
|
|
184261
|
-
return !!ensureNornCacheDir(path12.dirname(
|
|
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(
|
|
184282
|
-
const cache = loadCacheForConfig(
|
|
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(
|
|
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
|
-
|
|
184342
|
-
|
|
184343
|
-
|
|
184344
|
-
|
|
184345
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
184396
|
-
saveMcpToolsForAlias(filePath, alias, tools);
|
|
184918
|
+
saveMcpToolsForAlias(getMcpOriginPath(target.origin), target.alias, tools);
|
|
184397
184919
|
return { tools };
|
|
184398
184920
|
}
|
|
184399
|
-
async
|
|
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.
|
|
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
|
|
184414
|
-
|
|
184415
|
-
|
|
184416
|
-
|
|
184417
|
-
|
|
184418
|
-
|
|
184419
|
-
|
|
184420
|
-
|
|
184421
|
-
|
|
184422
|
-
|
|
184423
|
-
|
|
184424
|
-
|
|
184425
|
-
|
|
184426
|
-
|
|
184427
|
-
|
|
184428
|
-
|
|
184429
|
-
|
|
184430
|
-
|
|
184431
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 <
|
|
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 ===
|
|
185997
|
-
throw new
|
|
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
|
|
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:
|
|
186064
|
-
if (
|
|
186065
|
-
uuid43 =
|
|
186066
|
-
return
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
197766
|
-
return result.
|
|
198416
|
+
if (result.content.length === 0) {
|
|
198417
|
+
return { output: result.text, warnings: [] };
|
|
197767
198418
|
}
|
|
197768
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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.
|
|
198169
|
-
reference.
|
|
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.
|
|
198200
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
199202
|
+
return crypto9.createHash("sha256").update(sharedKey, "utf8").digest();
|
|
198456
199203
|
}
|
|
198457
199204
|
function generateSharedSecretKey() {
|
|
198458
|
-
return
|
|
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 =
|
|
198484
|
-
const cipher =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
201707
|
-
|
|
201708
|
-
|
|
201709
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202639
|
-
parsed.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
207244
|
-
|
|
207245
|
-
|
|
207246
|
-
|
|
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
|
|
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
|
-
|
|
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
|
);
|