nexus-agents 2.83.1 → 2.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{child-mcp-config-3ZW2UPKZ.js → child-mcp-config-FARMFCA3.js} +3 -3
- package/dist/{chunk-DWOSFYI2.js → chunk-25AQZKMN.js} +2 -2
- package/dist/{chunk-I2HMWH4R.js → chunk-2WCYG75B.js} +13 -2
- package/dist/chunk-2WCYG75B.js.map +1 -0
- package/dist/{chunk-A6WC5I7V.js → chunk-4MYJNFYL.js} +4 -4
- package/dist/{chunk-W6MLS2UL.js → chunk-4S5GY2MF.js} +2 -2
- package/dist/{chunk-7V367KT4.js → chunk-4SU4Q37U.js} +7 -23
- package/dist/chunk-4SU4Q37U.js.map +1 -0
- package/dist/{chunk-J4ZBKE4B.js → chunk-6KZL5DMU.js} +910 -308
- package/dist/chunk-6KZL5DMU.js.map +1 -0
- package/dist/{chunk-JA6ON2RP.js → chunk-6QKMUPDP.js} +2 -2
- package/dist/{chunk-MATFDJRW.js → chunk-7J7D3LAL.js} +4 -4
- package/dist/{chunk-LM2S2QWO.js → chunk-CIW2BVSN.js} +3 -2
- package/dist/{chunk-LM2S2QWO.js.map → chunk-CIW2BVSN.js.map} +1 -1
- package/dist/{chunk-ZLGU7T6J.js → chunk-EL2KV773.js} +78 -63
- package/dist/chunk-EL2KV773.js.map +1 -0
- package/dist/{chunk-IM2B3FK2.js → chunk-ELVQORLX.js} +4 -4
- package/dist/chunk-ELVQORLX.js.map +1 -0
- package/dist/{chunk-VXIHHHLQ.js → chunk-FOG7PMGM.js} +2 -2
- package/dist/{chunk-HGPIHDC4.js → chunk-FR3P47J2.js} +4 -4
- package/dist/{chunk-ODKIRXN7.js → chunk-IF6TXCSE.js} +683 -227
- package/dist/chunk-IF6TXCSE.js.map +1 -0
- package/dist/{chunk-TKGVRHEI.js → chunk-J5S27RQ4.js} +2 -2
- package/dist/{chunk-UFUR6RBP.js → chunk-LY2KLNXM.js} +2 -2
- package/dist/{chunk-5QJPM5CB.js → chunk-NSOB2WRI.js} +3 -3
- package/dist/{chunk-4XSCU4B3.js → chunk-OXVOUVKH.js} +8 -8
- package/dist/{chunk-6XRNFLPI.js → chunk-POTGGTJD.js} +2 -2
- package/dist/{chunk-ZA6AZ7LK.js → chunk-QR4VYHMI.js} +6 -6
- package/dist/{chunk-FHY7I736.js → chunk-QR5KHIZG.js} +2 -2
- package/dist/{chunk-ELRNVADA.js → chunk-RTAIMK6P.js} +155 -47
- package/dist/chunk-RTAIMK6P.js.map +1 -0
- package/dist/{chunk-O3XJLOEX.js → chunk-SOKQOPTM.js} +2 -2
- package/dist/chunk-TRX7CAD5.js +27 -0
- package/dist/chunk-TRX7CAD5.js.map +1 -0
- package/dist/{chunk-BICIQYET.js → chunk-XZAWH36N.js} +3 -3
- package/dist/{chunk-6UI4NKT4.js → chunk-YLMB5MFQ.js} +27 -4
- package/dist/{chunk-6UI4NKT4.js.map → chunk-YLMB5MFQ.js.map} +1 -1
- package/dist/{chunk-BUXXAN6V.js → chunk-Z5BAZS6F.js} +2 -2
- package/dist/{chunk-SZZS57X7.js → chunk-ZF3MSYLG.js} +7 -7
- package/dist/{chunk-XODXYOFN.js → chunk-ZMERLMEP.js} +2 -2
- package/dist/{cli-circuit-breaker-LC4NZUGD.js → cli-circuit-breaker-LF22Z3QL.js} +5 -5
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +39 -34
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-GXQ25DO4.js → composite-router-TEXZI7WJ.js} +3 -3
- package/dist/{consensus-vote-MTLP6E72.js → consensus-vote-LPYEXUVC.js} +13 -12
- package/dist/{consensus-vote-types-CoGbAEjf.d.ts → consensus-vote-types-COAhHVIp.d.ts} +5 -0
- package/dist/{context-retriever-GQRC2MVM.js → context-retriever-3TKV4EEC.js} +7 -6
- package/dist/{dist-APHOQ26Z.js → dist-75MXB4R3.js} +917 -822
- package/dist/dist-75MXB4R3.js.map +1 -0
- package/dist/{doctor-deep-T3UUJX2E.js → doctor-deep-OUAKPLJX.js} +4 -4
- package/dist/expert-bridge-22UVN5TB.js +13 -0
- package/dist/{factory-OKNCTJO6.js → factory-IGHUOUBS.js} +6 -5
- package/dist/factory-JNXETBS4.js +14 -0
- package/dist/index.d.ts +137 -10
- package/dist/index.js +59 -26
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-KDTBKLXS.js → init-opencode-52TLZ2TO.js} +6 -6
- package/dist/issue-triage-WUK7L25X.js +15 -0
- package/dist/{learning-persistence-NX2KHL5T.js → learning-persistence-5ALG6XRC.js} +2 -2
- package/dist/{registry-command-D56ROMFJ.js → registry-command-ZIAJB2J3.js} +3 -3
- package/dist/{repo-security-plan-N3CDMW6L.js → repo-security-plan-THHLNGEQ.js} +4 -4
- package/dist/{research-helpers-synthesize-36QBOKII.js → research-helpers-synthesize-KNRCAIXK.js} +4 -4
- package/dist/{routing-memory-6IEPJ3EP.js → routing-memory-UY6KTGT2.js} +3 -3
- package/dist/{session-memory-X6SCDFER.js → session-memory-PUJMR6FE.js} +4 -4
- package/dist/{setup-command-5AEO5A4Z.js → setup-command-ZXDAR3GQ.js} +12 -11
- package/dist/setup-config-EYY5AYZ3.js +10 -0
- package/dist/{setup-custom-api-RBOJT3SS.js → setup-custom-api-5XRNYFOP.js} +5 -5
- package/dist/{tool-memory-EEHITQRJ.js → tool-memory-PNAHRFZM.js} +6 -5
- package/dist/{weather-report-6BC3JK5H.js → weather-report-RQMZOL7C.js} +3 -3
- package/package.json +4 -4
- package/dist/chunk-7V367KT4.js.map +0 -1
- package/dist/chunk-ELRNVADA.js.map +0 -1
- package/dist/chunk-I2HMWH4R.js.map +0 -1
- package/dist/chunk-IM2B3FK2.js.map +0 -1
- package/dist/chunk-J4ZBKE4B.js.map +0 -1
- package/dist/chunk-ODKIRXN7.js.map +0 -1
- package/dist/chunk-ZLGU7T6J.js.map +0 -1
- package/dist/dist-APHOQ26Z.js.map +0 -1
- package/dist/expert-bridge-7LPGCPPR.js +0 -13
- package/dist/factory-3XB5CAAA.js +0 -14
- package/dist/issue-triage-MURMDN2G.js +0 -15
- package/dist/setup-config-RPSVIQJO.js +0 -10
- /package/dist/{child-mcp-config-3ZW2UPKZ.js.map → child-mcp-config-FARMFCA3.js.map} +0 -0
- /package/dist/{chunk-DWOSFYI2.js.map → chunk-25AQZKMN.js.map} +0 -0
- /package/dist/{chunk-A6WC5I7V.js.map → chunk-4MYJNFYL.js.map} +0 -0
- /package/dist/{chunk-W6MLS2UL.js.map → chunk-4S5GY2MF.js.map} +0 -0
- /package/dist/{chunk-JA6ON2RP.js.map → chunk-6QKMUPDP.js.map} +0 -0
- /package/dist/{chunk-MATFDJRW.js.map → chunk-7J7D3LAL.js.map} +0 -0
- /package/dist/{chunk-VXIHHHLQ.js.map → chunk-FOG7PMGM.js.map} +0 -0
- /package/dist/{chunk-HGPIHDC4.js.map → chunk-FR3P47J2.js.map} +0 -0
- /package/dist/{chunk-TKGVRHEI.js.map → chunk-J5S27RQ4.js.map} +0 -0
- /package/dist/{chunk-UFUR6RBP.js.map → chunk-LY2KLNXM.js.map} +0 -0
- /package/dist/{chunk-5QJPM5CB.js.map → chunk-NSOB2WRI.js.map} +0 -0
- /package/dist/{chunk-4XSCU4B3.js.map → chunk-OXVOUVKH.js.map} +0 -0
- /package/dist/{chunk-6XRNFLPI.js.map → chunk-POTGGTJD.js.map} +0 -0
- /package/dist/{chunk-ZA6AZ7LK.js.map → chunk-QR4VYHMI.js.map} +0 -0
- /package/dist/{chunk-FHY7I736.js.map → chunk-QR5KHIZG.js.map} +0 -0
- /package/dist/{chunk-O3XJLOEX.js.map → chunk-SOKQOPTM.js.map} +0 -0
- /package/dist/{chunk-BICIQYET.js.map → chunk-XZAWH36N.js.map} +0 -0
- /package/dist/{chunk-BUXXAN6V.js.map → chunk-Z5BAZS6F.js.map} +0 -0
- /package/dist/{chunk-SZZS57X7.js.map → chunk-ZF3MSYLG.js.map} +0 -0
- /package/dist/{chunk-XODXYOFN.js.map → chunk-ZMERLMEP.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-LC4NZUGD.js.map → cli-circuit-breaker-LF22Z3QL.js.map} +0 -0
- /package/dist/{composite-router-GXQ25DO4.js.map → composite-router-TEXZI7WJ.js.map} +0 -0
- /package/dist/{consensus-vote-MTLP6E72.js.map → consensus-vote-LPYEXUVC.js.map} +0 -0
- /package/dist/{context-retriever-GQRC2MVM.js.map → context-retriever-3TKV4EEC.js.map} +0 -0
- /package/dist/{doctor-deep-T3UUJX2E.js.map → doctor-deep-OUAKPLJX.js.map} +0 -0
- /package/dist/{expert-bridge-7LPGCPPR.js.map → expert-bridge-22UVN5TB.js.map} +0 -0
- /package/dist/{factory-3XB5CAAA.js.map → factory-IGHUOUBS.js.map} +0 -0
- /package/dist/{factory-OKNCTJO6.js.map → factory-JNXETBS4.js.map} +0 -0
- /package/dist/{init-opencode-KDTBKLXS.js.map → init-opencode-52TLZ2TO.js.map} +0 -0
- /package/dist/{issue-triage-MURMDN2G.js.map → issue-triage-WUK7L25X.js.map} +0 -0
- /package/dist/{learning-persistence-NX2KHL5T.js.map → learning-persistence-5ALG6XRC.js.map} +0 -0
- /package/dist/{registry-command-D56ROMFJ.js.map → registry-command-ZIAJB2J3.js.map} +0 -0
- /package/dist/{repo-security-plan-N3CDMW6L.js.map → repo-security-plan-THHLNGEQ.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-36QBOKII.js.map → research-helpers-synthesize-KNRCAIXK.js.map} +0 -0
- /package/dist/{routing-memory-6IEPJ3EP.js.map → routing-memory-UY6KTGT2.js.map} +0 -0
- /package/dist/{session-memory-X6SCDFER.js.map → session-memory-PUJMR6FE.js.map} +0 -0
- /package/dist/{setup-command-5AEO5A4Z.js.map → setup-command-ZXDAR3GQ.js.map} +0 -0
- /package/dist/{setup-config-RPSVIQJO.js.map → setup-config-EYY5AYZ3.js.map} +0 -0
- /package/dist/{setup-custom-api-RBOJT3SS.js.map → setup-custom-api-5XRNYFOP.js.map} +0 -0
- /package/dist/{tool-memory-EEHITQRJ.js.map → tool-memory-PNAHRFZM.js.map} +0 -0
- /package/dist/{weather-report-6BC3JK5H.js.map → weather-report-RQMZOL7C.js.map} +0 -0
|
@@ -4,21 +4,21 @@ import {
|
|
|
4
4
|
createStream,
|
|
5
5
|
requireApiKey,
|
|
6
6
|
validateApiKeyPresence
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-6QKMUPDP.js";
|
|
8
8
|
import {
|
|
9
9
|
CUSTOM_API_BASE_URL_ENV,
|
|
10
10
|
PROVIDER_ENV_KEYS,
|
|
11
11
|
validateCustomApiBaseUrl
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-POTGGTJD.js";
|
|
13
13
|
import {
|
|
14
14
|
getToolMemory
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4SU4Q37U.js";
|
|
16
16
|
import {
|
|
17
17
|
getDefaultAvailableModelsCache
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-FOG7PMGM.js";
|
|
19
19
|
import {
|
|
20
20
|
CUSTOM_API_DEFAULT_MODEL
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-25AQZKMN.js";
|
|
22
22
|
import {
|
|
23
23
|
createCliAdapter,
|
|
24
24
|
createCliDetectionCache,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
isCliAvailable,
|
|
28
28
|
sanitizeOutput,
|
|
29
29
|
withTimeout
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-RTAIMK6P.js";
|
|
31
31
|
import {
|
|
32
32
|
AgentError,
|
|
33
33
|
CACHE_TIMEOUTS,
|
|
@@ -70,47 +70,48 @@ import {
|
|
|
70
70
|
resolveCliAlias,
|
|
71
71
|
resolveVoteTimeout,
|
|
72
72
|
toRateLimitError
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-YLMB5MFQ.js";
|
|
74
74
|
import {
|
|
75
75
|
ensureLearningDir,
|
|
76
76
|
getNexusDataDir,
|
|
77
77
|
getOutcomesFile,
|
|
78
|
-
nexusDataPath
|
|
79
|
-
|
|
78
|
+
nexusDataPath,
|
|
79
|
+
nexusDataPathEnsure
|
|
80
|
+
} from "./chunk-2WCYG75B.js";
|
|
80
81
|
|
|
81
82
|
// src/mcp/tools/consensus-vote.ts
|
|
82
|
-
import { z as
|
|
83
|
+
import { z as z12 } from "zod";
|
|
83
84
|
|
|
84
85
|
// src/mcp/mcp-notifier.ts
|
|
85
86
|
import { AsyncLocalStorage } from "async_hooks";
|
|
86
87
|
var internalLogger = createLogger({ component: "mcp-notifier" });
|
|
87
88
|
function createMcpNotifier(server) {
|
|
88
|
-
function send(level,
|
|
89
|
+
function send(level, logger11, data) {
|
|
89
90
|
try {
|
|
90
|
-
server.sendLoggingMessage({ level, logger:
|
|
91
|
+
server.sendLoggingMessage({ level, logger: logger11, data }).catch((error) => {
|
|
91
92
|
internalLogger.debug("Failed to send MCP notification", {
|
|
92
93
|
level,
|
|
93
|
-
logger:
|
|
94
|
+
logger: logger11,
|
|
94
95
|
error: getErrorMessage(error)
|
|
95
96
|
});
|
|
96
97
|
});
|
|
97
98
|
} catch (error) {
|
|
98
99
|
internalLogger.debug("Failed to send MCP notification", {
|
|
99
100
|
level,
|
|
100
|
-
logger:
|
|
101
|
+
logger: logger11,
|
|
101
102
|
error: getErrorMessage(error)
|
|
102
103
|
});
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
return {
|
|
106
|
-
info: (
|
|
107
|
-
send("info",
|
|
107
|
+
info: (logger11, data) => {
|
|
108
|
+
send("info", logger11, data);
|
|
108
109
|
},
|
|
109
|
-
debug: (
|
|
110
|
-
send("debug",
|
|
110
|
+
debug: (logger11, data) => {
|
|
111
|
+
send("debug", logger11, data);
|
|
111
112
|
},
|
|
112
|
-
warn: (
|
|
113
|
-
send("warning",
|
|
113
|
+
warn: (logger11, data) => {
|
|
114
|
+
send("warning", logger11, data);
|
|
114
115
|
}
|
|
115
116
|
};
|
|
116
117
|
}
|
|
@@ -240,7 +241,7 @@ var RateLimiter = class {
|
|
|
240
241
|
this.logger.debug("Rate limiter reset", { tokens: this.tokens });
|
|
241
242
|
}
|
|
242
243
|
};
|
|
243
|
-
function createDefaultRateLimiter(name,
|
|
244
|
+
function createDefaultRateLimiter(name, logger11) {
|
|
244
245
|
const config = {
|
|
245
246
|
capacity: 100,
|
|
246
247
|
refillRate: 10,
|
|
@@ -249,8 +250,8 @@ function createDefaultRateLimiter(name, logger8) {
|
|
|
249
250
|
if (name !== void 0) {
|
|
250
251
|
config.name = name;
|
|
251
252
|
}
|
|
252
|
-
if (
|
|
253
|
-
config.logger =
|
|
253
|
+
if (logger11 !== void 0) {
|
|
254
|
+
config.logger = logger11;
|
|
254
255
|
}
|
|
255
256
|
return new RateLimiter(config);
|
|
256
257
|
}
|
|
@@ -888,6 +889,10 @@ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
|
888
889
|
"weather_report",
|
|
889
890
|
"query_trace",
|
|
890
891
|
"query_task_state",
|
|
892
|
+
// Async-mode result polling (#3042 / epic #2631)
|
|
893
|
+
"get_job_result",
|
|
894
|
+
// Async-mode job discovery (#3046 / epic #2631 Stage 5)
|
|
895
|
+
"list_jobs",
|
|
891
896
|
// Codebase intelligence (read-only over local files)
|
|
892
897
|
"search_codebase",
|
|
893
898
|
"extract_symbols",
|
|
@@ -1235,13 +1240,13 @@ function buildMiddlewareStack(config) {
|
|
|
1235
1240
|
return middlewares;
|
|
1236
1241
|
}
|
|
1237
1242
|
function createMiddlewareChain(config) {
|
|
1238
|
-
const
|
|
1243
|
+
const logger11 = config.logger ?? createLogger({ tool: config.toolName });
|
|
1239
1244
|
const middlewares = buildMiddlewareStack(config);
|
|
1240
1245
|
const composed = composeMiddleware(middlewares);
|
|
1241
1246
|
return (handler) => {
|
|
1242
1247
|
return async (args) => {
|
|
1243
1248
|
const requestContext = createRequestContext({ toolName: config.toolName });
|
|
1244
|
-
const requestLogger =
|
|
1249
|
+
const requestLogger = logger11.child(contextForLogging(requestContext));
|
|
1245
1250
|
const ctx = { requestContext, logger: requestLogger };
|
|
1246
1251
|
return composed(args, ctx, (finalArgs, finalCtx) => handler(finalArgs, finalCtx));
|
|
1247
1252
|
};
|
|
@@ -1428,8 +1433,8 @@ async function runMismatchedCall(ctx) {
|
|
|
1428
1433
|
throw err2;
|
|
1429
1434
|
}
|
|
1430
1435
|
}
|
|
1431
|
-
function toSdkCallbackWithBudgetCheck(handler, toolName, configuredTimeoutMs,
|
|
1432
|
-
const log =
|
|
1436
|
+
function toSdkCallbackWithBudgetCheck(handler, toolName, configuredTimeoutMs, logger11) {
|
|
1437
|
+
const log = logger11 ?? wrapperLogger;
|
|
1433
1438
|
return (args, extra) => {
|
|
1434
1439
|
const progressCtx = extractProgressContext(extra);
|
|
1435
1440
|
const signal = extra?.signal;
|
|
@@ -1553,15 +1558,15 @@ function sanitizeToolInput(args) {
|
|
|
1553
1558
|
detectedPatterns: uniquePatterns
|
|
1554
1559
|
};
|
|
1555
1560
|
}
|
|
1556
|
-
function logSanitizationResult(result,
|
|
1561
|
+
function logSanitizationResult(result, logger11, toolName) {
|
|
1557
1562
|
if (result.wasModified) {
|
|
1558
|
-
|
|
1563
|
+
logger11.warn("Tool input sanitized \u2014 XML injection tags stripped", {
|
|
1559
1564
|
tool: toolName,
|
|
1560
1565
|
modifiedFields: result.modifiedCount
|
|
1561
1566
|
});
|
|
1562
1567
|
}
|
|
1563
1568
|
if (result.detectedPatterns.length > 0) {
|
|
1564
|
-
|
|
1569
|
+
logger11.warn("Injection patterns detected in tool input", {
|
|
1565
1570
|
tool: toolName,
|
|
1566
1571
|
patterns: result.detectedPatterns
|
|
1567
1572
|
});
|
|
@@ -1601,11 +1606,11 @@ var SECRET_PATTERNS = [
|
|
|
1601
1606
|
// password= or token= in output
|
|
1602
1607
|
/(?:password|token|secret|apikey|api_key)\s*[=:]\s*\S{8,}/i
|
|
1603
1608
|
];
|
|
1604
|
-
function sanitizeOutput2(text,
|
|
1609
|
+
function sanitizeOutput2(text, logger11) {
|
|
1605
1610
|
let sanitized = text;
|
|
1606
1611
|
for (const pattern of SECRET_PATTERNS) {
|
|
1607
1612
|
if (pattern.test(sanitized)) {
|
|
1608
|
-
|
|
1613
|
+
logger11.warn("Potential secret detected in tool output, redacting", {
|
|
1609
1614
|
pattern: pattern.source.slice(0, 30)
|
|
1610
1615
|
});
|
|
1611
1616
|
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
@@ -1613,25 +1618,25 @@ function sanitizeOutput2(text, logger8) {
|
|
|
1613
1618
|
}
|
|
1614
1619
|
return sanitized;
|
|
1615
1620
|
}
|
|
1616
|
-
function sanitizeToolResult(result,
|
|
1621
|
+
function sanitizeToolResult(result, logger11) {
|
|
1617
1622
|
for (const item of result.content) {
|
|
1618
|
-
item.text = sanitizeOutput2(item.text,
|
|
1623
|
+
item.text = sanitizeOutput2(item.text, logger11);
|
|
1619
1624
|
}
|
|
1620
1625
|
}
|
|
1621
|
-
function checkInputSize(args,
|
|
1626
|
+
function checkInputSize(args, logger11, requestId) {
|
|
1622
1627
|
if (args === void 0) return null;
|
|
1623
1628
|
const inputSize = JSON.stringify(args).length;
|
|
1624
1629
|
if (inputSize > MAX_INPUT_SIZE_BYTES) {
|
|
1625
|
-
|
|
1630
|
+
logger11.warn("Input size exceeds limit", { inputSize, limit: MAX_INPUT_SIZE_BYTES });
|
|
1626
1631
|
return internalError("Input too large", requestId);
|
|
1627
1632
|
}
|
|
1628
1633
|
return null;
|
|
1629
1634
|
}
|
|
1630
|
-
function checkRateLimit(rateLimiter,
|
|
1635
|
+
function checkRateLimit(rateLimiter, logger11) {
|
|
1631
1636
|
const acquired = rateLimiter.tryAcquire();
|
|
1632
1637
|
if (!acquired) {
|
|
1633
1638
|
const state = rateLimiter.getState();
|
|
1634
|
-
|
|
1639
|
+
logger11.warn("Rate limit exceeded");
|
|
1635
1640
|
return rateLimitError(state.nextTokenMs);
|
|
1636
1641
|
}
|
|
1637
1642
|
return null;
|
|
@@ -1652,14 +1657,14 @@ function checkPolicy(opts) {
|
|
|
1652
1657
|
opts.logger.debug("Policy check passed", { reason: decision.reason });
|
|
1653
1658
|
return null;
|
|
1654
1659
|
}
|
|
1655
|
-
async function executeHandler(handler, args, ctx,
|
|
1660
|
+
async function executeHandler(handler, args, ctx, logger11) {
|
|
1656
1661
|
const startTime = getTimeProvider().now();
|
|
1657
1662
|
const result = handler.length >= 2 ? await handler(args, ctx) : await handler(args);
|
|
1658
1663
|
const durationMs = getTimeProvider().now() - startTime;
|
|
1659
1664
|
if (result.isError === true) {
|
|
1660
|
-
|
|
1665
|
+
logger11.warn("Tool execution completed with error", { durationMs });
|
|
1661
1666
|
} else {
|
|
1662
|
-
|
|
1667
|
+
logger11.info("Tool execution completed", { durationMs });
|
|
1663
1668
|
}
|
|
1664
1669
|
return result;
|
|
1665
1670
|
}
|
|
@@ -1705,12 +1710,12 @@ function emitRateLimitAudit(auditLogger, toolName, ctx) {
|
|
|
1705
1710
|
requestId: ctx.requestId
|
|
1706
1711
|
});
|
|
1707
1712
|
}
|
|
1708
|
-
function checkSecurityTier(config, sanitizeResult,
|
|
1713
|
+
function checkSecurityTier(config, sanitizeResult, logger11) {
|
|
1709
1714
|
const tier = config.securityTier ?? "standard";
|
|
1710
1715
|
if (tier === "standard" || sanitizeResult.detectedPatterns.length === 0) {
|
|
1711
1716
|
return null;
|
|
1712
1717
|
}
|
|
1713
|
-
|
|
1718
|
+
logger11.warn("Input rejected by security tier validation", {
|
|
1714
1719
|
tier,
|
|
1715
1720
|
patterns: sanitizeResult.detectedPatterns
|
|
1716
1721
|
});
|
|
@@ -1719,16 +1724,16 @@ function checkSecurityTier(config, sanitizeResult, logger8) {
|
|
|
1719
1724
|
message: `Input validation failed: detected patterns [${sanitizeResult.detectedPatterns.join(", ")}]. Remove prompt injection patterns and retry.`
|
|
1720
1725
|
});
|
|
1721
1726
|
}
|
|
1722
|
-
function runPreChecks(config, args, mode, requestContext,
|
|
1723
|
-
const sizeResult = checkInputSize(args,
|
|
1727
|
+
function runPreChecks(config, args, mode, requestContext, logger11) {
|
|
1728
|
+
const sizeResult = checkInputSize(args, logger11, requestContext.requestId);
|
|
1724
1729
|
if (sizeResult) return { error: sizeResult, sanitizedArgs: args };
|
|
1725
1730
|
const sanitizeResult = sanitizeToolInput(args);
|
|
1726
|
-
logSanitizationResult(sanitizeResult,
|
|
1731
|
+
logSanitizationResult(sanitizeResult, logger11, config.toolName);
|
|
1727
1732
|
const sanitizedArgs = sanitizeResult.wasModified ? sanitizeResult.sanitized : args;
|
|
1728
|
-
const tierError = checkSecurityTier(config, sanitizeResult,
|
|
1733
|
+
const tierError = checkSecurityTier(config, sanitizeResult, logger11);
|
|
1729
1734
|
if (tierError !== null) return { error: tierError, sanitizedArgs };
|
|
1730
1735
|
if (config.rateLimiter) {
|
|
1731
|
-
const rlResult = checkRateLimit(config.rateLimiter,
|
|
1736
|
+
const rlResult = checkRateLimit(config.rateLimiter, logger11);
|
|
1732
1737
|
if (rlResult) {
|
|
1733
1738
|
if (config.auditLogger)
|
|
1734
1739
|
emitRateLimitAudit(config.auditLogger, config.toolName, requestContext);
|
|
@@ -1742,7 +1747,7 @@ function runPreChecks(config, args, mode, requestContext, logger8) {
|
|
|
1742
1747
|
args: sanitizedArgs,
|
|
1743
1748
|
mode,
|
|
1744
1749
|
allowedPaths: config.allowedPaths,
|
|
1745
|
-
logger:
|
|
1750
|
+
logger: logger11,
|
|
1746
1751
|
requestId: requestContext.requestId
|
|
1747
1752
|
});
|
|
1748
1753
|
if (pResult) {
|
|
@@ -1754,7 +1759,7 @@ function runPreChecks(config, args, mode, requestContext, logger8) {
|
|
|
1754
1759
|
return { error: null, sanitizedArgs };
|
|
1755
1760
|
}
|
|
1756
1761
|
function createSecureHandler(handler, config) {
|
|
1757
|
-
const
|
|
1762
|
+
const logger11 = config.logger ?? createLogger({ tool: config.toolName });
|
|
1758
1763
|
const mode = config.executionMode ?? "read-only";
|
|
1759
1764
|
return async (args) => {
|
|
1760
1765
|
const ctxOpts = {
|
|
@@ -1762,7 +1767,7 @@ function createSecureHandler(handler, config) {
|
|
|
1762
1767
|
...config.callerInfo && { caller: config.callerInfo }
|
|
1763
1768
|
};
|
|
1764
1769
|
const requestContext = createRequestContext(ctxOpts);
|
|
1765
|
-
const requestLogger =
|
|
1770
|
+
const requestLogger = logger11.child(contextForLogging(requestContext));
|
|
1766
1771
|
requestLogger.info("Tool invocation started");
|
|
1767
1772
|
const { error: preCheckError, sanitizedArgs } = runPreChecks(
|
|
1768
1773
|
config,
|
|
@@ -2225,7 +2230,7 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
2225
2230
|
*/
|
|
2226
2231
|
async executeCompletion(request) {
|
|
2227
2232
|
const params = this.buildRequestParams(request);
|
|
2228
|
-
const response = await this.client.messages.create(params);
|
|
2233
|
+
const response = request.signal !== void 0 ? await this.client.messages.create(params, { signal: request.signal }) : await this.client.messages.create(params);
|
|
2229
2234
|
return this.mapResponse(response);
|
|
2230
2235
|
}
|
|
2231
2236
|
/**
|
|
@@ -2360,10 +2365,10 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
2360
2365
|
return this.modelsCache.value;
|
|
2361
2366
|
}
|
|
2362
2367
|
if (this.modelsInFlight !== null) return this.modelsInFlight;
|
|
2363
|
-
const
|
|
2364
|
-
this.modelsInFlight =
|
|
2368
|
+
const inFlight2 = this.fetchModels();
|
|
2369
|
+
this.modelsInFlight = inFlight2;
|
|
2365
2370
|
try {
|
|
2366
|
-
const value = await
|
|
2371
|
+
const value = await inFlight2;
|
|
2367
2372
|
this.modelsCache = { value, fetchedAt: Date.now() };
|
|
2368
2373
|
return value;
|
|
2369
2374
|
} finally {
|
|
@@ -2463,13 +2468,13 @@ var SdkAdapter = class extends BaseAdapter {
|
|
|
2463
2468
|
customBaseUrl;
|
|
2464
2469
|
/** Inflight init promise for coalescing concurrent calls (Issue #1438). */
|
|
2465
2470
|
initPromise;
|
|
2466
|
-
constructor(config,
|
|
2471
|
+
constructor(config, logger11) {
|
|
2467
2472
|
const apiKey = resolveApiKey(config.providerId, config.apiKey);
|
|
2468
2473
|
super({
|
|
2469
2474
|
providerId: `sdk-${config.providerId}`,
|
|
2470
2475
|
modelId: config.modelId,
|
|
2471
2476
|
capabilities: [ModelCapability.COMPLETION, ModelCapability.STREAMING],
|
|
2472
|
-
logger:
|
|
2477
|
+
logger: logger11 ?? createLogger({ adapter: `sdk-${config.providerId}` }),
|
|
2473
2478
|
...apiKey !== void 0 ? { apiKey } : {},
|
|
2474
2479
|
...config.timeout !== void 0 ? { timeout: config.timeout } : {},
|
|
2475
2480
|
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {}
|
|
@@ -2649,19 +2654,19 @@ var SdkAdapter = class extends BaseAdapter {
|
|
|
2649
2654
|
|
|
2650
2655
|
// src/adapters/auto-adapter.ts
|
|
2651
2656
|
var defaultLogger = createLogger({ component: "auto-adapter" });
|
|
2652
|
-
function resolveCache(config,
|
|
2657
|
+
function resolveCache(config, logger11) {
|
|
2653
2658
|
if (config.cache !== void 0) {
|
|
2654
2659
|
return config.cache;
|
|
2655
2660
|
}
|
|
2656
2661
|
const enableCache = config.enableCache ?? true;
|
|
2657
|
-
return enableCache ? createCliDetectionCache({ logger:
|
|
2662
|
+
return enableCache ? createCliDetectionCache({ logger: logger11 }) : void 0;
|
|
2658
2663
|
}
|
|
2659
|
-
async function tryCliAdapter(config,
|
|
2664
|
+
async function tryCliAdapter(config, logger11, cache) {
|
|
2660
2665
|
const preferredCli = config.preferredCli;
|
|
2661
2666
|
const bridgeConfig = config.defaultCliTimeoutMs !== void 0 ? { defaultTimeoutMs: config.defaultCliTimeoutMs } : void 0;
|
|
2662
2667
|
if (preferredCli !== void 0 && await isCliAvailable(preferredCli, cache)) {
|
|
2663
|
-
|
|
2664
|
-
const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger:
|
|
2668
|
+
logger11.info("Using preferred CLI", { cli: preferredCli });
|
|
2669
|
+
const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger: logger11 });
|
|
2665
2670
|
await cliAdapter2.initialize();
|
|
2666
2671
|
return {
|
|
2667
2672
|
adapter: createCliToModelAdapter(cliAdapter2, bridgeConfig),
|
|
@@ -2673,15 +2678,15 @@ async function tryCliAdapter(config, logger8, cache) {
|
|
|
2673
2678
|
}
|
|
2674
2679
|
const availableClis = await getAvailableClis(cache);
|
|
2675
2680
|
if (availableClis.length === 0) {
|
|
2676
|
-
|
|
2681
|
+
logger11.info("No CLI adapters available");
|
|
2677
2682
|
return null;
|
|
2678
2683
|
}
|
|
2679
2684
|
const selectedCli = availableClis[0];
|
|
2680
2685
|
if (selectedCli === void 0) {
|
|
2681
2686
|
return null;
|
|
2682
2687
|
}
|
|
2683
|
-
|
|
2684
|
-
const cliAdapter = createCliAdapter({ cli: selectedCli, logger:
|
|
2688
|
+
logger11.info("Auto-selected CLI", { cli: selectedCli, available: availableClis });
|
|
2689
|
+
const cliAdapter = createCliAdapter({ cli: selectedCli, logger: logger11 });
|
|
2685
2690
|
await cliAdapter.initialize();
|
|
2686
2691
|
return {
|
|
2687
2692
|
adapter: createCliToModelAdapter(cliAdapter, bridgeConfig),
|
|
@@ -2695,13 +2700,13 @@ function resolveApiKeyFromEnv(configKey, envVar) {
|
|
|
2695
2700
|
const key = configKey ?? process.env[envVar];
|
|
2696
2701
|
return key !== void 0 && key.length > 0 ? key : void 0;
|
|
2697
2702
|
}
|
|
2698
|
-
function tryApiAdapter(config,
|
|
2703
|
+
function tryApiAdapter(config, logger11) {
|
|
2699
2704
|
const claudeModelId = getCliModelName(getDefaultModelForCli("claude"));
|
|
2700
2705
|
const codexModelId = getCliModelName(getDefaultModelForCli("codex"));
|
|
2701
2706
|
const geminiModelId = getCliModelName(getDefaultModelForCli("gemini"));
|
|
2702
2707
|
const anthropicKey = resolveApiKeyFromEnv(config.anthropicApiKey, "ANTHROPIC_API_KEY");
|
|
2703
2708
|
if (anthropicKey !== void 0) {
|
|
2704
|
-
|
|
2709
|
+
logger11.info("Using Anthropic API adapter", { model: claudeModelId });
|
|
2705
2710
|
return {
|
|
2706
2711
|
adapter: createClaudeAdapter({ modelId: claudeModelId, apiKey: anthropicKey }),
|
|
2707
2712
|
source: "api",
|
|
@@ -2711,7 +2716,7 @@ function tryApiAdapter(config, logger8) {
|
|
|
2711
2716
|
}
|
|
2712
2717
|
const openaiKey = resolveApiKeyFromEnv(config.openaiApiKey, "OPENAI_API_KEY");
|
|
2713
2718
|
if (openaiKey !== void 0) {
|
|
2714
|
-
|
|
2719
|
+
logger11.info("Using OpenAI API adapter (AI SDK)", { model: codexModelId });
|
|
2715
2720
|
return {
|
|
2716
2721
|
adapter: new SdkAdapter({ providerId: "openai", modelId: codexModelId, apiKey: openaiKey }),
|
|
2717
2722
|
source: "api",
|
|
@@ -2721,7 +2726,7 @@ function tryApiAdapter(config, logger8) {
|
|
|
2721
2726
|
}
|
|
2722
2727
|
const googleKey = resolveApiKeyFromEnv(config.googleApiKey, "GOOGLE_AI_API_KEY");
|
|
2723
2728
|
if (googleKey !== void 0) {
|
|
2724
|
-
|
|
2729
|
+
logger11.info("Using Google AI API adapter (AI SDK)", { model: geminiModelId });
|
|
2725
2730
|
return {
|
|
2726
2731
|
adapter: new SdkAdapter({
|
|
2727
2732
|
providerId: "google",
|
|
@@ -2733,19 +2738,19 @@ function tryApiAdapter(config, logger8) {
|
|
|
2733
2738
|
reason: `Using Google AI API via AI SDK (model: ${geminiModelId})`
|
|
2734
2739
|
};
|
|
2735
2740
|
}
|
|
2736
|
-
const custom = tryCustomOpenAiAdapter(
|
|
2741
|
+
const custom = tryCustomOpenAiAdapter(logger11);
|
|
2737
2742
|
if (custom !== null) return custom;
|
|
2738
|
-
|
|
2743
|
+
logger11.info("No API keys available for any provider");
|
|
2739
2744
|
return null;
|
|
2740
2745
|
}
|
|
2741
|
-
function tryCustomOpenAiAdapter(
|
|
2746
|
+
function tryCustomOpenAiAdapter(logger11) {
|
|
2742
2747
|
const customKey = resolveApiKeyFromEnv(void 0, "NEXUS_CUSTOM_API_KEY");
|
|
2743
2748
|
const customBaseUrl = process.env["NEXUS_CUSTOM_API_BASE_URL"];
|
|
2744
2749
|
if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
|
|
2745
2750
|
return null;
|
|
2746
2751
|
}
|
|
2747
2752
|
const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? CUSTOM_API_DEFAULT_MODEL;
|
|
2748
|
-
|
|
2753
|
+
logger11.info("Using custom-openai SDK adapter", {
|
|
2749
2754
|
model: customModelId,
|
|
2750
2755
|
baseUrl: customBaseUrl
|
|
2751
2756
|
});
|
|
@@ -2761,52 +2766,52 @@ function tryCustomOpenAiAdapter(logger8) {
|
|
|
2761
2766
|
reason: `Using custom OpenAI-compatible gateway at ${customBaseUrl} (model: ${customModelId})`
|
|
2762
2767
|
};
|
|
2763
2768
|
}
|
|
2764
|
-
async function selectCliFirst(config,
|
|
2765
|
-
const cliResult = await tryCliAdapter(config,
|
|
2769
|
+
async function selectCliFirst(config, logger11, cache) {
|
|
2770
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2766
2771
|
if (cliResult !== null) return cliResult;
|
|
2767
|
-
const apiResult = tryApiAdapter(config,
|
|
2772
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2768
2773
|
if (apiResult !== null) return apiResult;
|
|
2769
2774
|
throw new Error(
|
|
2770
2775
|
"No adapters available. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
2771
2776
|
);
|
|
2772
2777
|
}
|
|
2773
|
-
async function selectApiFirst(config,
|
|
2774
|
-
const apiResult = tryApiAdapter(config,
|
|
2778
|
+
async function selectApiFirst(config, logger11, cache) {
|
|
2779
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2775
2780
|
if (apiResult !== null) return apiResult;
|
|
2776
|
-
const cliResult = await tryCliAdapter(config,
|
|
2781
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2777
2782
|
if (cliResult !== null) return cliResult;
|
|
2778
2783
|
throw new Error(
|
|
2779
2784
|
"No adapters available. Set ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_AI_API_KEY or install a CLI (claude/gemini/codex)."
|
|
2780
2785
|
);
|
|
2781
2786
|
}
|
|
2782
|
-
async function selectCliOnly(config,
|
|
2783
|
-
const cliResult = await tryCliAdapter(config,
|
|
2787
|
+
async function selectCliOnly(config, logger11, cache) {
|
|
2788
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2784
2789
|
if (cliResult !== null) return cliResult;
|
|
2785
2790
|
throw new Error(
|
|
2786
2791
|
"No CLI adapters available. Install and authenticate claude, gemini, or codex CLI."
|
|
2787
2792
|
);
|
|
2788
2793
|
}
|
|
2789
|
-
function selectApiOnly(config,
|
|
2790
|
-
const apiResult = tryApiAdapter(config,
|
|
2794
|
+
function selectApiOnly(config, logger11) {
|
|
2795
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2791
2796
|
if (apiResult !== null) return apiResult;
|
|
2792
2797
|
throw new Error(
|
|
2793
2798
|
"No API key available. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
2794
2799
|
);
|
|
2795
2800
|
}
|
|
2796
2801
|
async function createAutoAdapter(config = {}) {
|
|
2797
|
-
const
|
|
2802
|
+
const logger11 = config.logger ?? defaultLogger;
|
|
2798
2803
|
const priority = config.priority ?? "cli-first";
|
|
2799
|
-
const cache = resolveCache(config,
|
|
2800
|
-
|
|
2804
|
+
const cache = resolveCache(config, logger11);
|
|
2805
|
+
logger11.info("Auto-selecting adapter", { priority, cacheEnabled: cache !== void 0 });
|
|
2801
2806
|
switch (priority) {
|
|
2802
2807
|
case "cli-first":
|
|
2803
|
-
return selectCliFirst(config,
|
|
2808
|
+
return selectCliFirst(config, logger11, cache);
|
|
2804
2809
|
case "api-first":
|
|
2805
|
-
return selectApiFirst(config,
|
|
2810
|
+
return selectApiFirst(config, logger11, cache);
|
|
2806
2811
|
case "cli-only":
|
|
2807
|
-
return selectCliOnly(config,
|
|
2812
|
+
return selectCliOnly(config, logger11, cache);
|
|
2808
2813
|
case "api-only":
|
|
2809
|
-
return selectApiOnly(config,
|
|
2814
|
+
return selectApiOnly(config, logger11);
|
|
2810
2815
|
default: {
|
|
2811
2816
|
const exhaustive = priority;
|
|
2812
2817
|
throw new Error(`Unknown priority: ${String(exhaustive)}`);
|
|
@@ -4402,21 +4407,21 @@ async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs) {
|
|
|
4402
4407
|
}
|
|
4403
4408
|
}
|
|
4404
4409
|
async function executeWithRetries(opts) {
|
|
4405
|
-
const { role, proposal, adapter, logger:
|
|
4410
|
+
const { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries } = opts;
|
|
4406
4411
|
let lastError = "";
|
|
4407
4412
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
4408
4413
|
if (attempt > 0) {
|
|
4409
4414
|
const isRateLimit = isRateLimitError(lastError);
|
|
4410
4415
|
const baseDelay = isRateLimit ? RATE_LIMIT_RETRY_DELAY_MS : INITIAL_RETRY_DELAY_MS;
|
|
4411
4416
|
const delayMs = baseDelay * Math.pow(2, attempt - 1);
|
|
4412
|
-
|
|
4417
|
+
logger11.debug("Retrying vote execution", { role, attempt, delayMs, isRateLimit });
|
|
4413
4418
|
await delay(delayMs);
|
|
4414
4419
|
}
|
|
4415
4420
|
const attemptStart = Date.now();
|
|
4416
4421
|
const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs);
|
|
4417
4422
|
const attemptMs = Date.now() - attemptStart;
|
|
4418
4423
|
if (result.ok) {
|
|
4419
|
-
|
|
4424
|
+
logger11.info("Vote attempt timing", {
|
|
4420
4425
|
role,
|
|
4421
4426
|
attempt: attempt + 1,
|
|
4422
4427
|
attemptMs,
|
|
@@ -4426,14 +4431,14 @@ async function executeWithRetries(opts) {
|
|
|
4426
4431
|
}
|
|
4427
4432
|
lastError = result.error;
|
|
4428
4433
|
const rateLimited = isRateLimitError(lastError);
|
|
4429
|
-
|
|
4434
|
+
logger11.info("Vote attempt timing", {
|
|
4430
4435
|
role,
|
|
4431
4436
|
attempt: attempt + 1,
|
|
4432
4437
|
attemptMs,
|
|
4433
4438
|
succeeded: false,
|
|
4434
4439
|
rateLimited
|
|
4435
4440
|
});
|
|
4436
|
-
|
|
4441
|
+
logger11.warn("Vote attempt failed", {
|
|
4437
4442
|
role,
|
|
4438
4443
|
attempt: attempt + 1,
|
|
4439
4444
|
maxRetries: maxRetries + 1,
|
|
@@ -4463,7 +4468,7 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
4463
4468
|
proposal,
|
|
4464
4469
|
roleAdapters,
|
|
4465
4470
|
fallbackAdapter,
|
|
4466
|
-
logger:
|
|
4471
|
+
logger: logger11,
|
|
4467
4472
|
voteOptions,
|
|
4468
4473
|
interDelay,
|
|
4469
4474
|
overallDeadlineMs,
|
|
@@ -4475,12 +4480,12 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
4475
4480
|
const adapter = roleAdapters.get(role) ?? fallbackAdapter;
|
|
4476
4481
|
const elapsed = Date.now() - startedAt;
|
|
4477
4482
|
const remaining = Math.max(1, overallDeadlineMs - elapsed);
|
|
4478
|
-
return raceWithDeadline(voteFn(role, proposal, adapter,
|
|
4483
|
+
return raceWithDeadline(voteFn(role, proposal, adapter, logger11, voteOptions), role, remaining);
|
|
4479
4484
|
});
|
|
4480
4485
|
const results = await Promise.all(wrapped);
|
|
4481
4486
|
const expired = results.filter((r) => r.source === "error" && r.error === DEADLINE_MESSAGE);
|
|
4482
4487
|
if (expired.length > 0) {
|
|
4483
|
-
|
|
4488
|
+
logger11.warn("Consensus overall deadline reached; returning partial results", {
|
|
4484
4489
|
totalRoles: roles.length,
|
|
4485
4490
|
expiredRoles: expired.map((r) => r.role),
|
|
4486
4491
|
overallDeadlineMs
|
|
@@ -4497,32 +4502,32 @@ function computeOverallConsensusDeadlineMs(timeoutMs, maxRetries, roleCount, int
|
|
|
4497
4502
|
}
|
|
4498
4503
|
var DEFAULT_INTER_AGENT_DELAY_MS = 2e3;
|
|
4499
4504
|
var defaultLogger2 = createLogger({ component: "voter-agents" });
|
|
4500
|
-
async function executeAgentVote(role, proposal, adapter,
|
|
4505
|
+
async function executeAgentVote(role, proposal, adapter, logger11, options) {
|
|
4501
4506
|
const start = getTimeProvider().now();
|
|
4502
4507
|
const timeoutMs = options?.timeoutMs ?? resolveVoteTimeout();
|
|
4503
4508
|
const maxRetries = options?.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
4504
4509
|
const allowSimulation = options?.allowSimulation ?? false;
|
|
4505
|
-
|
|
4510
|
+
logger11.info("Executing vote", { role, model: adapter.modelId, provider: adapter.providerId });
|
|
4506
4511
|
const result = await executeWithRetries({
|
|
4507
4512
|
role,
|
|
4508
4513
|
proposal,
|
|
4509
4514
|
adapter,
|
|
4510
|
-
logger:
|
|
4515
|
+
logger: logger11,
|
|
4511
4516
|
timeoutMs,
|
|
4512
4517
|
maxRetries
|
|
4513
4518
|
});
|
|
4514
4519
|
const processingTimeMs = getTimeProvider().now() - start;
|
|
4515
4520
|
if (result.ok) {
|
|
4516
|
-
|
|
4521
|
+
logger11.info("Vote completed", { role, model: adapter.modelId, decision: result.vote.decision });
|
|
4517
4522
|
return { role, vote: result.vote, processingTimeMs, source: "llm", cli: adapter.providerId };
|
|
4518
4523
|
}
|
|
4519
|
-
|
|
4524
|
+
logger11.error("Vote execution failed after all retries", void 0, {
|
|
4520
4525
|
role,
|
|
4521
4526
|
model: adapter.modelId,
|
|
4522
4527
|
errorMessage: result.error
|
|
4523
4528
|
});
|
|
4524
4529
|
if (allowSimulation) {
|
|
4525
|
-
|
|
4530
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)", { role });
|
|
4526
4531
|
return createSimulationVoteResult(role, proposal, processingTimeMs, result.error);
|
|
4527
4532
|
}
|
|
4528
4533
|
return createErrorVoteResult(role, result.error, processingTimeMs);
|
|
@@ -4533,22 +4538,22 @@ var NoAdapterError = class extends Error {
|
|
|
4533
4538
|
this.name = "NoAdapterError";
|
|
4534
4539
|
}
|
|
4535
4540
|
};
|
|
4536
|
-
function resolveAdapter(options,
|
|
4541
|
+
function resolveAdapter(options, logger11) {
|
|
4537
4542
|
try {
|
|
4538
4543
|
if (options.adapter !== void 0) return { adapter: options.adapter };
|
|
4539
|
-
const registry = getGlobalRegistry({ logger:
|
|
4544
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
4540
4545
|
return { adapter: registry.getDefault() };
|
|
4541
4546
|
} catch (error) {
|
|
4542
4547
|
return { error: getErrorMessage(error) };
|
|
4543
4548
|
}
|
|
4544
4549
|
}
|
|
4545
|
-
function warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
4550
|
+
function warnIfCodexConcurrencyExceeded(roleAdapters, logger11) {
|
|
4546
4551
|
const codexBound = [...roleAdapters.values()].filter(
|
|
4547
4552
|
(a) => a.name === "codex"
|
|
4548
4553
|
).length;
|
|
4549
4554
|
const warning = checkCodexConcurrency(codexBound);
|
|
4550
4555
|
if (warning !== null) {
|
|
4551
|
-
|
|
4556
|
+
logger11.warn("Codex concurrency limit may be exceeded", { detail: warning });
|
|
4552
4557
|
}
|
|
4553
4558
|
}
|
|
4554
4559
|
function assignUniformAdapter(roles, adapter) {
|
|
@@ -4556,29 +4561,29 @@ function assignUniformAdapter(roles, adapter) {
|
|
|
4556
4561
|
for (const role of roles) adapters.set(role, adapter);
|
|
4557
4562
|
return adapters;
|
|
4558
4563
|
}
|
|
4559
|
-
function createCliAdapterMap(clis,
|
|
4560
|
-
const registry = getGlobalRegistry({ logger:
|
|
4564
|
+
function createCliAdapterMap(clis, logger11) {
|
|
4565
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
4561
4566
|
const result = /* @__PURE__ */ new Map();
|
|
4562
4567
|
for (const cli of clis) {
|
|
4563
4568
|
result.set(cli, registry.getAdapterForCli(cli));
|
|
4564
4569
|
}
|
|
4565
4570
|
return result;
|
|
4566
4571
|
}
|
|
4567
|
-
async function resolveDiverseAdapters(roles,
|
|
4572
|
+
async function resolveDiverseAdapters(roles, logger11, fallbackAdapter) {
|
|
4568
4573
|
let availableClis;
|
|
4569
4574
|
try {
|
|
4570
4575
|
availableClis = await getAvailableClis();
|
|
4571
4576
|
} catch (e) {
|
|
4572
|
-
|
|
4577
|
+
logger11.warn("Failed to resolve available CLIs; falling back to single adapter", {
|
|
4573
4578
|
error: String(e)
|
|
4574
4579
|
});
|
|
4575
4580
|
availableClis = [];
|
|
4576
4581
|
}
|
|
4577
4582
|
if (availableClis.length <= 1) {
|
|
4578
|
-
|
|
4583
|
+
logger11.info("Using single adapter for all roles", { cliCount: availableClis.length });
|
|
4579
4584
|
return assignUniformAdapter(roles, fallbackAdapter);
|
|
4580
4585
|
}
|
|
4581
|
-
const cliAdapters = createCliAdapterMap(availableClis,
|
|
4586
|
+
const cliAdapters = createCliAdapterMap(availableClis, logger11);
|
|
4582
4587
|
if (cliAdapters.size <= 1) return assignUniformAdapter(roles, fallbackAdapter);
|
|
4583
4588
|
const cliList = [...cliAdapters.entries()];
|
|
4584
4589
|
const adapters = /* @__PURE__ */ new Map();
|
|
@@ -4590,7 +4595,7 @@ async function resolveDiverseAdapters(roles, logger8, fallbackAdapter) {
|
|
|
4590
4595
|
adapters.set(role, entry[1]);
|
|
4591
4596
|
assignments[role] = entry[0];
|
|
4592
4597
|
}
|
|
4593
|
-
|
|
4598
|
+
logger11.info("Diverse adapters assigned", {
|
|
4594
4599
|
cliCount: cliAdapters.size,
|
|
4595
4600
|
clis: [...cliAdapters.keys()],
|
|
4596
4601
|
roleAssignments: assignments
|
|
@@ -4598,7 +4603,7 @@ async function resolveDiverseAdapters(roles, logger8, fallbackAdapter) {
|
|
|
4598
4603
|
return adapters;
|
|
4599
4604
|
}
|
|
4600
4605
|
async function launchStaggeredVotes(input) {
|
|
4601
|
-
const { roles, proposal, roleAdapters, fallbackAdapter, logger:
|
|
4606
|
+
const { roles, proposal, roleAdapters, fallbackAdapter, logger: logger11, voteOptions, interDelay } = input;
|
|
4602
4607
|
const computedDeadlineMs = computeOverallConsensusDeadlineMs(
|
|
4603
4608
|
voteOptions.timeoutMs,
|
|
4604
4609
|
voteOptions.maxRetries,
|
|
@@ -4607,7 +4612,7 @@ async function launchStaggeredVotes(input) {
|
|
|
4607
4612
|
);
|
|
4608
4613
|
const overallDeadlineMs = getMcpSafeDeadlineMs(computedDeadlineMs, "consensus_vote");
|
|
4609
4614
|
if (overallDeadlineMs < computedDeadlineMs) {
|
|
4610
|
-
|
|
4615
|
+
logger11.debug("Consensus deadline clamped to MCP wrapper timeout", {
|
|
4611
4616
|
computedDeadlineMs,
|
|
4612
4617
|
overallDeadlineMs
|
|
4613
4618
|
});
|
|
@@ -4617,7 +4622,7 @@ async function launchStaggeredVotes(input) {
|
|
|
4617
4622
|
proposal,
|
|
4618
4623
|
roleAdapters,
|
|
4619
4624
|
fallbackAdapter,
|
|
4620
|
-
logger:
|
|
4625
|
+
logger: logger11,
|
|
4621
4626
|
voteOptions,
|
|
4622
4627
|
interDelay,
|
|
4623
4628
|
overallDeadlineMs,
|
|
@@ -4625,27 +4630,27 @@ async function launchStaggeredVotes(input) {
|
|
|
4625
4630
|
});
|
|
4626
4631
|
}
|
|
4627
4632
|
async function collectRealVotes(options) {
|
|
4628
|
-
const
|
|
4633
|
+
const logger11 = options.logger ?? defaultLogger2;
|
|
4629
4634
|
const { roles, proposal, simulate, allowSimulation } = options;
|
|
4630
4635
|
const timeoutMs = options.timeoutMs ?? resolveVoteTimeout();
|
|
4631
4636
|
const maxRetries = options.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
4632
4637
|
if (simulate === true) {
|
|
4633
|
-
|
|
4638
|
+
logger11.info("Using simulation mode (explicitly requested)");
|
|
4634
4639
|
return createSimulatedVotes(roles, proposal);
|
|
4635
4640
|
}
|
|
4636
|
-
const adapterResult = resolveAdapter(options,
|
|
4641
|
+
const adapterResult = resolveAdapter(options, logger11);
|
|
4637
4642
|
if ("error" in adapterResult) {
|
|
4638
|
-
|
|
4643
|
+
logger11.error("No adapter available for voting", void 0, { error: adapterResult.error });
|
|
4639
4644
|
if (allowSimulation === true) {
|
|
4640
|
-
|
|
4645
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)");
|
|
4641
4646
|
return createSimulatedVotes(roles, proposal, "No adapter available");
|
|
4642
4647
|
}
|
|
4643
4648
|
throw new NoAdapterError(
|
|
4644
4649
|
`No adapter available for voting: ${adapterResult.error}. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY.`
|
|
4645
4650
|
);
|
|
4646
4651
|
}
|
|
4647
|
-
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles,
|
|
4648
|
-
warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
4652
|
+
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles, logger11, adapterResult.adapter);
|
|
4653
|
+
warnIfCodexConcurrencyExceeded(roleAdapters, logger11);
|
|
4649
4654
|
const voteOptions = { timeoutMs, maxRetries, allowSimulation: allowSimulation ?? false };
|
|
4650
4655
|
const interDelay = options.interAgentDelayMs ?? DEFAULT_INTER_AGENT_DELAY_MS;
|
|
4651
4656
|
return launchStaggeredVotes({
|
|
@@ -4653,7 +4658,7 @@ async function collectRealVotes(options) {
|
|
|
4653
4658
|
proposal,
|
|
4654
4659
|
roleAdapters,
|
|
4655
4660
|
fallbackAdapter: adapterResult.adapter,
|
|
4656
|
-
logger:
|
|
4661
|
+
logger: logger11,
|
|
4657
4662
|
voteOptions,
|
|
4658
4663
|
interDelay
|
|
4659
4664
|
});
|
|
@@ -5642,11 +5647,11 @@ var ConsensusEngine = class {
|
|
|
5642
5647
|
logger;
|
|
5643
5648
|
metrics;
|
|
5644
5649
|
voterExpansionCallback;
|
|
5645
|
-
constructor(config,
|
|
5650
|
+
constructor(config, logger11) {
|
|
5646
5651
|
this.config = { ...DEFAULT_CONSENSUS_CONFIG, ...config };
|
|
5647
5652
|
this.cacheConfig = { ...DEFAULT_PROPOSAL_CACHE_CONFIG, ...config?.proposalCache };
|
|
5648
5653
|
this.quorumConfig = { ...DEFAULT_INCREMENTAL_QUORUM_CONFIG, ...config?.incrementalQuorum };
|
|
5649
|
-
this.logger =
|
|
5654
|
+
this.logger = logger11 ?? createLogger({ component: "ConsensusEngine" });
|
|
5650
5655
|
this.strategyFactory = new VotingStrategyFactory();
|
|
5651
5656
|
this.metrics = this.createInitialMetrics();
|
|
5652
5657
|
}
|
|
@@ -6088,8 +6093,8 @@ var ConsensusEngine = class {
|
|
|
6088
6093
|
this.logger.debug("Proposal cache cleared");
|
|
6089
6094
|
}
|
|
6090
6095
|
};
|
|
6091
|
-
function createConsensusEngine(config,
|
|
6092
|
-
return new ConsensusEngine(config,
|
|
6096
|
+
function createConsensusEngine(config, logger11) {
|
|
6097
|
+
return new ConsensusEngine(config, logger11);
|
|
6093
6098
|
}
|
|
6094
6099
|
|
|
6095
6100
|
// src/consensus/quorum-validator.ts
|
|
@@ -6097,8 +6102,8 @@ var DEFAULT_MIN_TRUST = 0.3;
|
|
|
6097
6102
|
var DEFAULT_MIN_WEIGHT = 0.1;
|
|
6098
6103
|
var QuorumValidator = class {
|
|
6099
6104
|
logger;
|
|
6100
|
-
constructor(
|
|
6101
|
-
this.logger =
|
|
6105
|
+
constructor(logger11) {
|
|
6106
|
+
this.logger = logger11 ?? createLogger({ component: "QuorumValidator" });
|
|
6102
6107
|
}
|
|
6103
6108
|
validateQuorum(input) {
|
|
6104
6109
|
const { votes, config } = input;
|
|
@@ -6325,8 +6330,8 @@ var QuorumValidator = class {
|
|
|
6325
6330
|
};
|
|
6326
6331
|
}
|
|
6327
6332
|
};
|
|
6328
|
-
function createQuorumValidator(
|
|
6329
|
-
return new QuorumValidator(
|
|
6333
|
+
function createQuorumValidator(logger11) {
|
|
6334
|
+
return new QuorumValidator(logger11);
|
|
6330
6335
|
}
|
|
6331
6336
|
|
|
6332
6337
|
// src/consensus/voting-protocol-helpers.ts
|
|
@@ -7906,6 +7911,35 @@ var ConsensusVoteInputSchema = z9.object({
|
|
|
7906
7911
|
quickMode: z9.boolean().optional().default(false).describe("Use 3 agents instead of 5 for faster execution"),
|
|
7907
7912
|
simulateVotes: z9.boolean().optional().default(false).describe(
|
|
7908
7913
|
"TESTS ONLY \u2014 when true, voters return random decisions. Output must not be used for real decisions. (#2319)"
|
|
7914
|
+
),
|
|
7915
|
+
/**
|
|
7916
|
+
* Async-mode dispatch (#3045, Stage 4 of epic #2631). Default `sync` —
|
|
7917
|
+
* backward-compat invariant. `async` returns `{ status: 'pending', jobId }`
|
|
7918
|
+
* immediately; caller polls `get_job_result(jobId)`. Per-tool cap via
|
|
7919
|
+
* `NEXUS_JOB_MAX_CONCURRENT_CONSENSUS_VOTE` (default 2 — voting is
|
|
7920
|
+
* 7-fan-out so concurrent jobs multiply adapter load fast).
|
|
7921
|
+
*
|
|
7922
|
+
* Cancellation semantics (#3041 vote deferred this to Stage 4): when
|
|
7923
|
+
* a polling client calls `cancel_job` mid-vote, the dispatcher aborts
|
|
7924
|
+
* in-flight voters via the AbortSignal plumbing from #3038. The
|
|
7925
|
+
* resulting job result is `{ status: 'cancelled', partialVotes: [...] }`
|
|
7926
|
+
* with whatever voters completed before the abort signal — preserves
|
|
7927
|
+
* audit visibility into who voted before the cancel landed.
|
|
7928
|
+
*
|
|
7929
|
+
* Kept optional (no `.default()`) so the inferred type doesn't force
|
|
7930
|
+
* `mode: 'sync'` on every existing call site / test fixture.
|
|
7931
|
+
*/
|
|
7932
|
+
mode: z9.enum(["sync", "async"]).optional().describe(
|
|
7933
|
+
'Dispatch mode (default: sync). Use "async" for higher-order strategies with 7 voters.'
|
|
7934
|
+
),
|
|
7935
|
+
/**
|
|
7936
|
+
* Idempotency key for async-mode replay-safety (#3042 Stage 1c / epic
|
|
7937
|
+
* #2631). When set: identical (key, inputs) returns the existing job;
|
|
7938
|
+
* same key with different inputs fails closed with
|
|
7939
|
+
* `idempotency_key_collision`. Sync mode ignores this.
|
|
7940
|
+
*/
|
|
7941
|
+
idempotencyKey: z9.string().min(1).max(256).optional().describe(
|
|
7942
|
+
"Replay-safe key for async-mode dispatch (#3042 Stage 1c). Same (key, inputs) returns existing jobId."
|
|
7909
7943
|
)
|
|
7910
7944
|
});
|
|
7911
7945
|
function toAgentVoteSummary(result) {
|
|
@@ -8012,10 +8046,10 @@ import { appendFileSync as appendFileSync3, readFileSync as readFileSync2, write
|
|
|
8012
8046
|
var PersistentOutcomeStore = class extends OutcomeStore {
|
|
8013
8047
|
filePath;
|
|
8014
8048
|
logger;
|
|
8015
|
-
constructor(config,
|
|
8049
|
+
constructor(config, logger11) {
|
|
8016
8050
|
super(config);
|
|
8017
8051
|
this.filePath = config?.filePath ?? getOutcomesFile();
|
|
8018
|
-
this.logger =
|
|
8052
|
+
this.logger = logger11 ?? createLogger({ component: "PersistentOutcomeStore" });
|
|
8019
8053
|
const dataDir = config?.dataDir;
|
|
8020
8054
|
ensureLearningDir(dataDir);
|
|
8021
8055
|
this.hydrate();
|
|
@@ -8209,11 +8243,11 @@ var WARNED = /* @__PURE__ */ new Set();
|
|
|
8209
8243
|
function isTestRunner() {
|
|
8210
8244
|
return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
8211
8245
|
}
|
|
8212
|
-
function warnIfSimulatedOutsideTests(toolName,
|
|
8246
|
+
function warnIfSimulatedOutsideTests(toolName, logger11) {
|
|
8213
8247
|
if (isTestRunner()) return;
|
|
8214
8248
|
if (WARNED.has(toolName)) return;
|
|
8215
8249
|
WARNED.add(toolName);
|
|
8216
|
-
|
|
8250
|
+
logger11.warn(
|
|
8217
8251
|
`[${toolName}] simulateVotes=true: output is RANDOM and reserved for tests/demos. Do not treat the result as a real decision.`
|
|
8218
8252
|
);
|
|
8219
8253
|
}
|
|
@@ -8457,6 +8491,20 @@ var TOOL_ANNOTATIONS = {
|
|
|
8457
8491
|
idempotentHint: true,
|
|
8458
8492
|
openWorldHint: false
|
|
8459
8493
|
},
|
|
8494
|
+
/** Reads job-result sidecar file for an async-mode dispatch (#3042 / #2631). */
|
|
8495
|
+
get_job_result: {
|
|
8496
|
+
readOnlyHint: true,
|
|
8497
|
+
destructiveHint: false,
|
|
8498
|
+
idempotentHint: true,
|
|
8499
|
+
openWorldHint: false
|
|
8500
|
+
},
|
|
8501
|
+
/** Lists async-mode job summaries from the sidecar dir (#3046 / #2631 Stage 5). */
|
|
8502
|
+
list_jobs: {
|
|
8503
|
+
readOnlyHint: true,
|
|
8504
|
+
destructiveHint: false,
|
|
8505
|
+
idempotentHint: true,
|
|
8506
|
+
openWorldHint: false
|
|
8507
|
+
},
|
|
8460
8508
|
/** Verifies hash chain of audit log files. */
|
|
8461
8509
|
verify_audit_chain: {
|
|
8462
8510
|
readOnlyHint: true,
|
|
@@ -8530,7 +8578,321 @@ function getToolAnnotations(name) {
|
|
|
8530
8578
|
return a;
|
|
8531
8579
|
}
|
|
8532
8580
|
|
|
8581
|
+
// src/mcp/jobs/job-result-store.ts
|
|
8582
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8583
|
+
import { z as z10 } from "zod";
|
|
8584
|
+
var logger8 = createLogger({ component: "job-result-store" });
|
|
8585
|
+
var JobStatusSchema = z10.enum(["pending", "complete", "failed", "cancelled"]);
|
|
8586
|
+
var JobResultSchema = z10.object({
|
|
8587
|
+
/** Format version. Currently `1` — bump if the shape changes. */
|
|
8588
|
+
v: z10.literal(1),
|
|
8589
|
+
jobId: z10.string().min(1),
|
|
8590
|
+
/** Tool that was invoked (e.g. `orchestrate`). */
|
|
8591
|
+
toolName: z10.string().min(1),
|
|
8592
|
+
status: JobStatusSchema,
|
|
8593
|
+
createdAt: z10.iso.datetime(),
|
|
8594
|
+
/** Set when the job leaves `pending` — either via `complete` or `failed` or `cancelled`. */
|
|
8595
|
+
completedAt: z10.iso.datetime().optional(),
|
|
8596
|
+
/**
|
|
8597
|
+
* Structured payload the synchronous mode would have returned. Present
|
|
8598
|
+
* only when `status === 'complete'`. Shape is tool-specific — readers
|
|
8599
|
+
* cast to the tool's known output type after status check.
|
|
8600
|
+
*/
|
|
8601
|
+
result: z10.unknown().optional(),
|
|
8602
|
+
/**
|
|
8603
|
+
* Failure message when `status === 'failed'`. Cannot be paired with
|
|
8604
|
+
* `result` — the discriminator is `status`.
|
|
8605
|
+
*/
|
|
8606
|
+
error: z10.string().optional()
|
|
8607
|
+
});
|
|
8608
|
+
function jobResultPath(jobId) {
|
|
8609
|
+
return nexusDataPathEnsure("jobs", `result-${jobId}.json`);
|
|
8610
|
+
}
|
|
8611
|
+
function writeJobPending(jobId, toolName) {
|
|
8612
|
+
const path = jobResultPath(jobId);
|
|
8613
|
+
if (existsSync4(path)) {
|
|
8614
|
+
logger8.debug("Job result file already exists \u2014 leaving in place", { jobId });
|
|
8615
|
+
return;
|
|
8616
|
+
}
|
|
8617
|
+
const record = {
|
|
8618
|
+
v: 1,
|
|
8619
|
+
jobId,
|
|
8620
|
+
toolName,
|
|
8621
|
+
status: "pending",
|
|
8622
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8623
|
+
};
|
|
8624
|
+
writeFileSync3(path, JSON.stringify(record, null, 2));
|
|
8625
|
+
logger8.debug("Wrote pending job record", { jobId, toolName });
|
|
8626
|
+
}
|
|
8627
|
+
function writeJobComplete(jobId, toolName, result) {
|
|
8628
|
+
const record = {
|
|
8629
|
+
v: 1,
|
|
8630
|
+
jobId,
|
|
8631
|
+
toolName,
|
|
8632
|
+
status: "complete",
|
|
8633
|
+
createdAt: readJobResult(jobId)?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8634
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8635
|
+
result
|
|
8636
|
+
};
|
|
8637
|
+
writeFileSync3(jobResultPath(jobId), JSON.stringify(record, null, 2));
|
|
8638
|
+
logger8.debug("Wrote complete job record", { jobId, toolName });
|
|
8639
|
+
}
|
|
8640
|
+
function writeJobFailed(jobId, toolName, error) {
|
|
8641
|
+
const record = {
|
|
8642
|
+
v: 1,
|
|
8643
|
+
jobId,
|
|
8644
|
+
toolName,
|
|
8645
|
+
status: "failed",
|
|
8646
|
+
createdAt: readJobResult(jobId)?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8647
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8648
|
+
error
|
|
8649
|
+
};
|
|
8650
|
+
writeFileSync3(jobResultPath(jobId), JSON.stringify(record, null, 2));
|
|
8651
|
+
logger8.debug("Wrote failed job record", { jobId, toolName, error });
|
|
8652
|
+
}
|
|
8653
|
+
function readJobResult(jobId) {
|
|
8654
|
+
const path = nexusDataPath("jobs", `result-${jobId}.json`);
|
|
8655
|
+
if (!existsSync4(path)) return null;
|
|
8656
|
+
try {
|
|
8657
|
+
const raw = JSON.parse(readFileSync3(path, "utf-8"));
|
|
8658
|
+
const parsed = JobResultSchema.safeParse(raw);
|
|
8659
|
+
if (!parsed.success) {
|
|
8660
|
+
logger8.warn("Job result file failed schema check", { jobId, path });
|
|
8661
|
+
return null;
|
|
8662
|
+
}
|
|
8663
|
+
return parsed.data;
|
|
8664
|
+
} catch (err2) {
|
|
8665
|
+
logger8.warn("Job result file unreadable", {
|
|
8666
|
+
jobId,
|
|
8667
|
+
path,
|
|
8668
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8669
|
+
});
|
|
8670
|
+
return null;
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
function listJobs() {
|
|
8674
|
+
const dir = nexusDataPath("jobs");
|
|
8675
|
+
if (!existsSync4(dir)) return [];
|
|
8676
|
+
let entries;
|
|
8677
|
+
try {
|
|
8678
|
+
entries = readdirSync(dir);
|
|
8679
|
+
} catch (err2) {
|
|
8680
|
+
logger8.warn("jobs directory unreadable", {
|
|
8681
|
+
dir,
|
|
8682
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8683
|
+
});
|
|
8684
|
+
return [];
|
|
8685
|
+
}
|
|
8686
|
+
const summaries = [];
|
|
8687
|
+
for (const entry of entries) {
|
|
8688
|
+
const match = /^result-(.+)\.json$/.exec(entry);
|
|
8689
|
+
if (match === null) continue;
|
|
8690
|
+
const jobId = match[1];
|
|
8691
|
+
if (jobId === void 0) continue;
|
|
8692
|
+
const record = readJobResult(jobId);
|
|
8693
|
+
if (record === null) continue;
|
|
8694
|
+
const summary = {
|
|
8695
|
+
jobId: record.jobId,
|
|
8696
|
+
toolName: record.toolName,
|
|
8697
|
+
status: record.status,
|
|
8698
|
+
createdAt: record.createdAt,
|
|
8699
|
+
hasError: record.error !== void 0,
|
|
8700
|
+
...record.completedAt !== void 0 ? { completedAt: record.completedAt } : {}
|
|
8701
|
+
};
|
|
8702
|
+
summaries.push(summary);
|
|
8703
|
+
}
|
|
8704
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
8705
|
+
}
|
|
8706
|
+
|
|
8707
|
+
// src/mcp/jobs/job-idempotency.ts
|
|
8708
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
8709
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
8710
|
+
import { z as z11 } from "zod";
|
|
8711
|
+
var logger9 = createLogger({ component: "job-idempotency" });
|
|
8712
|
+
var IdempotencyIndexEntrySchema = z11.object({
|
|
8713
|
+
v: z11.literal(1),
|
|
8714
|
+
/** MCP tool name (e.g. `orchestrate`, `run_workflow`, `consensus_vote`). */
|
|
8715
|
+
tool: z11.string().min(1),
|
|
8716
|
+
/** Caller-supplied idempotency key. */
|
|
8717
|
+
key: z11.string().min(1),
|
|
8718
|
+
/** sha256 of the canonical input JSON. */
|
|
8719
|
+
inputsHash: z11.string().length(64),
|
|
8720
|
+
/** Existing jobId for this (tool, key, inputs) tuple. */
|
|
8721
|
+
jobId: z11.string().min(1),
|
|
8722
|
+
/** ISO timestamp of first dispatch. */
|
|
8723
|
+
createdAt: z11.iso.datetime()
|
|
8724
|
+
});
|
|
8725
|
+
function sha256Hex(input) {
|
|
8726
|
+
return createHash("sha256").update(input).digest("hex");
|
|
8727
|
+
}
|
|
8728
|
+
function canonicalJsonStringify(value) {
|
|
8729
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
8730
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJsonStringify).join(",")}]`;
|
|
8731
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
8732
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`).join(",")}}`;
|
|
8733
|
+
}
|
|
8734
|
+
function computeInputsHash(inputs) {
|
|
8735
|
+
return sha256Hex(canonicalJsonStringify(inputs));
|
|
8736
|
+
}
|
|
8737
|
+
function indexFilePath(tool, key) {
|
|
8738
|
+
const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
|
|
8739
|
+
return nexusDataPathEnsure("jobs", filename);
|
|
8740
|
+
}
|
|
8741
|
+
function indexFilePathReadOnly(tool, key) {
|
|
8742
|
+
const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
|
|
8743
|
+
return nexusDataPath("jobs", filename);
|
|
8744
|
+
}
|
|
8745
|
+
function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = randomUUID3) {
|
|
8746
|
+
if (idempotencyKey === void 0 || idempotencyKey === "") {
|
|
8747
|
+
return { kind: "fresh", jobId: randomFallback() };
|
|
8748
|
+
}
|
|
8749
|
+
const incomingInputsHash = computeInputsHash(inputs);
|
|
8750
|
+
const path = indexFilePathReadOnly(tool, idempotencyKey);
|
|
8751
|
+
if (existsSync5(path)) {
|
|
8752
|
+
const entry = readIndexEntry(path);
|
|
8753
|
+
if (entry !== null) {
|
|
8754
|
+
if (entry.inputsHash === incomingInputsHash) {
|
|
8755
|
+
return { kind: "replay", jobId: entry.jobId, entry };
|
|
8756
|
+
}
|
|
8757
|
+
return {
|
|
8758
|
+
kind: "collision",
|
|
8759
|
+
existingInputsHash: entry.inputsHash,
|
|
8760
|
+
incomingInputsHash,
|
|
8761
|
+
existingJobId: entry.jobId
|
|
8762
|
+
};
|
|
8763
|
+
}
|
|
8764
|
+
logger9.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path });
|
|
8765
|
+
}
|
|
8766
|
+
const jobId = `job-${tool}-${sha256Hex(`${tool}:${idempotencyKey}:${incomingInputsHash}`).slice(0, 16)}`;
|
|
8767
|
+
return { kind: "fresh", jobId };
|
|
8768
|
+
}
|
|
8769
|
+
function registerIdempotentJob(params) {
|
|
8770
|
+
const path = indexFilePath(params.tool, params.idempotencyKey);
|
|
8771
|
+
if (existsSync5(path)) {
|
|
8772
|
+
logger9.debug("Idempotency index entry already present; skipping write", {
|
|
8773
|
+
tool: params.tool,
|
|
8774
|
+
jobId: params.jobId
|
|
8775
|
+
});
|
|
8776
|
+
return;
|
|
8777
|
+
}
|
|
8778
|
+
const entry = {
|
|
8779
|
+
v: 1,
|
|
8780
|
+
tool: params.tool,
|
|
8781
|
+
key: params.idempotencyKey,
|
|
8782
|
+
inputsHash: computeInputsHash(params.inputs),
|
|
8783
|
+
jobId: params.jobId,
|
|
8784
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8785
|
+
};
|
|
8786
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2));
|
|
8787
|
+
logger9.debug("Wrote idempotency index entry", { tool: params.tool, jobId: params.jobId });
|
|
8788
|
+
}
|
|
8789
|
+
function shortCircuitOrFreshJobId(params) {
|
|
8790
|
+
const r = resolveIdempotency(
|
|
8791
|
+
params.tool,
|
|
8792
|
+
params.idempotencyKey,
|
|
8793
|
+
params.inputs,
|
|
8794
|
+
params.freshJobId
|
|
8795
|
+
);
|
|
8796
|
+
if (r.kind === "collision") {
|
|
8797
|
+
return { kind: "shortCircuit", value: params.collisionEnvelope(r.existingJobId) };
|
|
8798
|
+
}
|
|
8799
|
+
if (r.kind === "replay") {
|
|
8800
|
+
return { kind: "shortCircuit", value: params.replayEnvelope(r.jobId) };
|
|
8801
|
+
}
|
|
8802
|
+
return { kind: "continue", jobId: r.jobId };
|
|
8803
|
+
}
|
|
8804
|
+
function readIndexEntry(path) {
|
|
8805
|
+
try {
|
|
8806
|
+
const raw = JSON.parse(readFileSync4(path, "utf-8"));
|
|
8807
|
+
const parsed = IdempotencyIndexEntrySchema.safeParse(raw);
|
|
8808
|
+
if (!parsed.success) {
|
|
8809
|
+
logger9.warn("Idempotency index file failed schema check", { path });
|
|
8810
|
+
return null;
|
|
8811
|
+
}
|
|
8812
|
+
return parsed.data;
|
|
8813
|
+
} catch (err2) {
|
|
8814
|
+
logger9.warn("Idempotency index file read failed", {
|
|
8815
|
+
path,
|
|
8816
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8817
|
+
});
|
|
8818
|
+
return null;
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8821
|
+
|
|
8822
|
+
// src/mcp/jobs/job-concurrency.ts
|
|
8823
|
+
var logger10 = createLogger({ component: "job-concurrency" });
|
|
8824
|
+
var DEFAULT_JOB_CAPS = {
|
|
8825
|
+
orchestrate: 3,
|
|
8826
|
+
run_workflow: 3,
|
|
8827
|
+
consensus_vote: 2,
|
|
8828
|
+
execute_expert: 4
|
|
8829
|
+
};
|
|
8830
|
+
var DEFAULT_GLOBAL_JOB_CAP = 10;
|
|
8831
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
8832
|
+
function getJobCap(toolName) {
|
|
8833
|
+
const envKey = `NEXUS_JOB_MAX_CONCURRENT_${toolName.toUpperCase()}`;
|
|
8834
|
+
const envValue = process.env[envKey];
|
|
8835
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
8836
|
+
const parsed = Number(envValue);
|
|
8837
|
+
if (Number.isInteger(parsed) && parsed >= 0) {
|
|
8838
|
+
return parsed;
|
|
8839
|
+
}
|
|
8840
|
+
logger10.warn("Invalid env override for job cap \u2014 ignoring", {
|
|
8841
|
+
tool: toolName,
|
|
8842
|
+
envKey,
|
|
8843
|
+
envValue
|
|
8844
|
+
});
|
|
8845
|
+
}
|
|
8846
|
+
return DEFAULT_JOB_CAPS[toolName] ?? DEFAULT_GLOBAL_JOB_CAP;
|
|
8847
|
+
}
|
|
8848
|
+
function getGlobalJobCap() {
|
|
8849
|
+
const envValue = process.env["NEXUS_JOB_MAX_CONCURRENT_TOTAL"];
|
|
8850
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
8851
|
+
const parsed = Number(envValue);
|
|
8852
|
+
if (Number.isInteger(parsed) && parsed >= 0) {
|
|
8853
|
+
return parsed;
|
|
8854
|
+
}
|
|
8855
|
+
logger10.warn("Invalid env override for global job cap \u2014 ignoring", {
|
|
8856
|
+
envKey: "NEXUS_JOB_MAX_CONCURRENT_TOTAL",
|
|
8857
|
+
envValue
|
|
8858
|
+
});
|
|
8859
|
+
}
|
|
8860
|
+
return DEFAULT_GLOBAL_JOB_CAP;
|
|
8861
|
+
}
|
|
8862
|
+
function getTotalInFlight() {
|
|
8863
|
+
let total = 0;
|
|
8864
|
+
for (const count of inFlight.values()) total += count;
|
|
8865
|
+
return total;
|
|
8866
|
+
}
|
|
8867
|
+
function tryAcquire(toolName) {
|
|
8868
|
+
const cap = getJobCap(toolName);
|
|
8869
|
+
if (cap === 0) return false;
|
|
8870
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8871
|
+
if (current >= cap) return false;
|
|
8872
|
+
const globalCap = getGlobalJobCap();
|
|
8873
|
+
if (globalCap === 0) return false;
|
|
8874
|
+
if (getTotalInFlight() >= globalCap) return false;
|
|
8875
|
+
inFlight.set(toolName, current + 1);
|
|
8876
|
+
return true;
|
|
8877
|
+
}
|
|
8878
|
+
function release(toolName) {
|
|
8879
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8880
|
+
if (current === 0) {
|
|
8881
|
+
logger10.warn("release() called with no in-flight count \u2014 caller bug", { tool: toolName });
|
|
8882
|
+
return;
|
|
8883
|
+
}
|
|
8884
|
+
inFlight.set(toolName, current - 1);
|
|
8885
|
+
}
|
|
8886
|
+
function suggestRetryAfterMs(toolName) {
|
|
8887
|
+
const cap = getJobCap(toolName);
|
|
8888
|
+
if (cap === 0) return 0;
|
|
8889
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8890
|
+
const base = 3e4 * (current / Math.max(1, cap));
|
|
8891
|
+
return Math.min(6e4, Math.max(5e3, base));
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8533
8894
|
// src/mcp/tools/consensus-vote.ts
|
|
8895
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
8534
8896
|
var persistentCorrelationTracker;
|
|
8535
8897
|
function getOrCreateCorrelationTracker() {
|
|
8536
8898
|
persistentCorrelationTracker ??= createPersistentCorrelationTracker();
|
|
@@ -8639,22 +9001,22 @@ async function processVotesThroughEngine(engineVotes, proposal, algorithm) {
|
|
|
8639
9001
|
});
|
|
8640
9002
|
return resultRes.value;
|
|
8641
9003
|
}
|
|
8642
|
-
function runHigherOrderVoting(strategy, voteMap,
|
|
9004
|
+
function runHigherOrderVoting(strategy, voteMap, logger11) {
|
|
8643
9005
|
if (strategy !== "higher_order") return void 0;
|
|
8644
9006
|
const hovStrategy = new HigherOrderVotingStrategy();
|
|
8645
9007
|
const tracker = getOrCreateCorrelationTracker();
|
|
8646
9008
|
const result = hovStrategy.aggregate(voteMap, tracker);
|
|
8647
|
-
|
|
9009
|
+
logger11.info("Higher-Order Voting complete", {
|
|
8648
9010
|
method: result.method,
|
|
8649
9011
|
decision: result.decision,
|
|
8650
9012
|
posteriorApproval: result.posteriorApproval.toFixed(3)
|
|
8651
9013
|
});
|
|
8652
9014
|
return result;
|
|
8653
9015
|
}
|
|
8654
|
-
function recordVotesToTracker(votes, voteMap, outcome,
|
|
9016
|
+
function recordVotesToTracker(votes, voteMap, outcome, logger11) {
|
|
8655
9017
|
const allVotesReal = votes.every((v) => v.source === "llm");
|
|
8656
9018
|
if (!allVotesReal) {
|
|
8657
|
-
|
|
9019
|
+
logger11.warn("Skipping correlation recording due to non-LLM votes", {
|
|
8658
9020
|
count: votes.filter((v) => v.source !== "llm").length
|
|
8659
9021
|
});
|
|
8660
9022
|
return;
|
|
@@ -8662,16 +9024,16 @@ function recordVotesToTracker(votes, voteMap, outcome, logger8) {
|
|
|
8662
9024
|
const tracker = getOrCreateCorrelationTracker();
|
|
8663
9025
|
const id = `consensus-${String(getTimeProvider().now())}-${getRandomProvider().random().toString(36).slice(2, 9)}`;
|
|
8664
9026
|
tracker.recordProposalVotes(id, voteMap, outcome);
|
|
8665
|
-
|
|
9027
|
+
logger11.debug("Recorded votes to tracker", { proposalId: id, outcome });
|
|
8666
9028
|
try {
|
|
8667
9029
|
const persisted = createPersistedProposal(id, voteMap, outcome);
|
|
8668
9030
|
const saveResult = saveCorrelationData([persisted]);
|
|
8669
9031
|
if (!saveResult.ok) {
|
|
8670
|
-
|
|
9032
|
+
logger11.warn("Failed to persist correlation data", { error: saveResult.error.message });
|
|
8671
9033
|
}
|
|
8672
9034
|
} catch (error) {
|
|
8673
9035
|
const message = getErrorMessage(error);
|
|
8674
|
-
|
|
9036
|
+
logger11.warn("Error persisting correlation data", { error: message });
|
|
8675
9037
|
}
|
|
8676
9038
|
}
|
|
8677
9039
|
async function processVotesWithCascade(engineVotes, opts) {
|
|
@@ -8696,7 +9058,7 @@ async function processVotesWithCascade(engineVotes, opts) {
|
|
|
8696
9058
|
var CONTRARIAN_ESCALATION_THRESHOLD = 0.8;
|
|
8697
9059
|
async function runContrarianCheck(proposal, log) {
|
|
8698
9060
|
try {
|
|
8699
|
-
const { executeExpert } = await import("./expert-bridge-
|
|
9061
|
+
const { executeExpert } = await import("./expert-bridge-22UVN5TB.js");
|
|
8700
9062
|
const prompt = [
|
|
8701
9063
|
"You are a contrarian analyst. Your job is to find reasons this proposal should be REJECTED.",
|
|
8702
9064
|
"Look for: YAGNI (not needed), MISALIGNED (wrong tech/architecture), SECURITY_RISK, SCOPE_CREEP.",
|
|
@@ -8746,23 +9108,23 @@ function buildPolicyShortCircuitResult(args) {
|
|
|
8746
9108
|
strategy: args.strategy
|
|
8747
9109
|
};
|
|
8748
9110
|
}
|
|
8749
|
-
async function maybeEscalateContrarian(input, outcome,
|
|
9111
|
+
async function maybeEscalateContrarian(input, outcome, logger11, opts) {
|
|
8750
9112
|
if (!input.quickMode || outcome !== "approved" || input.simulateVotes) return void 0;
|
|
8751
|
-
const escalation = await runContrarianCheck(input.proposal,
|
|
9113
|
+
const escalation = await runContrarianCheck(input.proposal, logger11);
|
|
8752
9114
|
if (!escalation.shouldEscalate) return void 0;
|
|
8753
|
-
|
|
9115
|
+
logger11.warn("Contrarian escalation: re-running with full vote", {
|
|
8754
9116
|
reason: escalation.reason,
|
|
8755
9117
|
confidence: escalation.confidence
|
|
8756
9118
|
});
|
|
8757
|
-
return executeVoting({ ...input, quickMode: false },
|
|
9119
|
+
return executeVoting({ ...input, quickMode: false }, logger11, opts);
|
|
8758
9120
|
}
|
|
8759
|
-
async function executeVoting(input,
|
|
9121
|
+
async function executeVoting(input, logger11, opts) {
|
|
8760
9122
|
const strategy = resolveStrategy(input);
|
|
8761
9123
|
const algorithm = strategyToAlgorithm(strategy);
|
|
8762
9124
|
const roles = getVoterRoles(input.quickMode);
|
|
8763
9125
|
const startTime = getTimeProvider().now();
|
|
8764
9126
|
const errorPolicy = input.errorPolicy ?? getDefaultErrorPolicy(strategy);
|
|
8765
|
-
|
|
9127
|
+
logger11.info("Starting consensus vote", {
|
|
8766
9128
|
strategy,
|
|
8767
9129
|
algorithm,
|
|
8768
9130
|
roleCount: roles.length,
|
|
@@ -8784,7 +9146,7 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8784
9146
|
errorPolicy,
|
|
8785
9147
|
reason: policyDecision.reason ?? "error policy short-circuit",
|
|
8786
9148
|
startTime,
|
|
8787
|
-
logger:
|
|
9149
|
+
logger: logger11
|
|
8788
9150
|
});
|
|
8789
9151
|
}
|
|
8790
9152
|
const { engineResult, voteMap, higherOrderResult, outcome, cascaded } = await processVotesWithCascade(policyDecision.engineVotes, {
|
|
@@ -8792,10 +9154,10 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8792
9154
|
proposal: input.proposal,
|
|
8793
9155
|
algorithm,
|
|
8794
9156
|
strategy,
|
|
8795
|
-
log:
|
|
9157
|
+
log: logger11
|
|
8796
9158
|
});
|
|
8797
|
-
recordVotesToTracker(votes, voteMap, outcome,
|
|
8798
|
-
const escalated = await maybeEscalateContrarian(input, outcome,
|
|
9159
|
+
recordVotesToTracker(votes, voteMap, outcome, logger11);
|
|
9160
|
+
const escalated = await maybeEscalateContrarian(input, outcome, logger11, opts);
|
|
8799
9161
|
if (escalated !== void 0) return escalated;
|
|
8800
9162
|
return finalizeVotingResult({
|
|
8801
9163
|
input,
|
|
@@ -8807,7 +9169,7 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8807
9169
|
outcome,
|
|
8808
9170
|
cascaded,
|
|
8809
9171
|
startTime,
|
|
8810
|
-
logger:
|
|
9172
|
+
logger: logger11
|
|
8811
9173
|
});
|
|
8812
9174
|
}
|
|
8813
9175
|
function finalizeVotingResult(args) {
|
|
@@ -8831,15 +9193,15 @@ function finalizeVotingResult(args) {
|
|
|
8831
9193
|
return result;
|
|
8832
9194
|
}
|
|
8833
9195
|
async function handleConsensusVote(deps, args) {
|
|
8834
|
-
const
|
|
8835
|
-
if (args.simulateVotes) warnIfSimulatedOutsideTests("consensus_vote",
|
|
9196
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
9197
|
+
if (args.simulateVotes) warnIfSimulatedOutsideTests("consensus_vote", logger11);
|
|
8836
9198
|
try {
|
|
8837
|
-
const result = await executeVoting(args,
|
|
9199
|
+
const result = await executeVoting(args, logger11);
|
|
8838
9200
|
const strategy = args.strategy ?? "simple_majority";
|
|
8839
9201
|
const errorVotes = result.votes.filter((v) => v.source === "error");
|
|
8840
9202
|
if (errorVotes.length === result.votes.length && result.votes.length > 0) {
|
|
8841
9203
|
const failures = errorVotes.map((v) => `${v.role}: ${v.error ?? "unknown error"}`).join("; ");
|
|
8842
|
-
|
|
9204
|
+
logger11.warn("All voters failed", { failureCount: errorVotes.length, failures });
|
|
8843
9205
|
recordVoteError(args.proposal, `All ${String(errorVotes.length)} voters failed: ${failures}`);
|
|
8844
9206
|
return {
|
|
8845
9207
|
ok: false,
|
|
@@ -8857,11 +9219,109 @@ async function handleConsensusVote(deps, args) {
|
|
|
8857
9219
|
} catch (error) {
|
|
8858
9220
|
const message = getErrorMessage(error);
|
|
8859
9221
|
const cause = error instanceof Error ? error : new Error(message);
|
|
8860
|
-
|
|
9222
|
+
logger11.error("Consensus vote failed", cause);
|
|
8861
9223
|
recordVoteError(args.proposal, message);
|
|
8862
9224
|
return { ok: false, error: `Voting failed: ${message}` };
|
|
8863
9225
|
}
|
|
8864
9226
|
}
|
|
9227
|
+
function buildConsensusVoteReplayEnvelope(jobId) {
|
|
9228
|
+
return toolSuccess(
|
|
9229
|
+
JSON.stringify({
|
|
9230
|
+
status: "replay",
|
|
9231
|
+
jobId,
|
|
9232
|
+
pollTool: "get_job_result",
|
|
9233
|
+
note: "Idempotency key matched a prior dispatch \u2014 poll get_job_result for current status."
|
|
9234
|
+
})
|
|
9235
|
+
);
|
|
9236
|
+
}
|
|
9237
|
+
function buildConsensusVoteCollisionEnvelope(existingJobId) {
|
|
9238
|
+
return toolStructuredError({
|
|
9239
|
+
errorCategory: "validation",
|
|
9240
|
+
message: `Idempotency key already used with different inputs. Existing jobId: ${existingJobId}. Use a fresh key or omit it.`
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
9243
|
+
function dispatchAsyncConsensusVote(deps, args) {
|
|
9244
|
+
const idempotency = shortCircuitOrFreshJobId({
|
|
9245
|
+
tool: "consensus_vote",
|
|
9246
|
+
idempotencyKey: args.idempotencyKey,
|
|
9247
|
+
inputs: args,
|
|
9248
|
+
freshJobId: () => `job-vote-${randomUUID4()}`,
|
|
9249
|
+
replayEnvelope: buildConsensusVoteReplayEnvelope,
|
|
9250
|
+
collisionEnvelope: buildConsensusVoteCollisionEnvelope
|
|
9251
|
+
});
|
|
9252
|
+
if (idempotency.kind === "shortCircuit") return idempotency.value;
|
|
9253
|
+
if (!tryAcquire("consensus_vote")) {
|
|
9254
|
+
return toolSuccess(
|
|
9255
|
+
JSON.stringify({
|
|
9256
|
+
status: "busy",
|
|
9257
|
+
retryAfterMs: suggestRetryAfterMs("consensus_vote"),
|
|
9258
|
+
note: 'Async-mode concurrency cap reached for consensus_vote. Retry later or use mode: "sync".'
|
|
9259
|
+
})
|
|
9260
|
+
);
|
|
9261
|
+
}
|
|
9262
|
+
const jobId = idempotency.jobId;
|
|
9263
|
+
recordConsensusVotePending(jobId, args);
|
|
9264
|
+
void runConsensusVoteInBackground(jobId, deps, args);
|
|
9265
|
+
return toolSuccess(
|
|
9266
|
+
JSON.stringify({
|
|
9267
|
+
status: "pending",
|
|
9268
|
+
jobId,
|
|
9269
|
+
pollTool: "get_job_result",
|
|
9270
|
+
note: 'Poll via get_job_result({ jobId }) until status !== "pending".'
|
|
9271
|
+
})
|
|
9272
|
+
);
|
|
9273
|
+
}
|
|
9274
|
+
function recordConsensusVotePending(jobId, args) {
|
|
9275
|
+
writeJobPending(jobId, "consensus_vote");
|
|
9276
|
+
if (args.idempotencyKey !== void 0 && args.idempotencyKey !== "") {
|
|
9277
|
+
registerIdempotentJob({
|
|
9278
|
+
tool: "consensus_vote",
|
|
9279
|
+
idempotencyKey: args.idempotencyKey,
|
|
9280
|
+
inputs: args,
|
|
9281
|
+
jobId
|
|
9282
|
+
});
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
async function runConsensusVoteInBackground(jobId, deps, args) {
|
|
9286
|
+
try {
|
|
9287
|
+
const result = await handleConsensusVote(deps, args);
|
|
9288
|
+
writeJobComplete(jobId, "consensus_vote", result);
|
|
9289
|
+
} catch (err2) {
|
|
9290
|
+
const errObj = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
9291
|
+
deps.logger?.error("Async consensus_vote dispatch failed", errObj, { jobId });
|
|
9292
|
+
writeJobFailed(jobId, "consensus_vote", errObj.message);
|
|
9293
|
+
} finally {
|
|
9294
|
+
release("consensus_vote");
|
|
9295
|
+
}
|
|
9296
|
+
}
|
|
9297
|
+
async function runSyncConsensusVote(deps, notifier, args) {
|
|
9298
|
+
const result = await withProgressHeartbeat(
|
|
9299
|
+
"consensus_vote",
|
|
9300
|
+
notifier,
|
|
9301
|
+
() => handleConsensusVote(deps, args)
|
|
9302
|
+
);
|
|
9303
|
+
if (!result.ok) {
|
|
9304
|
+
return toolStructuredError({ errorCategory: "internal", message: result.error });
|
|
9305
|
+
}
|
|
9306
|
+
for (const vote of result.value.votes) {
|
|
9307
|
+
notifier.debug("consensus_vote", {
|
|
9308
|
+
event: "vote_collected",
|
|
9309
|
+
role: vote.role,
|
|
9310
|
+
decision: vote.decision
|
|
9311
|
+
});
|
|
9312
|
+
}
|
|
9313
|
+
notifier.info("consensus_vote", {
|
|
9314
|
+
event: "vote_complete",
|
|
9315
|
+
decision: result.value.decision,
|
|
9316
|
+
approvalPercentage: result.value.approvalPercentage,
|
|
9317
|
+
voteCount: result.value.votes.length
|
|
9318
|
+
});
|
|
9319
|
+
const data = result.value;
|
|
9320
|
+
return {
|
|
9321
|
+
...toolSuccess(JSON.stringify(result.value, null, 2)),
|
|
9322
|
+
structuredContent: data
|
|
9323
|
+
};
|
|
9324
|
+
}
|
|
8865
9325
|
function createConsensusVoteHandler(deps) {
|
|
8866
9326
|
const notifier = deps.notifier ?? NOOP_NOTIFIER;
|
|
8867
9327
|
return async (args, ctx) => {
|
|
@@ -8875,63 +9335,48 @@ function createConsensusVoteHandler(deps) {
|
|
|
8875
9335
|
const strategy = validationResult.data.strategy ?? "simple_majority";
|
|
8876
9336
|
ctx.logger.debug("Starting consensus vote", {
|
|
8877
9337
|
strategy,
|
|
8878
|
-
quickMode: validationResult.data.quickMode
|
|
9338
|
+
quickMode: validationResult.data.quickMode,
|
|
9339
|
+
...validationResult.data.mode !== void 0 ? { mode: validationResult.data.mode } : {}
|
|
8879
9340
|
});
|
|
8880
9341
|
notifier.info("consensus_vote", {
|
|
8881
9342
|
event: "vote_start",
|
|
8882
9343
|
proposalLength: validationResult.data.proposal.length,
|
|
8883
9344
|
strategy
|
|
8884
9345
|
});
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
notifier,
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
return toolStructuredError({ errorCategory: "internal", message: result.error });
|
|
8892
|
-
}
|
|
8893
|
-
for (const vote of result.value.votes) {
|
|
8894
|
-
notifier.debug("consensus_vote", {
|
|
8895
|
-
event: "vote_collected",
|
|
8896
|
-
role: vote.role,
|
|
8897
|
-
decision: vote.decision
|
|
9346
|
+
if (validationResult.data.mode === "async") {
|
|
9347
|
+
const asyncResult = dispatchAsyncConsensusVote(deps, validationResult.data);
|
|
9348
|
+
notifier.info("consensus_vote", {
|
|
9349
|
+
event: "vote_dispatched_async",
|
|
9350
|
+
proposalLength: validationResult.data.proposal.length,
|
|
9351
|
+
strategy
|
|
8898
9352
|
});
|
|
9353
|
+
return asyncResult;
|
|
8899
9354
|
}
|
|
8900
|
-
|
|
8901
|
-
event: "vote_complete",
|
|
8902
|
-
decision: result.value.decision,
|
|
8903
|
-
approvalPercentage: result.value.approvalPercentage,
|
|
8904
|
-
voteCount: result.value.votes.length
|
|
8905
|
-
});
|
|
8906
|
-
const data = result.value;
|
|
8907
|
-
return {
|
|
8908
|
-
...toolSuccess(JSON.stringify(result.value, null, 2)),
|
|
8909
|
-
structuredContent: data
|
|
8910
|
-
};
|
|
9355
|
+
return runSyncConsensusVote(deps, notifier, validationResult.data);
|
|
8911
9356
|
};
|
|
8912
9357
|
}
|
|
8913
9358
|
var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
8914
|
-
proposal:
|
|
9359
|
+
proposal: z12.string(),
|
|
8915
9360
|
strategy: VotingStrategySchema,
|
|
8916
|
-
decision:
|
|
8917
|
-
approvalPercentage:
|
|
8918
|
-
voteCounts:
|
|
8919
|
-
approve:
|
|
8920
|
-
reject:
|
|
8921
|
-
abstain:
|
|
8922
|
-
error:
|
|
9361
|
+
decision: z12.enum(["approved", "rejected", "no_quorum"]),
|
|
9362
|
+
approvalPercentage: z12.number(),
|
|
9363
|
+
voteCounts: z12.object({
|
|
9364
|
+
approve: z12.number(),
|
|
9365
|
+
reject: z12.number(),
|
|
9366
|
+
abstain: z12.number(),
|
|
9367
|
+
error: z12.number()
|
|
8923
9368
|
}),
|
|
8924
|
-
votes:
|
|
8925
|
-
|
|
8926
|
-
role:
|
|
8927
|
-
decision:
|
|
8928
|
-
confidence:
|
|
8929
|
-
reasoning:
|
|
8930
|
-
simulated:
|
|
8931
|
-
error:
|
|
8932
|
-
modelUsed:
|
|
8933
|
-
rejectionCategories:
|
|
8934
|
-
|
|
9369
|
+
votes: z12.array(
|
|
9370
|
+
z12.object({
|
|
9371
|
+
role: z12.string().max(100),
|
|
9372
|
+
decision: z12.enum(["approve", "reject", "abstain"]),
|
|
9373
|
+
confidence: z12.number(),
|
|
9374
|
+
reasoning: z12.string().max(4e3),
|
|
9375
|
+
simulated: z12.boolean(),
|
|
9376
|
+
error: z12.boolean(),
|
|
9377
|
+
modelUsed: z12.string().max(100).optional(),
|
|
9378
|
+
rejectionCategories: z12.array(
|
|
9379
|
+
z12.enum([
|
|
8935
9380
|
"YAGNI",
|
|
8936
9381
|
"DRY_VIOLATION",
|
|
8937
9382
|
"OVER_ENGINEERING",
|
|
@@ -8944,42 +9389,42 @@ var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
|
8944
9389
|
})
|
|
8945
9390
|
),
|
|
8946
9391
|
threshold: VoteThresholdSchema.optional(),
|
|
8947
|
-
durationMs:
|
|
8948
|
-
simulateVotes:
|
|
8949
|
-
higherOrderMetadata:
|
|
8950
|
-
posteriorApproval:
|
|
8951
|
-
posteriorRejection:
|
|
8952
|
-
effectiveVoteCount:
|
|
8953
|
-
method:
|
|
8954
|
-
usedCorrelationData:
|
|
8955
|
-
improvementOverBaseline:
|
|
8956
|
-
downweightedAgents:
|
|
8957
|
-
reasoning:
|
|
9392
|
+
durationMs: z12.number(),
|
|
9393
|
+
simulateVotes: z12.boolean(),
|
|
9394
|
+
higherOrderMetadata: z12.object({
|
|
9395
|
+
posteriorApproval: z12.number(),
|
|
9396
|
+
posteriorRejection: z12.number(),
|
|
9397
|
+
effectiveVoteCount: z12.number(),
|
|
9398
|
+
method: z12.enum(["ow", "isp", "simple"]),
|
|
9399
|
+
usedCorrelationData: z12.boolean(),
|
|
9400
|
+
improvementOverBaseline: z12.number(),
|
|
9401
|
+
downweightedAgents: z12.array(z12.string().max(100)).max(10),
|
|
9402
|
+
reasoning: z12.string().max(2e3)
|
|
8958
9403
|
}).optional()
|
|
8959
9404
|
};
|
|
8960
9405
|
function registerConsensusVoteTool(server, deps) {
|
|
8961
|
-
const
|
|
9406
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
8962
9407
|
const notifier = deps.notifier ?? createMcpNotifier(server);
|
|
8963
9408
|
const depsWithNotifier = { ...deps, notifier };
|
|
8964
9409
|
const toolSchema = {
|
|
8965
|
-
proposal:
|
|
8966
|
-
threshold:
|
|
9410
|
+
proposal: z12.string().min(1).max(MAX_PROPOSAL_LENGTH).describe("Proposal text to vote on"),
|
|
9411
|
+
threshold: z12.enum(["majority", "supermajority", "unanimous"]).optional().describe("Voting threshold (legacy). Use strategy instead."),
|
|
8967
9412
|
strategy: VotingStrategySchema.optional().describe(
|
|
8968
9413
|
"Voting strategy: simple_majority (default), supermajority, unanimous, proof_of_learning, or higher_order"
|
|
8969
9414
|
),
|
|
8970
|
-
quickMode:
|
|
8971
|
-
simulateVotes:
|
|
9415
|
+
quickMode: z12.boolean().optional().default(false).describe("Use 3 agents instead of 7"),
|
|
9416
|
+
simulateVotes: z12.boolean().optional().default(false).describe("TESTS ONLY \u2014 random output, must not be used for real decisions (#2319)")
|
|
8972
9417
|
};
|
|
8973
9418
|
const description = "Execute multi-model consensus voting on a proposal. Uses 7 specialized agent roles (architect, security, devex, ai_ml, pm, catfish, scope_steward) to vote on proposals with configurable strategies. Supports higher_order strategy for Bayesian-optimal aggregation with correlation awareness (Issue #514).";
|
|
8974
9419
|
const secureHandler = createSecureHandler(createConsensusVoteHandler(depsWithNotifier), {
|
|
8975
9420
|
toolName: "consensus_vote",
|
|
8976
9421
|
rateLimiter: deps.rateLimiter,
|
|
8977
|
-
logger:
|
|
9422
|
+
logger: logger11
|
|
8978
9423
|
});
|
|
8979
9424
|
const timeoutMs = getToolTimeout("consensus_vote", deps.security);
|
|
8980
9425
|
const wrappedHandler = wrapToolWithTimeout("consensus_vote", secureHandler, {
|
|
8981
9426
|
timeoutMs,
|
|
8982
|
-
logger:
|
|
9427
|
+
logger: logger11
|
|
8983
9428
|
});
|
|
8984
9429
|
server.registerTool(
|
|
8985
9430
|
"consensus_vote",
|
|
@@ -8989,9 +9434,9 @@ function registerConsensusVoteTool(server, deps) {
|
|
|
8989
9434
|
outputSchema: CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
8990
9435
|
annotations: getToolAnnotations("consensus_vote")
|
|
8991
9436
|
},
|
|
8992
|
-
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs,
|
|
9437
|
+
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs, logger11)
|
|
8993
9438
|
);
|
|
8994
|
-
|
|
9439
|
+
logger11.info("Registered consensus_vote tool with secure handler and timeout protection");
|
|
8995
9440
|
}
|
|
8996
9441
|
|
|
8997
9442
|
export {
|
|
@@ -9044,6 +9489,17 @@ export {
|
|
|
9044
9489
|
toSdkCallbackWithBudgetCheck,
|
|
9045
9490
|
getToolAnnotations,
|
|
9046
9491
|
PersistentOutcomeStore,
|
|
9492
|
+
JobStatusSchema,
|
|
9493
|
+
writeJobPending,
|
|
9494
|
+
writeJobComplete,
|
|
9495
|
+
writeJobFailed,
|
|
9496
|
+
readJobResult,
|
|
9497
|
+
listJobs,
|
|
9498
|
+
registerIdempotentJob,
|
|
9499
|
+
shortCircuitOrFreshJobId,
|
|
9500
|
+
tryAcquire,
|
|
9501
|
+
release,
|
|
9502
|
+
suggestRetryAfterMs,
|
|
9047
9503
|
DEFAULT_VOTE_TIMEOUT_MS,
|
|
9048
9504
|
isRateLimitError,
|
|
9049
9505
|
VOTER_ROLES,
|
|
@@ -9113,4 +9569,4 @@ export {
|
|
|
9113
9569
|
CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
9114
9570
|
registerConsensusVoteTool
|
|
9115
9571
|
};
|
|
9116
|
-
//# sourceMappingURL=chunk-
|
|
9572
|
+
//# sourceMappingURL=chunk-IF6TXCSE.js.map
|