nexus-agents 2.84.0 → 2.86.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-VSGPMVLL.js → child-mcp-config-FARMFCA3.js} +2 -2
- package/dist/{chunk-BX3MUA26.js → chunk-25AQZKMN.js} +2 -2
- package/dist/{chunk-ZJGCOJC6.js → chunk-4MYJNFYL.js} +3 -3
- package/dist/{chunk-GBGMTN7O.js → chunk-4S5GY2MF.js} +2 -2
- package/dist/{chunk-KVYQOOML.js → chunk-4SU4Q37U.js} +4 -4
- package/dist/{chunk-4IXQ6IXI.js → chunk-6QKMUPDP.js} +2 -2
- package/dist/{chunk-CJHIKBWI.js → chunk-7J7D3LAL.js} +4 -4
- package/dist/{chunk-2PKAWFEU.js → chunk-CIW2BVSN.js} +2 -2
- package/dist/{chunk-V7RC2PIY.js → chunk-EHBSXLX6.js} +697 -225
- package/dist/chunk-EHBSXLX6.js.map +1 -0
- package/dist/{chunk-REH5BB67.js → chunk-EL2KV773.js} +2 -2
- package/dist/{chunk-T6B6CKU6.js → chunk-ELVQORLX.js} +3 -3
- package/dist/{chunk-UJMIGLZI.js → chunk-FOG7PMGM.js} +2 -2
- package/dist/{chunk-I3O5WOGW.js → chunk-FR3P47J2.js} +3 -3
- package/dist/{chunk-QIRZSDJK.js → chunk-J5S27RQ4.js} +2 -2
- package/dist/{chunk-XK523BNC.js → chunk-LY2KLNXM.js} +2 -2
- package/dist/{chunk-MBD5EJBO.js → chunk-NSOB2WRI.js} +3 -3
- package/dist/{chunk-V35FWPHD.js → chunk-POTGGTJD.js} +2 -2
- package/dist/{chunk-VQIWH6X4.js → chunk-QOOYL3B3.js} +7 -7
- package/dist/{chunk-KK2RZVPU.js → chunk-QR5KHIZG.js} +2 -2
- package/dist/{chunk-OE7OL2BE.js → chunk-RTAIMK6P.js} +4 -4
- package/dist/{chunk-3PFESR6I.js → chunk-RZNED4KY.js} +2064 -1654
- package/dist/chunk-RZNED4KY.js.map +1 -0
- package/dist/{chunk-5LFUZ5IC.js → chunk-SOKQOPTM.js} +2 -2
- package/dist/{chunk-YVG32OQF.js → chunk-TRX7CAD5.js} +2 -2
- package/dist/{chunk-KRRBRT7B.js → chunk-XRROTMWR.js} +5 -5
- package/dist/{chunk-VXHDZULO.js → chunk-XZAWH36N.js} +3 -3
- package/dist/{chunk-HLNMWGW3.js → chunk-YLMB5MFQ.js} +2 -2
- package/dist/{chunk-HLNMWGW3.js.map → chunk-YLMB5MFQ.js.map} +1 -1
- package/dist/{chunk-OROUYVUX.js → chunk-Z5BAZS6F.js} +2 -2
- package/dist/{chunk-RZVEVT3M.js → chunk-ZF3MSYLG.js} +7 -7
- package/dist/{chunk-D5BKSM6H.js → chunk-ZMERLMEP.js} +2 -2
- package/dist/{cli-circuit-breaker-6EQKYNAA.js → cli-circuit-breaker-LF22Z3QL.js} +4 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +38 -34
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-SA4XHXA3.js → composite-router-TEXZI7WJ.js} +2 -2
- package/dist/{consensus-vote-QKUHNHS4.js → consensus-vote-K7HSOLYR.js} +12 -12
- package/dist/{consensus-vote-types-CoGbAEjf.d.ts → consensus-vote-types-COAhHVIp.d.ts} +5 -0
- package/dist/{context-retriever-XX4TQBIB.js → context-retriever-3TKV4EEC.js} +6 -6
- package/dist/{dist-APHOQ26Z.js → dist-75MXB4R3.js} +917 -822
- package/dist/dist-75MXB4R3.js.map +1 -0
- package/dist/{doctor-deep-NB7IDWJE.js → doctor-deep-OUAKPLJX.js} +3 -3
- package/dist/{expert-bridge-4MHM77VW.js → expert-bridge-22UVN5TB.js} +4 -4
- package/dist/{factory-FKOPQXRS.js → factory-IGHUOUBS.js} +5 -5
- package/dist/{factory-UHH4ZZ64.js → factory-JNXETBS4.js} +5 -5
- package/dist/index.d.ts +26 -4
- package/dist/index.js +25 -25
- package/dist/{init-opencode-AVYAC33Q.js → init-opencode-52TLZ2TO.js} +5 -5
- package/dist/{issue-triage-7PYND7PY.js → issue-triage-WUK7L25X.js} +4 -4
- package/dist/{registry-command-7SFNZRG5.js → registry-command-ZIAJB2J3.js} +2 -2
- package/dist/{repo-security-plan-MTVL5B73.js → repo-security-plan-THHLNGEQ.js} +3 -3
- package/dist/{research-helpers-synthesize-6CJ5P4WL.js → research-helpers-synthesize-KNRCAIXK.js} +3 -3
- package/dist/{routing-memory-Y577PXFP.js → routing-memory-UY6KTGT2.js} +2 -2
- package/dist/{session-memory-6YFGMEMP.js → session-memory-PUJMR6FE.js} +3 -3
- package/dist/{setup-command-YBTD6D53.js → setup-command-J2AVNUJV.js} +11 -11
- package/dist/{setup-config-BQYLUJHY.js → setup-config-EYY5AYZ3.js} +3 -3
- package/dist/{setup-custom-api-ZB7LOQC5.js → setup-custom-api-5XRNYFOP.js} +4 -4
- package/dist/{tool-memory-GFYQFRQI.js → tool-memory-PNAHRFZM.js} +5 -5
- package/dist/{weather-report-CN6WPJI4.js → weather-report-RQMZOL7C.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-3PFESR6I.js.map +0 -1
- package/dist/chunk-V7RC2PIY.js.map +0 -1
- package/dist/dist-APHOQ26Z.js.map +0 -1
- /package/dist/{child-mcp-config-VSGPMVLL.js.map → child-mcp-config-FARMFCA3.js.map} +0 -0
- /package/dist/{chunk-BX3MUA26.js.map → chunk-25AQZKMN.js.map} +0 -0
- /package/dist/{chunk-ZJGCOJC6.js.map → chunk-4MYJNFYL.js.map} +0 -0
- /package/dist/{chunk-GBGMTN7O.js.map → chunk-4S5GY2MF.js.map} +0 -0
- /package/dist/{chunk-KVYQOOML.js.map → chunk-4SU4Q37U.js.map} +0 -0
- /package/dist/{chunk-4IXQ6IXI.js.map → chunk-6QKMUPDP.js.map} +0 -0
- /package/dist/{chunk-CJHIKBWI.js.map → chunk-7J7D3LAL.js.map} +0 -0
- /package/dist/{chunk-2PKAWFEU.js.map → chunk-CIW2BVSN.js.map} +0 -0
- /package/dist/{chunk-REH5BB67.js.map → chunk-EL2KV773.js.map} +0 -0
- /package/dist/{chunk-T6B6CKU6.js.map → chunk-ELVQORLX.js.map} +0 -0
- /package/dist/{chunk-UJMIGLZI.js.map → chunk-FOG7PMGM.js.map} +0 -0
- /package/dist/{chunk-I3O5WOGW.js.map → chunk-FR3P47J2.js.map} +0 -0
- /package/dist/{chunk-QIRZSDJK.js.map → chunk-J5S27RQ4.js.map} +0 -0
- /package/dist/{chunk-XK523BNC.js.map → chunk-LY2KLNXM.js.map} +0 -0
- /package/dist/{chunk-MBD5EJBO.js.map → chunk-NSOB2WRI.js.map} +0 -0
- /package/dist/{chunk-V35FWPHD.js.map → chunk-POTGGTJD.js.map} +0 -0
- /package/dist/{chunk-VQIWH6X4.js.map → chunk-QOOYL3B3.js.map} +0 -0
- /package/dist/{chunk-KK2RZVPU.js.map → chunk-QR5KHIZG.js.map} +0 -0
- /package/dist/{chunk-OE7OL2BE.js.map → chunk-RTAIMK6P.js.map} +0 -0
- /package/dist/{chunk-5LFUZ5IC.js.map → chunk-SOKQOPTM.js.map} +0 -0
- /package/dist/{chunk-YVG32OQF.js.map → chunk-TRX7CAD5.js.map} +0 -0
- /package/dist/{chunk-KRRBRT7B.js.map → chunk-XRROTMWR.js.map} +0 -0
- /package/dist/{chunk-VXHDZULO.js.map → chunk-XZAWH36N.js.map} +0 -0
- /package/dist/{chunk-OROUYVUX.js.map → chunk-Z5BAZS6F.js.map} +0 -0
- /package/dist/{chunk-RZVEVT3M.js.map → chunk-ZF3MSYLG.js.map} +0 -0
- /package/dist/{chunk-D5BKSM6H.js.map → chunk-ZMERLMEP.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-6EQKYNAA.js.map → cli-circuit-breaker-LF22Z3QL.js.map} +0 -0
- /package/dist/{composite-router-SA4XHXA3.js.map → composite-router-TEXZI7WJ.js.map} +0 -0
- /package/dist/{consensus-vote-QKUHNHS4.js.map → consensus-vote-K7HSOLYR.js.map} +0 -0
- /package/dist/{context-retriever-XX4TQBIB.js.map → context-retriever-3TKV4EEC.js.map} +0 -0
- /package/dist/{doctor-deep-NB7IDWJE.js.map → doctor-deep-OUAKPLJX.js.map} +0 -0
- /package/dist/{expert-bridge-4MHM77VW.js.map → expert-bridge-22UVN5TB.js.map} +0 -0
- /package/dist/{factory-FKOPQXRS.js.map → factory-IGHUOUBS.js.map} +0 -0
- /package/dist/{factory-UHH4ZZ64.js.map → factory-JNXETBS4.js.map} +0 -0
- /package/dist/{init-opencode-AVYAC33Q.js.map → init-opencode-52TLZ2TO.js.map} +0 -0
- /package/dist/{issue-triage-7PYND7PY.js.map → issue-triage-WUK7L25X.js.map} +0 -0
- /package/dist/{registry-command-7SFNZRG5.js.map → registry-command-ZIAJB2J3.js.map} +0 -0
- /package/dist/{repo-security-plan-MTVL5B73.js.map → repo-security-plan-THHLNGEQ.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-6CJ5P4WL.js.map → research-helpers-synthesize-KNRCAIXK.js.map} +0 -0
- /package/dist/{routing-memory-Y577PXFP.js.map → routing-memory-UY6KTGT2.js.map} +0 -0
- /package/dist/{session-memory-6YFGMEMP.js.map → session-memory-PUJMR6FE.js.map} +0 -0
- /package/dist/{setup-command-YBTD6D53.js.map → setup-command-J2AVNUJV.js.map} +0 -0
- /package/dist/{setup-config-BQYLUJHY.js.map → setup-config-EYY5AYZ3.js.map} +0 -0
- /package/dist/{setup-custom-api-ZB7LOQC5.js.map → setup-custom-api-5XRNYFOP.js.map} +0 -0
- /package/dist/{tool-memory-GFYQFRQI.js.map → tool-memory-PNAHRFZM.js.map} +0 -0
- /package/dist/{weather-report-CN6WPJI4.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
|
|
78
|
+
nexusDataPath,
|
|
79
|
+
nexusDataPathEnsure
|
|
79
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
|
}
|
|
@@ -890,6 +891,8 @@ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
|
890
891
|
"query_task_state",
|
|
891
892
|
// Async-mode result polling (#3042 / epic #2631)
|
|
892
893
|
"get_job_result",
|
|
894
|
+
// Async-mode job discovery (#3046 / epic #2631 Stage 5)
|
|
895
|
+
"list_jobs",
|
|
893
896
|
// Codebase intelligence (read-only over local files)
|
|
894
897
|
"search_codebase",
|
|
895
898
|
"extract_symbols",
|
|
@@ -1237,13 +1240,13 @@ function buildMiddlewareStack(config) {
|
|
|
1237
1240
|
return middlewares;
|
|
1238
1241
|
}
|
|
1239
1242
|
function createMiddlewareChain(config) {
|
|
1240
|
-
const
|
|
1243
|
+
const logger11 = config.logger ?? createLogger({ tool: config.toolName });
|
|
1241
1244
|
const middlewares = buildMiddlewareStack(config);
|
|
1242
1245
|
const composed = composeMiddleware(middlewares);
|
|
1243
1246
|
return (handler) => {
|
|
1244
1247
|
return async (args) => {
|
|
1245
1248
|
const requestContext = createRequestContext({ toolName: config.toolName });
|
|
1246
|
-
const requestLogger =
|
|
1249
|
+
const requestLogger = logger11.child(contextForLogging(requestContext));
|
|
1247
1250
|
const ctx = { requestContext, logger: requestLogger };
|
|
1248
1251
|
return composed(args, ctx, (finalArgs, finalCtx) => handler(finalArgs, finalCtx));
|
|
1249
1252
|
};
|
|
@@ -1430,8 +1433,8 @@ async function runMismatchedCall(ctx) {
|
|
|
1430
1433
|
throw err2;
|
|
1431
1434
|
}
|
|
1432
1435
|
}
|
|
1433
|
-
function toSdkCallbackWithBudgetCheck(handler, toolName, configuredTimeoutMs,
|
|
1434
|
-
const log =
|
|
1436
|
+
function toSdkCallbackWithBudgetCheck(handler, toolName, configuredTimeoutMs, logger11) {
|
|
1437
|
+
const log = logger11 ?? wrapperLogger;
|
|
1435
1438
|
return (args, extra) => {
|
|
1436
1439
|
const progressCtx = extractProgressContext(extra);
|
|
1437
1440
|
const signal = extra?.signal;
|
|
@@ -1555,15 +1558,15 @@ function sanitizeToolInput(args) {
|
|
|
1555
1558
|
detectedPatterns: uniquePatterns
|
|
1556
1559
|
};
|
|
1557
1560
|
}
|
|
1558
|
-
function logSanitizationResult(result,
|
|
1561
|
+
function logSanitizationResult(result, logger11, toolName) {
|
|
1559
1562
|
if (result.wasModified) {
|
|
1560
|
-
|
|
1563
|
+
logger11.warn("Tool input sanitized \u2014 XML injection tags stripped", {
|
|
1561
1564
|
tool: toolName,
|
|
1562
1565
|
modifiedFields: result.modifiedCount
|
|
1563
1566
|
});
|
|
1564
1567
|
}
|
|
1565
1568
|
if (result.detectedPatterns.length > 0) {
|
|
1566
|
-
|
|
1569
|
+
logger11.warn("Injection patterns detected in tool input", {
|
|
1567
1570
|
tool: toolName,
|
|
1568
1571
|
patterns: result.detectedPatterns
|
|
1569
1572
|
});
|
|
@@ -1603,11 +1606,11 @@ var SECRET_PATTERNS = [
|
|
|
1603
1606
|
// password= or token= in output
|
|
1604
1607
|
/(?:password|token|secret|apikey|api_key)\s*[=:]\s*\S{8,}/i
|
|
1605
1608
|
];
|
|
1606
|
-
function sanitizeOutput2(text,
|
|
1609
|
+
function sanitizeOutput2(text, logger11) {
|
|
1607
1610
|
let sanitized = text;
|
|
1608
1611
|
for (const pattern of SECRET_PATTERNS) {
|
|
1609
1612
|
if (pattern.test(sanitized)) {
|
|
1610
|
-
|
|
1613
|
+
logger11.warn("Potential secret detected in tool output, redacting", {
|
|
1611
1614
|
pattern: pattern.source.slice(0, 30)
|
|
1612
1615
|
});
|
|
1613
1616
|
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
@@ -1615,25 +1618,25 @@ function sanitizeOutput2(text, logger8) {
|
|
|
1615
1618
|
}
|
|
1616
1619
|
return sanitized;
|
|
1617
1620
|
}
|
|
1618
|
-
function sanitizeToolResult(result,
|
|
1621
|
+
function sanitizeToolResult(result, logger11) {
|
|
1619
1622
|
for (const item of result.content) {
|
|
1620
|
-
item.text = sanitizeOutput2(item.text,
|
|
1623
|
+
item.text = sanitizeOutput2(item.text, logger11);
|
|
1621
1624
|
}
|
|
1622
1625
|
}
|
|
1623
|
-
function checkInputSize(args,
|
|
1626
|
+
function checkInputSize(args, logger11, requestId) {
|
|
1624
1627
|
if (args === void 0) return null;
|
|
1625
1628
|
const inputSize = JSON.stringify(args).length;
|
|
1626
1629
|
if (inputSize > MAX_INPUT_SIZE_BYTES) {
|
|
1627
|
-
|
|
1630
|
+
logger11.warn("Input size exceeds limit", { inputSize, limit: MAX_INPUT_SIZE_BYTES });
|
|
1628
1631
|
return internalError("Input too large", requestId);
|
|
1629
1632
|
}
|
|
1630
1633
|
return null;
|
|
1631
1634
|
}
|
|
1632
|
-
function checkRateLimit(rateLimiter,
|
|
1635
|
+
function checkRateLimit(rateLimiter, logger11) {
|
|
1633
1636
|
const acquired = rateLimiter.tryAcquire();
|
|
1634
1637
|
if (!acquired) {
|
|
1635
1638
|
const state = rateLimiter.getState();
|
|
1636
|
-
|
|
1639
|
+
logger11.warn("Rate limit exceeded");
|
|
1637
1640
|
return rateLimitError(state.nextTokenMs);
|
|
1638
1641
|
}
|
|
1639
1642
|
return null;
|
|
@@ -1654,14 +1657,14 @@ function checkPolicy(opts) {
|
|
|
1654
1657
|
opts.logger.debug("Policy check passed", { reason: decision.reason });
|
|
1655
1658
|
return null;
|
|
1656
1659
|
}
|
|
1657
|
-
async function executeHandler(handler, args, ctx,
|
|
1660
|
+
async function executeHandler(handler, args, ctx, logger11) {
|
|
1658
1661
|
const startTime = getTimeProvider().now();
|
|
1659
1662
|
const result = handler.length >= 2 ? await handler(args, ctx) : await handler(args);
|
|
1660
1663
|
const durationMs = getTimeProvider().now() - startTime;
|
|
1661
1664
|
if (result.isError === true) {
|
|
1662
|
-
|
|
1665
|
+
logger11.warn("Tool execution completed with error", { durationMs });
|
|
1663
1666
|
} else {
|
|
1664
|
-
|
|
1667
|
+
logger11.info("Tool execution completed", { durationMs });
|
|
1665
1668
|
}
|
|
1666
1669
|
return result;
|
|
1667
1670
|
}
|
|
@@ -1707,12 +1710,12 @@ function emitRateLimitAudit(auditLogger, toolName, ctx) {
|
|
|
1707
1710
|
requestId: ctx.requestId
|
|
1708
1711
|
});
|
|
1709
1712
|
}
|
|
1710
|
-
function checkSecurityTier(config, sanitizeResult,
|
|
1713
|
+
function checkSecurityTier(config, sanitizeResult, logger11) {
|
|
1711
1714
|
const tier = config.securityTier ?? "standard";
|
|
1712
1715
|
if (tier === "standard" || sanitizeResult.detectedPatterns.length === 0) {
|
|
1713
1716
|
return null;
|
|
1714
1717
|
}
|
|
1715
|
-
|
|
1718
|
+
logger11.warn("Input rejected by security tier validation", {
|
|
1716
1719
|
tier,
|
|
1717
1720
|
patterns: sanitizeResult.detectedPatterns
|
|
1718
1721
|
});
|
|
@@ -1721,16 +1724,16 @@ function checkSecurityTier(config, sanitizeResult, logger8) {
|
|
|
1721
1724
|
message: `Input validation failed: detected patterns [${sanitizeResult.detectedPatterns.join(", ")}]. Remove prompt injection patterns and retry.`
|
|
1722
1725
|
});
|
|
1723
1726
|
}
|
|
1724
|
-
function runPreChecks(config, args, mode, requestContext,
|
|
1725
|
-
const sizeResult = checkInputSize(args,
|
|
1727
|
+
function runPreChecks(config, args, mode, requestContext, logger11) {
|
|
1728
|
+
const sizeResult = checkInputSize(args, logger11, requestContext.requestId);
|
|
1726
1729
|
if (sizeResult) return { error: sizeResult, sanitizedArgs: args };
|
|
1727
1730
|
const sanitizeResult = sanitizeToolInput(args);
|
|
1728
|
-
logSanitizationResult(sanitizeResult,
|
|
1731
|
+
logSanitizationResult(sanitizeResult, logger11, config.toolName);
|
|
1729
1732
|
const sanitizedArgs = sanitizeResult.wasModified ? sanitizeResult.sanitized : args;
|
|
1730
|
-
const tierError = checkSecurityTier(config, sanitizeResult,
|
|
1733
|
+
const tierError = checkSecurityTier(config, sanitizeResult, logger11);
|
|
1731
1734
|
if (tierError !== null) return { error: tierError, sanitizedArgs };
|
|
1732
1735
|
if (config.rateLimiter) {
|
|
1733
|
-
const rlResult = checkRateLimit(config.rateLimiter,
|
|
1736
|
+
const rlResult = checkRateLimit(config.rateLimiter, logger11);
|
|
1734
1737
|
if (rlResult) {
|
|
1735
1738
|
if (config.auditLogger)
|
|
1736
1739
|
emitRateLimitAudit(config.auditLogger, config.toolName, requestContext);
|
|
@@ -1744,7 +1747,7 @@ function runPreChecks(config, args, mode, requestContext, logger8) {
|
|
|
1744
1747
|
args: sanitizedArgs,
|
|
1745
1748
|
mode,
|
|
1746
1749
|
allowedPaths: config.allowedPaths,
|
|
1747
|
-
logger:
|
|
1750
|
+
logger: logger11,
|
|
1748
1751
|
requestId: requestContext.requestId
|
|
1749
1752
|
});
|
|
1750
1753
|
if (pResult) {
|
|
@@ -1756,7 +1759,7 @@ function runPreChecks(config, args, mode, requestContext, logger8) {
|
|
|
1756
1759
|
return { error: null, sanitizedArgs };
|
|
1757
1760
|
}
|
|
1758
1761
|
function createSecureHandler(handler, config) {
|
|
1759
|
-
const
|
|
1762
|
+
const logger11 = config.logger ?? createLogger({ tool: config.toolName });
|
|
1760
1763
|
const mode = config.executionMode ?? "read-only";
|
|
1761
1764
|
return async (args) => {
|
|
1762
1765
|
const ctxOpts = {
|
|
@@ -1764,7 +1767,7 @@ function createSecureHandler(handler, config) {
|
|
|
1764
1767
|
...config.callerInfo && { caller: config.callerInfo }
|
|
1765
1768
|
};
|
|
1766
1769
|
const requestContext = createRequestContext(ctxOpts);
|
|
1767
|
-
const requestLogger =
|
|
1770
|
+
const requestLogger = logger11.child(contextForLogging(requestContext));
|
|
1768
1771
|
requestLogger.info("Tool invocation started");
|
|
1769
1772
|
const { error: preCheckError, sanitizedArgs } = runPreChecks(
|
|
1770
1773
|
config,
|
|
@@ -2362,10 +2365,10 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
2362
2365
|
return this.modelsCache.value;
|
|
2363
2366
|
}
|
|
2364
2367
|
if (this.modelsInFlight !== null) return this.modelsInFlight;
|
|
2365
|
-
const
|
|
2366
|
-
this.modelsInFlight =
|
|
2368
|
+
const inFlight2 = this.fetchModels();
|
|
2369
|
+
this.modelsInFlight = inFlight2;
|
|
2367
2370
|
try {
|
|
2368
|
-
const value = await
|
|
2371
|
+
const value = await inFlight2;
|
|
2369
2372
|
this.modelsCache = { value, fetchedAt: Date.now() };
|
|
2370
2373
|
return value;
|
|
2371
2374
|
} finally {
|
|
@@ -2465,13 +2468,13 @@ var SdkAdapter = class extends BaseAdapter {
|
|
|
2465
2468
|
customBaseUrl;
|
|
2466
2469
|
/** Inflight init promise for coalescing concurrent calls (Issue #1438). */
|
|
2467
2470
|
initPromise;
|
|
2468
|
-
constructor(config,
|
|
2471
|
+
constructor(config, logger11) {
|
|
2469
2472
|
const apiKey = resolveApiKey(config.providerId, config.apiKey);
|
|
2470
2473
|
super({
|
|
2471
2474
|
providerId: `sdk-${config.providerId}`,
|
|
2472
2475
|
modelId: config.modelId,
|
|
2473
2476
|
capabilities: [ModelCapability.COMPLETION, ModelCapability.STREAMING],
|
|
2474
|
-
logger:
|
|
2477
|
+
logger: logger11 ?? createLogger({ adapter: `sdk-${config.providerId}` }),
|
|
2475
2478
|
...apiKey !== void 0 ? { apiKey } : {},
|
|
2476
2479
|
...config.timeout !== void 0 ? { timeout: config.timeout } : {},
|
|
2477
2480
|
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {}
|
|
@@ -2651,19 +2654,19 @@ var SdkAdapter = class extends BaseAdapter {
|
|
|
2651
2654
|
|
|
2652
2655
|
// src/adapters/auto-adapter.ts
|
|
2653
2656
|
var defaultLogger = createLogger({ component: "auto-adapter" });
|
|
2654
|
-
function resolveCache(config,
|
|
2657
|
+
function resolveCache(config, logger11) {
|
|
2655
2658
|
if (config.cache !== void 0) {
|
|
2656
2659
|
return config.cache;
|
|
2657
2660
|
}
|
|
2658
2661
|
const enableCache = config.enableCache ?? true;
|
|
2659
|
-
return enableCache ? createCliDetectionCache({ logger:
|
|
2662
|
+
return enableCache ? createCliDetectionCache({ logger: logger11 }) : void 0;
|
|
2660
2663
|
}
|
|
2661
|
-
async function tryCliAdapter(config,
|
|
2664
|
+
async function tryCliAdapter(config, logger11, cache) {
|
|
2662
2665
|
const preferredCli = config.preferredCli;
|
|
2663
2666
|
const bridgeConfig = config.defaultCliTimeoutMs !== void 0 ? { defaultTimeoutMs: config.defaultCliTimeoutMs } : void 0;
|
|
2664
2667
|
if (preferredCli !== void 0 && await isCliAvailable(preferredCli, cache)) {
|
|
2665
|
-
|
|
2666
|
-
const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger:
|
|
2668
|
+
logger11.info("Using preferred CLI", { cli: preferredCli });
|
|
2669
|
+
const cliAdapter2 = createCliAdapter({ cli: preferredCli, logger: logger11 });
|
|
2667
2670
|
await cliAdapter2.initialize();
|
|
2668
2671
|
return {
|
|
2669
2672
|
adapter: createCliToModelAdapter(cliAdapter2, bridgeConfig),
|
|
@@ -2675,15 +2678,15 @@ async function tryCliAdapter(config, logger8, cache) {
|
|
|
2675
2678
|
}
|
|
2676
2679
|
const availableClis = await getAvailableClis(cache);
|
|
2677
2680
|
if (availableClis.length === 0) {
|
|
2678
|
-
|
|
2681
|
+
logger11.info("No CLI adapters available");
|
|
2679
2682
|
return null;
|
|
2680
2683
|
}
|
|
2681
2684
|
const selectedCli = availableClis[0];
|
|
2682
2685
|
if (selectedCli === void 0) {
|
|
2683
2686
|
return null;
|
|
2684
2687
|
}
|
|
2685
|
-
|
|
2686
|
-
const cliAdapter = createCliAdapter({ cli: selectedCli, logger:
|
|
2688
|
+
logger11.info("Auto-selected CLI", { cli: selectedCli, available: availableClis });
|
|
2689
|
+
const cliAdapter = createCliAdapter({ cli: selectedCli, logger: logger11 });
|
|
2687
2690
|
await cliAdapter.initialize();
|
|
2688
2691
|
return {
|
|
2689
2692
|
adapter: createCliToModelAdapter(cliAdapter, bridgeConfig),
|
|
@@ -2697,13 +2700,13 @@ function resolveApiKeyFromEnv(configKey, envVar) {
|
|
|
2697
2700
|
const key = configKey ?? process.env[envVar];
|
|
2698
2701
|
return key !== void 0 && key.length > 0 ? key : void 0;
|
|
2699
2702
|
}
|
|
2700
|
-
function tryApiAdapter(config,
|
|
2703
|
+
function tryApiAdapter(config, logger11) {
|
|
2701
2704
|
const claudeModelId = getCliModelName(getDefaultModelForCli("claude"));
|
|
2702
2705
|
const codexModelId = getCliModelName(getDefaultModelForCli("codex"));
|
|
2703
2706
|
const geminiModelId = getCliModelName(getDefaultModelForCli("gemini"));
|
|
2704
2707
|
const anthropicKey = resolveApiKeyFromEnv(config.anthropicApiKey, "ANTHROPIC_API_KEY");
|
|
2705
2708
|
if (anthropicKey !== void 0) {
|
|
2706
|
-
|
|
2709
|
+
logger11.info("Using Anthropic API adapter", { model: claudeModelId });
|
|
2707
2710
|
return {
|
|
2708
2711
|
adapter: createClaudeAdapter({ modelId: claudeModelId, apiKey: anthropicKey }),
|
|
2709
2712
|
source: "api",
|
|
@@ -2713,7 +2716,7 @@ function tryApiAdapter(config, logger8) {
|
|
|
2713
2716
|
}
|
|
2714
2717
|
const openaiKey = resolveApiKeyFromEnv(config.openaiApiKey, "OPENAI_API_KEY");
|
|
2715
2718
|
if (openaiKey !== void 0) {
|
|
2716
|
-
|
|
2719
|
+
logger11.info("Using OpenAI API adapter (AI SDK)", { model: codexModelId });
|
|
2717
2720
|
return {
|
|
2718
2721
|
adapter: new SdkAdapter({ providerId: "openai", modelId: codexModelId, apiKey: openaiKey }),
|
|
2719
2722
|
source: "api",
|
|
@@ -2723,7 +2726,7 @@ function tryApiAdapter(config, logger8) {
|
|
|
2723
2726
|
}
|
|
2724
2727
|
const googleKey = resolveApiKeyFromEnv(config.googleApiKey, "GOOGLE_AI_API_KEY");
|
|
2725
2728
|
if (googleKey !== void 0) {
|
|
2726
|
-
|
|
2729
|
+
logger11.info("Using Google AI API adapter (AI SDK)", { model: geminiModelId });
|
|
2727
2730
|
return {
|
|
2728
2731
|
adapter: new SdkAdapter({
|
|
2729
2732
|
providerId: "google",
|
|
@@ -2735,19 +2738,19 @@ function tryApiAdapter(config, logger8) {
|
|
|
2735
2738
|
reason: `Using Google AI API via AI SDK (model: ${geminiModelId})`
|
|
2736
2739
|
};
|
|
2737
2740
|
}
|
|
2738
|
-
const custom = tryCustomOpenAiAdapter(
|
|
2741
|
+
const custom = tryCustomOpenAiAdapter(logger11);
|
|
2739
2742
|
if (custom !== null) return custom;
|
|
2740
|
-
|
|
2743
|
+
logger11.info("No API keys available for any provider");
|
|
2741
2744
|
return null;
|
|
2742
2745
|
}
|
|
2743
|
-
function tryCustomOpenAiAdapter(
|
|
2746
|
+
function tryCustomOpenAiAdapter(logger11) {
|
|
2744
2747
|
const customKey = resolveApiKeyFromEnv(void 0, "NEXUS_CUSTOM_API_KEY");
|
|
2745
2748
|
const customBaseUrl = process.env["NEXUS_CUSTOM_API_BASE_URL"];
|
|
2746
2749
|
if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
|
|
2747
2750
|
return null;
|
|
2748
2751
|
}
|
|
2749
2752
|
const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? CUSTOM_API_DEFAULT_MODEL;
|
|
2750
|
-
|
|
2753
|
+
logger11.info("Using custom-openai SDK adapter", {
|
|
2751
2754
|
model: customModelId,
|
|
2752
2755
|
baseUrl: customBaseUrl
|
|
2753
2756
|
});
|
|
@@ -2763,52 +2766,52 @@ function tryCustomOpenAiAdapter(logger8) {
|
|
|
2763
2766
|
reason: `Using custom OpenAI-compatible gateway at ${customBaseUrl} (model: ${customModelId})`
|
|
2764
2767
|
};
|
|
2765
2768
|
}
|
|
2766
|
-
async function selectCliFirst(config,
|
|
2767
|
-
const cliResult = await tryCliAdapter(config,
|
|
2769
|
+
async function selectCliFirst(config, logger11, cache) {
|
|
2770
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2768
2771
|
if (cliResult !== null) return cliResult;
|
|
2769
|
-
const apiResult = tryApiAdapter(config,
|
|
2772
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2770
2773
|
if (apiResult !== null) return apiResult;
|
|
2771
2774
|
throw new Error(
|
|
2772
2775
|
"No adapters available. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
2773
2776
|
);
|
|
2774
2777
|
}
|
|
2775
|
-
async function selectApiFirst(config,
|
|
2776
|
-
const apiResult = tryApiAdapter(config,
|
|
2778
|
+
async function selectApiFirst(config, logger11, cache) {
|
|
2779
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2777
2780
|
if (apiResult !== null) return apiResult;
|
|
2778
|
-
const cliResult = await tryCliAdapter(config,
|
|
2781
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2779
2782
|
if (cliResult !== null) return cliResult;
|
|
2780
2783
|
throw new Error(
|
|
2781
2784
|
"No adapters available. Set ANTHROPIC_API_KEY/OPENAI_API_KEY/GOOGLE_AI_API_KEY or install a CLI (claude/gemini/codex)."
|
|
2782
2785
|
);
|
|
2783
2786
|
}
|
|
2784
|
-
async function selectCliOnly(config,
|
|
2785
|
-
const cliResult = await tryCliAdapter(config,
|
|
2787
|
+
async function selectCliOnly(config, logger11, cache) {
|
|
2788
|
+
const cliResult = await tryCliAdapter(config, logger11, cache);
|
|
2786
2789
|
if (cliResult !== null) return cliResult;
|
|
2787
2790
|
throw new Error(
|
|
2788
2791
|
"No CLI adapters available. Install and authenticate claude, gemini, or codex CLI."
|
|
2789
2792
|
);
|
|
2790
2793
|
}
|
|
2791
|
-
function selectApiOnly(config,
|
|
2792
|
-
const apiResult = tryApiAdapter(config,
|
|
2794
|
+
function selectApiOnly(config, logger11) {
|
|
2795
|
+
const apiResult = tryApiAdapter(config, logger11);
|
|
2793
2796
|
if (apiResult !== null) return apiResult;
|
|
2794
2797
|
throw new Error(
|
|
2795
2798
|
"No API key available. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY."
|
|
2796
2799
|
);
|
|
2797
2800
|
}
|
|
2798
2801
|
async function createAutoAdapter(config = {}) {
|
|
2799
|
-
const
|
|
2802
|
+
const logger11 = config.logger ?? defaultLogger;
|
|
2800
2803
|
const priority = config.priority ?? "cli-first";
|
|
2801
|
-
const cache = resolveCache(config,
|
|
2802
|
-
|
|
2804
|
+
const cache = resolveCache(config, logger11);
|
|
2805
|
+
logger11.info("Auto-selecting adapter", { priority, cacheEnabled: cache !== void 0 });
|
|
2803
2806
|
switch (priority) {
|
|
2804
2807
|
case "cli-first":
|
|
2805
|
-
return selectCliFirst(config,
|
|
2808
|
+
return selectCliFirst(config, logger11, cache);
|
|
2806
2809
|
case "api-first":
|
|
2807
|
-
return selectApiFirst(config,
|
|
2810
|
+
return selectApiFirst(config, logger11, cache);
|
|
2808
2811
|
case "cli-only":
|
|
2809
|
-
return selectCliOnly(config,
|
|
2812
|
+
return selectCliOnly(config, logger11, cache);
|
|
2810
2813
|
case "api-only":
|
|
2811
|
-
return selectApiOnly(config,
|
|
2814
|
+
return selectApiOnly(config, logger11);
|
|
2812
2815
|
default: {
|
|
2813
2816
|
const exhaustive = priority;
|
|
2814
2817
|
throw new Error(`Unknown priority: ${String(exhaustive)}`);
|
|
@@ -4404,21 +4407,21 @@ async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs) {
|
|
|
4404
4407
|
}
|
|
4405
4408
|
}
|
|
4406
4409
|
async function executeWithRetries(opts) {
|
|
4407
|
-
const { role, proposal, adapter, logger:
|
|
4410
|
+
const { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries } = opts;
|
|
4408
4411
|
let lastError = "";
|
|
4409
4412
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
4410
4413
|
if (attempt > 0) {
|
|
4411
4414
|
const isRateLimit = isRateLimitError(lastError);
|
|
4412
4415
|
const baseDelay = isRateLimit ? RATE_LIMIT_RETRY_DELAY_MS : INITIAL_RETRY_DELAY_MS;
|
|
4413
4416
|
const delayMs = baseDelay * Math.pow(2, attempt - 1);
|
|
4414
|
-
|
|
4417
|
+
logger11.debug("Retrying vote execution", { role, attempt, delayMs, isRateLimit });
|
|
4415
4418
|
await delay(delayMs);
|
|
4416
4419
|
}
|
|
4417
4420
|
const attemptStart = Date.now();
|
|
4418
4421
|
const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs);
|
|
4419
4422
|
const attemptMs = Date.now() - attemptStart;
|
|
4420
4423
|
if (result.ok) {
|
|
4421
|
-
|
|
4424
|
+
logger11.info("Vote attempt timing", {
|
|
4422
4425
|
role,
|
|
4423
4426
|
attempt: attempt + 1,
|
|
4424
4427
|
attemptMs,
|
|
@@ -4428,14 +4431,14 @@ async function executeWithRetries(opts) {
|
|
|
4428
4431
|
}
|
|
4429
4432
|
lastError = result.error;
|
|
4430
4433
|
const rateLimited = isRateLimitError(lastError);
|
|
4431
|
-
|
|
4434
|
+
logger11.info("Vote attempt timing", {
|
|
4432
4435
|
role,
|
|
4433
4436
|
attempt: attempt + 1,
|
|
4434
4437
|
attemptMs,
|
|
4435
4438
|
succeeded: false,
|
|
4436
4439
|
rateLimited
|
|
4437
4440
|
});
|
|
4438
|
-
|
|
4441
|
+
logger11.warn("Vote attempt failed", {
|
|
4439
4442
|
role,
|
|
4440
4443
|
attempt: attempt + 1,
|
|
4441
4444
|
maxRetries: maxRetries + 1,
|
|
@@ -4465,7 +4468,7 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
4465
4468
|
proposal,
|
|
4466
4469
|
roleAdapters,
|
|
4467
4470
|
fallbackAdapter,
|
|
4468
|
-
logger:
|
|
4471
|
+
logger: logger11,
|
|
4469
4472
|
voteOptions,
|
|
4470
4473
|
interDelay,
|
|
4471
4474
|
overallDeadlineMs,
|
|
@@ -4477,12 +4480,12 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
4477
4480
|
const adapter = roleAdapters.get(role) ?? fallbackAdapter;
|
|
4478
4481
|
const elapsed = Date.now() - startedAt;
|
|
4479
4482
|
const remaining = Math.max(1, overallDeadlineMs - elapsed);
|
|
4480
|
-
return raceWithDeadline(voteFn(role, proposal, adapter,
|
|
4483
|
+
return raceWithDeadline(voteFn(role, proposal, adapter, logger11, voteOptions), role, remaining);
|
|
4481
4484
|
});
|
|
4482
4485
|
const results = await Promise.all(wrapped);
|
|
4483
4486
|
const expired = results.filter((r) => r.source === "error" && r.error === DEADLINE_MESSAGE);
|
|
4484
4487
|
if (expired.length > 0) {
|
|
4485
|
-
|
|
4488
|
+
logger11.warn("Consensus overall deadline reached; returning partial results", {
|
|
4486
4489
|
totalRoles: roles.length,
|
|
4487
4490
|
expiredRoles: expired.map((r) => r.role),
|
|
4488
4491
|
overallDeadlineMs
|
|
@@ -4499,32 +4502,32 @@ function computeOverallConsensusDeadlineMs(timeoutMs, maxRetries, roleCount, int
|
|
|
4499
4502
|
}
|
|
4500
4503
|
var DEFAULT_INTER_AGENT_DELAY_MS = 2e3;
|
|
4501
4504
|
var defaultLogger2 = createLogger({ component: "voter-agents" });
|
|
4502
|
-
async function executeAgentVote(role, proposal, adapter,
|
|
4505
|
+
async function executeAgentVote(role, proposal, adapter, logger11, options) {
|
|
4503
4506
|
const start = getTimeProvider().now();
|
|
4504
4507
|
const timeoutMs = options?.timeoutMs ?? resolveVoteTimeout();
|
|
4505
4508
|
const maxRetries = options?.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
4506
4509
|
const allowSimulation = options?.allowSimulation ?? false;
|
|
4507
|
-
|
|
4510
|
+
logger11.info("Executing vote", { role, model: adapter.modelId, provider: adapter.providerId });
|
|
4508
4511
|
const result = await executeWithRetries({
|
|
4509
4512
|
role,
|
|
4510
4513
|
proposal,
|
|
4511
4514
|
adapter,
|
|
4512
|
-
logger:
|
|
4515
|
+
logger: logger11,
|
|
4513
4516
|
timeoutMs,
|
|
4514
4517
|
maxRetries
|
|
4515
4518
|
});
|
|
4516
4519
|
const processingTimeMs = getTimeProvider().now() - start;
|
|
4517
4520
|
if (result.ok) {
|
|
4518
|
-
|
|
4521
|
+
logger11.info("Vote completed", { role, model: adapter.modelId, decision: result.vote.decision });
|
|
4519
4522
|
return { role, vote: result.vote, processingTimeMs, source: "llm", cli: adapter.providerId };
|
|
4520
4523
|
}
|
|
4521
|
-
|
|
4524
|
+
logger11.error("Vote execution failed after all retries", void 0, {
|
|
4522
4525
|
role,
|
|
4523
4526
|
model: adapter.modelId,
|
|
4524
4527
|
errorMessage: result.error
|
|
4525
4528
|
});
|
|
4526
4529
|
if (allowSimulation) {
|
|
4527
|
-
|
|
4530
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)", { role });
|
|
4528
4531
|
return createSimulationVoteResult(role, proposal, processingTimeMs, result.error);
|
|
4529
4532
|
}
|
|
4530
4533
|
return createErrorVoteResult(role, result.error, processingTimeMs);
|
|
@@ -4535,22 +4538,22 @@ var NoAdapterError = class extends Error {
|
|
|
4535
4538
|
this.name = "NoAdapterError";
|
|
4536
4539
|
}
|
|
4537
4540
|
};
|
|
4538
|
-
function resolveAdapter(options,
|
|
4541
|
+
function resolveAdapter(options, logger11) {
|
|
4539
4542
|
try {
|
|
4540
4543
|
if (options.adapter !== void 0) return { adapter: options.adapter };
|
|
4541
|
-
const registry = getGlobalRegistry({ logger:
|
|
4544
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
4542
4545
|
return { adapter: registry.getDefault() };
|
|
4543
4546
|
} catch (error) {
|
|
4544
4547
|
return { error: getErrorMessage(error) };
|
|
4545
4548
|
}
|
|
4546
4549
|
}
|
|
4547
|
-
function warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
4550
|
+
function warnIfCodexConcurrencyExceeded(roleAdapters, logger11) {
|
|
4548
4551
|
const codexBound = [...roleAdapters.values()].filter(
|
|
4549
4552
|
(a) => a.name === "codex"
|
|
4550
4553
|
).length;
|
|
4551
4554
|
const warning = checkCodexConcurrency(codexBound);
|
|
4552
4555
|
if (warning !== null) {
|
|
4553
|
-
|
|
4556
|
+
logger11.warn("Codex concurrency limit may be exceeded", { detail: warning });
|
|
4554
4557
|
}
|
|
4555
4558
|
}
|
|
4556
4559
|
function assignUniformAdapter(roles, adapter) {
|
|
@@ -4558,29 +4561,29 @@ function assignUniformAdapter(roles, adapter) {
|
|
|
4558
4561
|
for (const role of roles) adapters.set(role, adapter);
|
|
4559
4562
|
return adapters;
|
|
4560
4563
|
}
|
|
4561
|
-
function createCliAdapterMap(clis,
|
|
4562
|
-
const registry = getGlobalRegistry({ logger:
|
|
4564
|
+
function createCliAdapterMap(clis, logger11) {
|
|
4565
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
4563
4566
|
const result = /* @__PURE__ */ new Map();
|
|
4564
4567
|
for (const cli of clis) {
|
|
4565
4568
|
result.set(cli, registry.getAdapterForCli(cli));
|
|
4566
4569
|
}
|
|
4567
4570
|
return result;
|
|
4568
4571
|
}
|
|
4569
|
-
async function resolveDiverseAdapters(roles,
|
|
4572
|
+
async function resolveDiverseAdapters(roles, logger11, fallbackAdapter) {
|
|
4570
4573
|
let availableClis;
|
|
4571
4574
|
try {
|
|
4572
4575
|
availableClis = await getAvailableClis();
|
|
4573
4576
|
} catch (e) {
|
|
4574
|
-
|
|
4577
|
+
logger11.warn("Failed to resolve available CLIs; falling back to single adapter", {
|
|
4575
4578
|
error: String(e)
|
|
4576
4579
|
});
|
|
4577
4580
|
availableClis = [];
|
|
4578
4581
|
}
|
|
4579
4582
|
if (availableClis.length <= 1) {
|
|
4580
|
-
|
|
4583
|
+
logger11.info("Using single adapter for all roles", { cliCount: availableClis.length });
|
|
4581
4584
|
return assignUniformAdapter(roles, fallbackAdapter);
|
|
4582
4585
|
}
|
|
4583
|
-
const cliAdapters = createCliAdapterMap(availableClis,
|
|
4586
|
+
const cliAdapters = createCliAdapterMap(availableClis, logger11);
|
|
4584
4587
|
if (cliAdapters.size <= 1) return assignUniformAdapter(roles, fallbackAdapter);
|
|
4585
4588
|
const cliList = [...cliAdapters.entries()];
|
|
4586
4589
|
const adapters = /* @__PURE__ */ new Map();
|
|
@@ -4592,7 +4595,7 @@ async function resolveDiverseAdapters(roles, logger8, fallbackAdapter) {
|
|
|
4592
4595
|
adapters.set(role, entry[1]);
|
|
4593
4596
|
assignments[role] = entry[0];
|
|
4594
4597
|
}
|
|
4595
|
-
|
|
4598
|
+
logger11.info("Diverse adapters assigned", {
|
|
4596
4599
|
cliCount: cliAdapters.size,
|
|
4597
4600
|
clis: [...cliAdapters.keys()],
|
|
4598
4601
|
roleAssignments: assignments
|
|
@@ -4600,7 +4603,7 @@ async function resolveDiverseAdapters(roles, logger8, fallbackAdapter) {
|
|
|
4600
4603
|
return adapters;
|
|
4601
4604
|
}
|
|
4602
4605
|
async function launchStaggeredVotes(input) {
|
|
4603
|
-
const { roles, proposal, roleAdapters, fallbackAdapter, logger:
|
|
4606
|
+
const { roles, proposal, roleAdapters, fallbackAdapter, logger: logger11, voteOptions, interDelay } = input;
|
|
4604
4607
|
const computedDeadlineMs = computeOverallConsensusDeadlineMs(
|
|
4605
4608
|
voteOptions.timeoutMs,
|
|
4606
4609
|
voteOptions.maxRetries,
|
|
@@ -4609,7 +4612,7 @@ async function launchStaggeredVotes(input) {
|
|
|
4609
4612
|
);
|
|
4610
4613
|
const overallDeadlineMs = getMcpSafeDeadlineMs(computedDeadlineMs, "consensus_vote");
|
|
4611
4614
|
if (overallDeadlineMs < computedDeadlineMs) {
|
|
4612
|
-
|
|
4615
|
+
logger11.debug("Consensus deadline clamped to MCP wrapper timeout", {
|
|
4613
4616
|
computedDeadlineMs,
|
|
4614
4617
|
overallDeadlineMs
|
|
4615
4618
|
});
|
|
@@ -4619,7 +4622,7 @@ async function launchStaggeredVotes(input) {
|
|
|
4619
4622
|
proposal,
|
|
4620
4623
|
roleAdapters,
|
|
4621
4624
|
fallbackAdapter,
|
|
4622
|
-
logger:
|
|
4625
|
+
logger: logger11,
|
|
4623
4626
|
voteOptions,
|
|
4624
4627
|
interDelay,
|
|
4625
4628
|
overallDeadlineMs,
|
|
@@ -4627,27 +4630,27 @@ async function launchStaggeredVotes(input) {
|
|
|
4627
4630
|
});
|
|
4628
4631
|
}
|
|
4629
4632
|
async function collectRealVotes(options) {
|
|
4630
|
-
const
|
|
4633
|
+
const logger11 = options.logger ?? defaultLogger2;
|
|
4631
4634
|
const { roles, proposal, simulate, allowSimulation } = options;
|
|
4632
4635
|
const timeoutMs = options.timeoutMs ?? resolveVoteTimeout();
|
|
4633
4636
|
const maxRetries = options.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
4634
4637
|
if (simulate === true) {
|
|
4635
|
-
|
|
4638
|
+
logger11.info("Using simulation mode (explicitly requested)");
|
|
4636
4639
|
return createSimulatedVotes(roles, proposal);
|
|
4637
4640
|
}
|
|
4638
|
-
const adapterResult = resolveAdapter(options,
|
|
4641
|
+
const adapterResult = resolveAdapter(options, logger11);
|
|
4639
4642
|
if ("error" in adapterResult) {
|
|
4640
|
-
|
|
4643
|
+
logger11.error("No adapter available for voting", void 0, { error: adapterResult.error });
|
|
4641
4644
|
if (allowSimulation === true) {
|
|
4642
|
-
|
|
4645
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)");
|
|
4643
4646
|
return createSimulatedVotes(roles, proposal, "No adapter available");
|
|
4644
4647
|
}
|
|
4645
4648
|
throw new NoAdapterError(
|
|
4646
4649
|
`No adapter available for voting: ${adapterResult.error}. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY.`
|
|
4647
4650
|
);
|
|
4648
4651
|
}
|
|
4649
|
-
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles,
|
|
4650
|
-
warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
4652
|
+
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles, logger11, adapterResult.adapter);
|
|
4653
|
+
warnIfCodexConcurrencyExceeded(roleAdapters, logger11);
|
|
4651
4654
|
const voteOptions = { timeoutMs, maxRetries, allowSimulation: allowSimulation ?? false };
|
|
4652
4655
|
const interDelay = options.interAgentDelayMs ?? DEFAULT_INTER_AGENT_DELAY_MS;
|
|
4653
4656
|
return launchStaggeredVotes({
|
|
@@ -4655,7 +4658,7 @@ async function collectRealVotes(options) {
|
|
|
4655
4658
|
proposal,
|
|
4656
4659
|
roleAdapters,
|
|
4657
4660
|
fallbackAdapter: adapterResult.adapter,
|
|
4658
|
-
logger:
|
|
4661
|
+
logger: logger11,
|
|
4659
4662
|
voteOptions,
|
|
4660
4663
|
interDelay
|
|
4661
4664
|
});
|
|
@@ -5644,11 +5647,11 @@ var ConsensusEngine = class {
|
|
|
5644
5647
|
logger;
|
|
5645
5648
|
metrics;
|
|
5646
5649
|
voterExpansionCallback;
|
|
5647
|
-
constructor(config,
|
|
5650
|
+
constructor(config, logger11) {
|
|
5648
5651
|
this.config = { ...DEFAULT_CONSENSUS_CONFIG, ...config };
|
|
5649
5652
|
this.cacheConfig = { ...DEFAULT_PROPOSAL_CACHE_CONFIG, ...config?.proposalCache };
|
|
5650
5653
|
this.quorumConfig = { ...DEFAULT_INCREMENTAL_QUORUM_CONFIG, ...config?.incrementalQuorum };
|
|
5651
|
-
this.logger =
|
|
5654
|
+
this.logger = logger11 ?? createLogger({ component: "ConsensusEngine" });
|
|
5652
5655
|
this.strategyFactory = new VotingStrategyFactory();
|
|
5653
5656
|
this.metrics = this.createInitialMetrics();
|
|
5654
5657
|
}
|
|
@@ -6090,8 +6093,8 @@ var ConsensusEngine = class {
|
|
|
6090
6093
|
this.logger.debug("Proposal cache cleared");
|
|
6091
6094
|
}
|
|
6092
6095
|
};
|
|
6093
|
-
function createConsensusEngine(config,
|
|
6094
|
-
return new ConsensusEngine(config,
|
|
6096
|
+
function createConsensusEngine(config, logger11) {
|
|
6097
|
+
return new ConsensusEngine(config, logger11);
|
|
6095
6098
|
}
|
|
6096
6099
|
|
|
6097
6100
|
// src/consensus/quorum-validator.ts
|
|
@@ -6099,8 +6102,8 @@ var DEFAULT_MIN_TRUST = 0.3;
|
|
|
6099
6102
|
var DEFAULT_MIN_WEIGHT = 0.1;
|
|
6100
6103
|
var QuorumValidator = class {
|
|
6101
6104
|
logger;
|
|
6102
|
-
constructor(
|
|
6103
|
-
this.logger =
|
|
6105
|
+
constructor(logger11) {
|
|
6106
|
+
this.logger = logger11 ?? createLogger({ component: "QuorumValidator" });
|
|
6104
6107
|
}
|
|
6105
6108
|
validateQuorum(input) {
|
|
6106
6109
|
const { votes, config } = input;
|
|
@@ -6327,8 +6330,8 @@ var QuorumValidator = class {
|
|
|
6327
6330
|
};
|
|
6328
6331
|
}
|
|
6329
6332
|
};
|
|
6330
|
-
function createQuorumValidator(
|
|
6331
|
-
return new QuorumValidator(
|
|
6333
|
+
function createQuorumValidator(logger11) {
|
|
6334
|
+
return new QuorumValidator(logger11);
|
|
6332
6335
|
}
|
|
6333
6336
|
|
|
6334
6337
|
// src/consensus/voting-protocol-helpers.ts
|
|
@@ -7908,6 +7911,35 @@ var ConsensusVoteInputSchema = z9.object({
|
|
|
7908
7911
|
quickMode: z9.boolean().optional().default(false).describe("Use 3 agents instead of 5 for faster execution"),
|
|
7909
7912
|
simulateVotes: z9.boolean().optional().default(false).describe(
|
|
7910
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."
|
|
7911
7943
|
)
|
|
7912
7944
|
});
|
|
7913
7945
|
function toAgentVoteSummary(result) {
|
|
@@ -8014,10 +8046,10 @@ import { appendFileSync as appendFileSync3, readFileSync as readFileSync2, write
|
|
|
8014
8046
|
var PersistentOutcomeStore = class extends OutcomeStore {
|
|
8015
8047
|
filePath;
|
|
8016
8048
|
logger;
|
|
8017
|
-
constructor(config,
|
|
8049
|
+
constructor(config, logger11) {
|
|
8018
8050
|
super(config);
|
|
8019
8051
|
this.filePath = config?.filePath ?? getOutcomesFile();
|
|
8020
|
-
this.logger =
|
|
8052
|
+
this.logger = logger11 ?? createLogger({ component: "PersistentOutcomeStore" });
|
|
8021
8053
|
const dataDir = config?.dataDir;
|
|
8022
8054
|
ensureLearningDir(dataDir);
|
|
8023
8055
|
this.hydrate();
|
|
@@ -8211,11 +8243,11 @@ var WARNED = /* @__PURE__ */ new Set();
|
|
|
8211
8243
|
function isTestRunner() {
|
|
8212
8244
|
return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
8213
8245
|
}
|
|
8214
|
-
function warnIfSimulatedOutsideTests(toolName,
|
|
8246
|
+
function warnIfSimulatedOutsideTests(toolName, logger11) {
|
|
8215
8247
|
if (isTestRunner()) return;
|
|
8216
8248
|
if (WARNED.has(toolName)) return;
|
|
8217
8249
|
WARNED.add(toolName);
|
|
8218
|
-
|
|
8250
|
+
logger11.warn(
|
|
8219
8251
|
`[${toolName}] simulateVotes=true: output is RANDOM and reserved for tests/demos. Do not treat the result as a real decision.`
|
|
8220
8252
|
);
|
|
8221
8253
|
}
|
|
@@ -8466,6 +8498,24 @@ var TOOL_ANNOTATIONS = {
|
|
|
8466
8498
|
idempotentHint: true,
|
|
8467
8499
|
openWorldHint: false
|
|
8468
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
|
+
},
|
|
8508
|
+
/** Cancels an in-flight async-mode job (#3042 Stage 1b / #2631). */
|
|
8509
|
+
cancel_job: {
|
|
8510
|
+
// Mutates the sidecar record — NOT read-only.
|
|
8511
|
+
readOnlyHint: false,
|
|
8512
|
+
// Reversibility: cancel writes a new state, but doesn't delete data —
|
|
8513
|
+
// not destructive in the OWASP sense (no irreversible data loss).
|
|
8514
|
+
destructiveHint: false,
|
|
8515
|
+
// Idempotent: repeat cancels are no-ops with explicit outcome envelope.
|
|
8516
|
+
idempotentHint: true,
|
|
8517
|
+
openWorldHint: false
|
|
8518
|
+
},
|
|
8469
8519
|
/** Verifies hash chain of audit log files. */
|
|
8470
8520
|
verify_audit_chain: {
|
|
8471
8521
|
readOnlyHint: true,
|
|
@@ -8539,7 +8589,334 @@ function getToolAnnotations(name) {
|
|
|
8539
8589
|
return a;
|
|
8540
8590
|
}
|
|
8541
8591
|
|
|
8592
|
+
// src/mcp/jobs/job-result-store.ts
|
|
8593
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8594
|
+
import { z as z10 } from "zod";
|
|
8595
|
+
var logger8 = createLogger({ component: "job-result-store" });
|
|
8596
|
+
var JobStatusSchema = z10.enum(["pending", "complete", "failed", "cancelled"]);
|
|
8597
|
+
var JobResultSchema = z10.object({
|
|
8598
|
+
/** Format version. Currently `1` — bump if the shape changes. */
|
|
8599
|
+
v: z10.literal(1),
|
|
8600
|
+
jobId: z10.string().min(1),
|
|
8601
|
+
/** Tool that was invoked (e.g. `orchestrate`). */
|
|
8602
|
+
toolName: z10.string().min(1),
|
|
8603
|
+
status: JobStatusSchema,
|
|
8604
|
+
createdAt: z10.iso.datetime(),
|
|
8605
|
+
/** Set when the job leaves `pending` — either via `complete` or `failed` or `cancelled`. */
|
|
8606
|
+
completedAt: z10.iso.datetime().optional(),
|
|
8607
|
+
/**
|
|
8608
|
+
* Structured payload the synchronous mode would have returned. Present
|
|
8609
|
+
* only when `status === 'complete'`. Shape is tool-specific — readers
|
|
8610
|
+
* cast to the tool's known output type after status check.
|
|
8611
|
+
*/
|
|
8612
|
+
result: z10.unknown().optional(),
|
|
8613
|
+
/**
|
|
8614
|
+
* Failure message when `status === 'failed'`. Cannot be paired with
|
|
8615
|
+
* `result` — the discriminator is `status`.
|
|
8616
|
+
*/
|
|
8617
|
+
error: z10.string().optional()
|
|
8618
|
+
});
|
|
8619
|
+
function jobResultPath(jobId) {
|
|
8620
|
+
return nexusDataPathEnsure("jobs", `result-${jobId}.json`);
|
|
8621
|
+
}
|
|
8622
|
+
function writeJobPending(jobId, toolName) {
|
|
8623
|
+
const path = jobResultPath(jobId);
|
|
8624
|
+
if (existsSync4(path)) {
|
|
8625
|
+
logger8.debug("Job result file already exists \u2014 leaving in place", { jobId });
|
|
8626
|
+
return;
|
|
8627
|
+
}
|
|
8628
|
+
const record = {
|
|
8629
|
+
v: 1,
|
|
8630
|
+
jobId,
|
|
8631
|
+
toolName,
|
|
8632
|
+
status: "pending",
|
|
8633
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8634
|
+
};
|
|
8635
|
+
writeFileSync3(path, JSON.stringify(record, null, 2));
|
|
8636
|
+
logger8.debug("Wrote pending job record", { jobId, toolName });
|
|
8637
|
+
}
|
|
8638
|
+
function writeJobComplete(jobId, toolName, result) {
|
|
8639
|
+
const record = {
|
|
8640
|
+
v: 1,
|
|
8641
|
+
jobId,
|
|
8642
|
+
toolName,
|
|
8643
|
+
status: "complete",
|
|
8644
|
+
createdAt: readJobResult(jobId)?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8645
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8646
|
+
result
|
|
8647
|
+
};
|
|
8648
|
+
writeFileSync3(jobResultPath(jobId), JSON.stringify(record, null, 2));
|
|
8649
|
+
logger8.debug("Wrote complete job record", { jobId, toolName });
|
|
8650
|
+
}
|
|
8651
|
+
function writeJobFailed(jobId, toolName, error) {
|
|
8652
|
+
const record = {
|
|
8653
|
+
v: 1,
|
|
8654
|
+
jobId,
|
|
8655
|
+
toolName,
|
|
8656
|
+
status: "failed",
|
|
8657
|
+
createdAt: readJobResult(jobId)?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8658
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8659
|
+
error
|
|
8660
|
+
};
|
|
8661
|
+
writeFileSync3(jobResultPath(jobId), JSON.stringify(record, null, 2));
|
|
8662
|
+
logger8.debug("Wrote failed job record", { jobId, toolName, error });
|
|
8663
|
+
}
|
|
8664
|
+
function writeJobCancelled(jobId, toolName, reason) {
|
|
8665
|
+
const record = {
|
|
8666
|
+
v: 1,
|
|
8667
|
+
jobId,
|
|
8668
|
+
toolName,
|
|
8669
|
+
status: "cancelled",
|
|
8670
|
+
createdAt: readJobResult(jobId)?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8671
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8672
|
+
...reason !== void 0 ? { error: reason } : {}
|
|
8673
|
+
};
|
|
8674
|
+
writeFileSync3(jobResultPath(jobId), JSON.stringify(record, null, 2));
|
|
8675
|
+
logger8.debug("Wrote cancelled job record", { jobId, toolName, reason });
|
|
8676
|
+
}
|
|
8677
|
+
function readJobResult(jobId) {
|
|
8678
|
+
const path = nexusDataPath("jobs", `result-${jobId}.json`);
|
|
8679
|
+
if (!existsSync4(path)) return null;
|
|
8680
|
+
try {
|
|
8681
|
+
const raw = JSON.parse(readFileSync3(path, "utf-8"));
|
|
8682
|
+
const parsed = JobResultSchema.safeParse(raw);
|
|
8683
|
+
if (!parsed.success) {
|
|
8684
|
+
logger8.warn("Job result file failed schema check", { jobId, path });
|
|
8685
|
+
return null;
|
|
8686
|
+
}
|
|
8687
|
+
return parsed.data;
|
|
8688
|
+
} catch (err2) {
|
|
8689
|
+
logger8.warn("Job result file unreadable", {
|
|
8690
|
+
jobId,
|
|
8691
|
+
path,
|
|
8692
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8693
|
+
});
|
|
8694
|
+
return null;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
function listJobs() {
|
|
8698
|
+
const dir = nexusDataPath("jobs");
|
|
8699
|
+
if (!existsSync4(dir)) return [];
|
|
8700
|
+
let entries;
|
|
8701
|
+
try {
|
|
8702
|
+
entries = readdirSync(dir);
|
|
8703
|
+
} catch (err2) {
|
|
8704
|
+
logger8.warn("jobs directory unreadable", {
|
|
8705
|
+
dir,
|
|
8706
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8707
|
+
});
|
|
8708
|
+
return [];
|
|
8709
|
+
}
|
|
8710
|
+
const summaries = [];
|
|
8711
|
+
for (const entry of entries) {
|
|
8712
|
+
const match = /^result-(.+)\.json$/.exec(entry);
|
|
8713
|
+
if (match === null) continue;
|
|
8714
|
+
const jobId = match[1];
|
|
8715
|
+
if (jobId === void 0) continue;
|
|
8716
|
+
const record = readJobResult(jobId);
|
|
8717
|
+
if (record === null) continue;
|
|
8718
|
+
const summary = {
|
|
8719
|
+
jobId: record.jobId,
|
|
8720
|
+
toolName: record.toolName,
|
|
8721
|
+
status: record.status,
|
|
8722
|
+
createdAt: record.createdAt,
|
|
8723
|
+
hasError: record.error !== void 0,
|
|
8724
|
+
...record.completedAt !== void 0 ? { completedAt: record.completedAt } : {}
|
|
8725
|
+
};
|
|
8726
|
+
summaries.push(summary);
|
|
8727
|
+
}
|
|
8728
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
8729
|
+
}
|
|
8730
|
+
|
|
8731
|
+
// src/mcp/jobs/job-idempotency.ts
|
|
8732
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
8733
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
8734
|
+
import { z as z11 } from "zod";
|
|
8735
|
+
var logger9 = createLogger({ component: "job-idempotency" });
|
|
8736
|
+
var IdempotencyIndexEntrySchema = z11.object({
|
|
8737
|
+
v: z11.literal(1),
|
|
8738
|
+
/** MCP tool name (e.g. `orchestrate`, `run_workflow`, `consensus_vote`). */
|
|
8739
|
+
tool: z11.string().min(1),
|
|
8740
|
+
/** Caller-supplied idempotency key. */
|
|
8741
|
+
key: z11.string().min(1),
|
|
8742
|
+
/** sha256 of the canonical input JSON. */
|
|
8743
|
+
inputsHash: z11.string().length(64),
|
|
8744
|
+
/** Existing jobId for this (tool, key, inputs) tuple. */
|
|
8745
|
+
jobId: z11.string().min(1),
|
|
8746
|
+
/** ISO timestamp of first dispatch. */
|
|
8747
|
+
createdAt: z11.iso.datetime()
|
|
8748
|
+
});
|
|
8749
|
+
function sha256Hex(input) {
|
|
8750
|
+
return createHash("sha256").update(input).digest("hex");
|
|
8751
|
+
}
|
|
8752
|
+
function canonicalJsonStringify(value) {
|
|
8753
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
8754
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJsonStringify).join(",")}]`;
|
|
8755
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
8756
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJsonStringify(v)}`).join(",")}}`;
|
|
8757
|
+
}
|
|
8758
|
+
function computeInputsHash(inputs) {
|
|
8759
|
+
return sha256Hex(canonicalJsonStringify(inputs));
|
|
8760
|
+
}
|
|
8761
|
+
function indexFilePath(tool, key) {
|
|
8762
|
+
const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
|
|
8763
|
+
return nexusDataPathEnsure("jobs", filename);
|
|
8764
|
+
}
|
|
8765
|
+
function indexFilePathReadOnly(tool, key) {
|
|
8766
|
+
const filename = `key-${sha256Hex(`${tool}:${key}`)}.json`;
|
|
8767
|
+
return nexusDataPath("jobs", filename);
|
|
8768
|
+
}
|
|
8769
|
+
function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = randomUUID3) {
|
|
8770
|
+
if (idempotencyKey === void 0 || idempotencyKey === "") {
|
|
8771
|
+
return { kind: "fresh", jobId: randomFallback() };
|
|
8772
|
+
}
|
|
8773
|
+
const incomingInputsHash = computeInputsHash(inputs);
|
|
8774
|
+
const path = indexFilePathReadOnly(tool, idempotencyKey);
|
|
8775
|
+
if (existsSync5(path)) {
|
|
8776
|
+
const entry = readIndexEntry(path);
|
|
8777
|
+
if (entry !== null) {
|
|
8778
|
+
if (entry.inputsHash === incomingInputsHash) {
|
|
8779
|
+
return { kind: "replay", jobId: entry.jobId, entry };
|
|
8780
|
+
}
|
|
8781
|
+
return {
|
|
8782
|
+
kind: "collision",
|
|
8783
|
+
existingInputsHash: entry.inputsHash,
|
|
8784
|
+
incomingInputsHash,
|
|
8785
|
+
existingJobId: entry.jobId
|
|
8786
|
+
};
|
|
8787
|
+
}
|
|
8788
|
+
logger9.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path });
|
|
8789
|
+
}
|
|
8790
|
+
const jobId = `job-${tool}-${sha256Hex(`${tool}:${idempotencyKey}:${incomingInputsHash}`).slice(0, 16)}`;
|
|
8791
|
+
return { kind: "fresh", jobId };
|
|
8792
|
+
}
|
|
8793
|
+
function registerIdempotentJob(params) {
|
|
8794
|
+
const path = indexFilePath(params.tool, params.idempotencyKey);
|
|
8795
|
+
if (existsSync5(path)) {
|
|
8796
|
+
logger9.debug("Idempotency index entry already present; skipping write", {
|
|
8797
|
+
tool: params.tool,
|
|
8798
|
+
jobId: params.jobId
|
|
8799
|
+
});
|
|
8800
|
+
return;
|
|
8801
|
+
}
|
|
8802
|
+
const entry = {
|
|
8803
|
+
v: 1,
|
|
8804
|
+
tool: params.tool,
|
|
8805
|
+
key: params.idempotencyKey,
|
|
8806
|
+
inputsHash: computeInputsHash(params.inputs),
|
|
8807
|
+
jobId: params.jobId,
|
|
8808
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8809
|
+
};
|
|
8810
|
+
writeFileSync4(path, JSON.stringify(entry, null, 2));
|
|
8811
|
+
logger9.debug("Wrote idempotency index entry", { tool: params.tool, jobId: params.jobId });
|
|
8812
|
+
}
|
|
8813
|
+
function shortCircuitOrFreshJobId(params) {
|
|
8814
|
+
const r = resolveIdempotency(
|
|
8815
|
+
params.tool,
|
|
8816
|
+
params.idempotencyKey,
|
|
8817
|
+
params.inputs,
|
|
8818
|
+
params.freshJobId
|
|
8819
|
+
);
|
|
8820
|
+
if (r.kind === "collision") {
|
|
8821
|
+
return { kind: "shortCircuit", value: params.collisionEnvelope(r.existingJobId) };
|
|
8822
|
+
}
|
|
8823
|
+
if (r.kind === "replay") {
|
|
8824
|
+
return { kind: "shortCircuit", value: params.replayEnvelope(r.jobId) };
|
|
8825
|
+
}
|
|
8826
|
+
return { kind: "continue", jobId: r.jobId };
|
|
8827
|
+
}
|
|
8828
|
+
function readIndexEntry(path) {
|
|
8829
|
+
try {
|
|
8830
|
+
const raw = JSON.parse(readFileSync4(path, "utf-8"));
|
|
8831
|
+
const parsed = IdempotencyIndexEntrySchema.safeParse(raw);
|
|
8832
|
+
if (!parsed.success) {
|
|
8833
|
+
logger9.warn("Idempotency index file failed schema check", { path });
|
|
8834
|
+
return null;
|
|
8835
|
+
}
|
|
8836
|
+
return parsed.data;
|
|
8837
|
+
} catch (err2) {
|
|
8838
|
+
logger9.warn("Idempotency index file read failed", {
|
|
8839
|
+
path,
|
|
8840
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
8841
|
+
});
|
|
8842
|
+
return null;
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
|
|
8846
|
+
// src/mcp/jobs/job-concurrency.ts
|
|
8847
|
+
var logger10 = createLogger({ component: "job-concurrency" });
|
|
8848
|
+
var DEFAULT_JOB_CAPS = {
|
|
8849
|
+
orchestrate: 3,
|
|
8850
|
+
run_workflow: 3,
|
|
8851
|
+
consensus_vote: 2,
|
|
8852
|
+
execute_expert: 4
|
|
8853
|
+
};
|
|
8854
|
+
var DEFAULT_GLOBAL_JOB_CAP = 10;
|
|
8855
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
8856
|
+
function getJobCap(toolName) {
|
|
8857
|
+
const envKey = `NEXUS_JOB_MAX_CONCURRENT_${toolName.toUpperCase()}`;
|
|
8858
|
+
const envValue = process.env[envKey];
|
|
8859
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
8860
|
+
const parsed = Number(envValue);
|
|
8861
|
+
if (Number.isInteger(parsed) && parsed >= 0) {
|
|
8862
|
+
return parsed;
|
|
8863
|
+
}
|
|
8864
|
+
logger10.warn("Invalid env override for job cap \u2014 ignoring", {
|
|
8865
|
+
tool: toolName,
|
|
8866
|
+
envKey,
|
|
8867
|
+
envValue
|
|
8868
|
+
});
|
|
8869
|
+
}
|
|
8870
|
+
return DEFAULT_JOB_CAPS[toolName] ?? DEFAULT_GLOBAL_JOB_CAP;
|
|
8871
|
+
}
|
|
8872
|
+
function getGlobalJobCap() {
|
|
8873
|
+
const envValue = process.env["NEXUS_JOB_MAX_CONCURRENT_TOTAL"];
|
|
8874
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
8875
|
+
const parsed = Number(envValue);
|
|
8876
|
+
if (Number.isInteger(parsed) && parsed >= 0) {
|
|
8877
|
+
return parsed;
|
|
8878
|
+
}
|
|
8879
|
+
logger10.warn("Invalid env override for global job cap \u2014 ignoring", {
|
|
8880
|
+
envKey: "NEXUS_JOB_MAX_CONCURRENT_TOTAL",
|
|
8881
|
+
envValue
|
|
8882
|
+
});
|
|
8883
|
+
}
|
|
8884
|
+
return DEFAULT_GLOBAL_JOB_CAP;
|
|
8885
|
+
}
|
|
8886
|
+
function getTotalInFlight() {
|
|
8887
|
+
let total = 0;
|
|
8888
|
+
for (const count of inFlight.values()) total += count;
|
|
8889
|
+
return total;
|
|
8890
|
+
}
|
|
8891
|
+
function tryAcquire(toolName) {
|
|
8892
|
+
const cap = getJobCap(toolName);
|
|
8893
|
+
if (cap === 0) return false;
|
|
8894
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8895
|
+
if (current >= cap) return false;
|
|
8896
|
+
const globalCap = getGlobalJobCap();
|
|
8897
|
+
if (globalCap === 0) return false;
|
|
8898
|
+
if (getTotalInFlight() >= globalCap) return false;
|
|
8899
|
+
inFlight.set(toolName, current + 1);
|
|
8900
|
+
return true;
|
|
8901
|
+
}
|
|
8902
|
+
function release(toolName) {
|
|
8903
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8904
|
+
if (current === 0) {
|
|
8905
|
+
logger10.warn("release() called with no in-flight count \u2014 caller bug", { tool: toolName });
|
|
8906
|
+
return;
|
|
8907
|
+
}
|
|
8908
|
+
inFlight.set(toolName, current - 1);
|
|
8909
|
+
}
|
|
8910
|
+
function suggestRetryAfterMs(toolName) {
|
|
8911
|
+
const cap = getJobCap(toolName);
|
|
8912
|
+
if (cap === 0) return 0;
|
|
8913
|
+
const current = inFlight.get(toolName) ?? 0;
|
|
8914
|
+
const base = 3e4 * (current / Math.max(1, cap));
|
|
8915
|
+
return Math.min(6e4, Math.max(5e3, base));
|
|
8916
|
+
}
|
|
8917
|
+
|
|
8542
8918
|
// src/mcp/tools/consensus-vote.ts
|
|
8919
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
8543
8920
|
var persistentCorrelationTracker;
|
|
8544
8921
|
function getOrCreateCorrelationTracker() {
|
|
8545
8922
|
persistentCorrelationTracker ??= createPersistentCorrelationTracker();
|
|
@@ -8648,22 +9025,22 @@ async function processVotesThroughEngine(engineVotes, proposal, algorithm) {
|
|
|
8648
9025
|
});
|
|
8649
9026
|
return resultRes.value;
|
|
8650
9027
|
}
|
|
8651
|
-
function runHigherOrderVoting(strategy, voteMap,
|
|
9028
|
+
function runHigherOrderVoting(strategy, voteMap, logger11) {
|
|
8652
9029
|
if (strategy !== "higher_order") return void 0;
|
|
8653
9030
|
const hovStrategy = new HigherOrderVotingStrategy();
|
|
8654
9031
|
const tracker = getOrCreateCorrelationTracker();
|
|
8655
9032
|
const result = hovStrategy.aggregate(voteMap, tracker);
|
|
8656
|
-
|
|
9033
|
+
logger11.info("Higher-Order Voting complete", {
|
|
8657
9034
|
method: result.method,
|
|
8658
9035
|
decision: result.decision,
|
|
8659
9036
|
posteriorApproval: result.posteriorApproval.toFixed(3)
|
|
8660
9037
|
});
|
|
8661
9038
|
return result;
|
|
8662
9039
|
}
|
|
8663
|
-
function recordVotesToTracker(votes, voteMap, outcome,
|
|
9040
|
+
function recordVotesToTracker(votes, voteMap, outcome, logger11) {
|
|
8664
9041
|
const allVotesReal = votes.every((v) => v.source === "llm");
|
|
8665
9042
|
if (!allVotesReal) {
|
|
8666
|
-
|
|
9043
|
+
logger11.warn("Skipping correlation recording due to non-LLM votes", {
|
|
8667
9044
|
count: votes.filter((v) => v.source !== "llm").length
|
|
8668
9045
|
});
|
|
8669
9046
|
return;
|
|
@@ -8671,16 +9048,16 @@ function recordVotesToTracker(votes, voteMap, outcome, logger8) {
|
|
|
8671
9048
|
const tracker = getOrCreateCorrelationTracker();
|
|
8672
9049
|
const id = `consensus-${String(getTimeProvider().now())}-${getRandomProvider().random().toString(36).slice(2, 9)}`;
|
|
8673
9050
|
tracker.recordProposalVotes(id, voteMap, outcome);
|
|
8674
|
-
|
|
9051
|
+
logger11.debug("Recorded votes to tracker", { proposalId: id, outcome });
|
|
8675
9052
|
try {
|
|
8676
9053
|
const persisted = createPersistedProposal(id, voteMap, outcome);
|
|
8677
9054
|
const saveResult = saveCorrelationData([persisted]);
|
|
8678
9055
|
if (!saveResult.ok) {
|
|
8679
|
-
|
|
9056
|
+
logger11.warn("Failed to persist correlation data", { error: saveResult.error.message });
|
|
8680
9057
|
}
|
|
8681
9058
|
} catch (error) {
|
|
8682
9059
|
const message = getErrorMessage(error);
|
|
8683
|
-
|
|
9060
|
+
logger11.warn("Error persisting correlation data", { error: message });
|
|
8684
9061
|
}
|
|
8685
9062
|
}
|
|
8686
9063
|
async function processVotesWithCascade(engineVotes, opts) {
|
|
@@ -8705,7 +9082,7 @@ async function processVotesWithCascade(engineVotes, opts) {
|
|
|
8705
9082
|
var CONTRARIAN_ESCALATION_THRESHOLD = 0.8;
|
|
8706
9083
|
async function runContrarianCheck(proposal, log) {
|
|
8707
9084
|
try {
|
|
8708
|
-
const { executeExpert } = await import("./expert-bridge-
|
|
9085
|
+
const { executeExpert } = await import("./expert-bridge-22UVN5TB.js");
|
|
8709
9086
|
const prompt = [
|
|
8710
9087
|
"You are a contrarian analyst. Your job is to find reasons this proposal should be REJECTED.",
|
|
8711
9088
|
"Look for: YAGNI (not needed), MISALIGNED (wrong tech/architecture), SECURITY_RISK, SCOPE_CREEP.",
|
|
@@ -8755,23 +9132,23 @@ function buildPolicyShortCircuitResult(args) {
|
|
|
8755
9132
|
strategy: args.strategy
|
|
8756
9133
|
};
|
|
8757
9134
|
}
|
|
8758
|
-
async function maybeEscalateContrarian(input, outcome,
|
|
9135
|
+
async function maybeEscalateContrarian(input, outcome, logger11, opts) {
|
|
8759
9136
|
if (!input.quickMode || outcome !== "approved" || input.simulateVotes) return void 0;
|
|
8760
|
-
const escalation = await runContrarianCheck(input.proposal,
|
|
9137
|
+
const escalation = await runContrarianCheck(input.proposal, logger11);
|
|
8761
9138
|
if (!escalation.shouldEscalate) return void 0;
|
|
8762
|
-
|
|
9139
|
+
logger11.warn("Contrarian escalation: re-running with full vote", {
|
|
8763
9140
|
reason: escalation.reason,
|
|
8764
9141
|
confidence: escalation.confidence
|
|
8765
9142
|
});
|
|
8766
|
-
return executeVoting({ ...input, quickMode: false },
|
|
9143
|
+
return executeVoting({ ...input, quickMode: false }, logger11, opts);
|
|
8767
9144
|
}
|
|
8768
|
-
async function executeVoting(input,
|
|
9145
|
+
async function executeVoting(input, logger11, opts) {
|
|
8769
9146
|
const strategy = resolveStrategy(input);
|
|
8770
9147
|
const algorithm = strategyToAlgorithm(strategy);
|
|
8771
9148
|
const roles = getVoterRoles(input.quickMode);
|
|
8772
9149
|
const startTime = getTimeProvider().now();
|
|
8773
9150
|
const errorPolicy = input.errorPolicy ?? getDefaultErrorPolicy(strategy);
|
|
8774
|
-
|
|
9151
|
+
logger11.info("Starting consensus vote", {
|
|
8775
9152
|
strategy,
|
|
8776
9153
|
algorithm,
|
|
8777
9154
|
roleCount: roles.length,
|
|
@@ -8793,7 +9170,7 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8793
9170
|
errorPolicy,
|
|
8794
9171
|
reason: policyDecision.reason ?? "error policy short-circuit",
|
|
8795
9172
|
startTime,
|
|
8796
|
-
logger:
|
|
9173
|
+
logger: logger11
|
|
8797
9174
|
});
|
|
8798
9175
|
}
|
|
8799
9176
|
const { engineResult, voteMap, higherOrderResult, outcome, cascaded } = await processVotesWithCascade(policyDecision.engineVotes, {
|
|
@@ -8801,10 +9178,10 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8801
9178
|
proposal: input.proposal,
|
|
8802
9179
|
algorithm,
|
|
8803
9180
|
strategy,
|
|
8804
|
-
log:
|
|
9181
|
+
log: logger11
|
|
8805
9182
|
});
|
|
8806
|
-
recordVotesToTracker(votes, voteMap, outcome,
|
|
8807
|
-
const escalated = await maybeEscalateContrarian(input, outcome,
|
|
9183
|
+
recordVotesToTracker(votes, voteMap, outcome, logger11);
|
|
9184
|
+
const escalated = await maybeEscalateContrarian(input, outcome, logger11, opts);
|
|
8808
9185
|
if (escalated !== void 0) return escalated;
|
|
8809
9186
|
return finalizeVotingResult({
|
|
8810
9187
|
input,
|
|
@@ -8816,7 +9193,7 @@ async function executeVoting(input, logger8, opts) {
|
|
|
8816
9193
|
outcome,
|
|
8817
9194
|
cascaded,
|
|
8818
9195
|
startTime,
|
|
8819
|
-
logger:
|
|
9196
|
+
logger: logger11
|
|
8820
9197
|
});
|
|
8821
9198
|
}
|
|
8822
9199
|
function finalizeVotingResult(args) {
|
|
@@ -8840,15 +9217,15 @@ function finalizeVotingResult(args) {
|
|
|
8840
9217
|
return result;
|
|
8841
9218
|
}
|
|
8842
9219
|
async function handleConsensusVote(deps, args) {
|
|
8843
|
-
const
|
|
8844
|
-
if (args.simulateVotes) warnIfSimulatedOutsideTests("consensus_vote",
|
|
9220
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
9221
|
+
if (args.simulateVotes) warnIfSimulatedOutsideTests("consensus_vote", logger11);
|
|
8845
9222
|
try {
|
|
8846
|
-
const result = await executeVoting(args,
|
|
9223
|
+
const result = await executeVoting(args, logger11);
|
|
8847
9224
|
const strategy = args.strategy ?? "simple_majority";
|
|
8848
9225
|
const errorVotes = result.votes.filter((v) => v.source === "error");
|
|
8849
9226
|
if (errorVotes.length === result.votes.length && result.votes.length > 0) {
|
|
8850
9227
|
const failures = errorVotes.map((v) => `${v.role}: ${v.error ?? "unknown error"}`).join("; ");
|
|
8851
|
-
|
|
9228
|
+
logger11.warn("All voters failed", { failureCount: errorVotes.length, failures });
|
|
8852
9229
|
recordVoteError(args.proposal, `All ${String(errorVotes.length)} voters failed: ${failures}`);
|
|
8853
9230
|
return {
|
|
8854
9231
|
ok: false,
|
|
@@ -8866,11 +9243,109 @@ async function handleConsensusVote(deps, args) {
|
|
|
8866
9243
|
} catch (error) {
|
|
8867
9244
|
const message = getErrorMessage(error);
|
|
8868
9245
|
const cause = error instanceof Error ? error : new Error(message);
|
|
8869
|
-
|
|
9246
|
+
logger11.error("Consensus vote failed", cause);
|
|
8870
9247
|
recordVoteError(args.proposal, message);
|
|
8871
9248
|
return { ok: false, error: `Voting failed: ${message}` };
|
|
8872
9249
|
}
|
|
8873
9250
|
}
|
|
9251
|
+
function buildConsensusVoteReplayEnvelope(jobId) {
|
|
9252
|
+
return toolSuccess(
|
|
9253
|
+
JSON.stringify({
|
|
9254
|
+
status: "replay",
|
|
9255
|
+
jobId,
|
|
9256
|
+
pollTool: "get_job_result",
|
|
9257
|
+
note: "Idempotency key matched a prior dispatch \u2014 poll get_job_result for current status."
|
|
9258
|
+
})
|
|
9259
|
+
);
|
|
9260
|
+
}
|
|
9261
|
+
function buildConsensusVoteCollisionEnvelope(existingJobId) {
|
|
9262
|
+
return toolStructuredError({
|
|
9263
|
+
errorCategory: "validation",
|
|
9264
|
+
message: `Idempotency key already used with different inputs. Existing jobId: ${existingJobId}. Use a fresh key or omit it.`
|
|
9265
|
+
});
|
|
9266
|
+
}
|
|
9267
|
+
function dispatchAsyncConsensusVote(deps, args) {
|
|
9268
|
+
const idempotency = shortCircuitOrFreshJobId({
|
|
9269
|
+
tool: "consensus_vote",
|
|
9270
|
+
idempotencyKey: args.idempotencyKey,
|
|
9271
|
+
inputs: args,
|
|
9272
|
+
freshJobId: () => `job-vote-${randomUUID4()}`,
|
|
9273
|
+
replayEnvelope: buildConsensusVoteReplayEnvelope,
|
|
9274
|
+
collisionEnvelope: buildConsensusVoteCollisionEnvelope
|
|
9275
|
+
});
|
|
9276
|
+
if (idempotency.kind === "shortCircuit") return idempotency.value;
|
|
9277
|
+
if (!tryAcquire("consensus_vote")) {
|
|
9278
|
+
return toolSuccess(
|
|
9279
|
+
JSON.stringify({
|
|
9280
|
+
status: "busy",
|
|
9281
|
+
retryAfterMs: suggestRetryAfterMs("consensus_vote"),
|
|
9282
|
+
note: 'Async-mode concurrency cap reached for consensus_vote. Retry later or use mode: "sync".'
|
|
9283
|
+
})
|
|
9284
|
+
);
|
|
9285
|
+
}
|
|
9286
|
+
const jobId = idempotency.jobId;
|
|
9287
|
+
recordConsensusVotePending(jobId, args);
|
|
9288
|
+
void runConsensusVoteInBackground(jobId, deps, args);
|
|
9289
|
+
return toolSuccess(
|
|
9290
|
+
JSON.stringify({
|
|
9291
|
+
status: "pending",
|
|
9292
|
+
jobId,
|
|
9293
|
+
pollTool: "get_job_result",
|
|
9294
|
+
note: 'Poll via get_job_result({ jobId }) until status !== "pending".'
|
|
9295
|
+
})
|
|
9296
|
+
);
|
|
9297
|
+
}
|
|
9298
|
+
function recordConsensusVotePending(jobId, args) {
|
|
9299
|
+
writeJobPending(jobId, "consensus_vote");
|
|
9300
|
+
if (args.idempotencyKey !== void 0 && args.idempotencyKey !== "") {
|
|
9301
|
+
registerIdempotentJob({
|
|
9302
|
+
tool: "consensus_vote",
|
|
9303
|
+
idempotencyKey: args.idempotencyKey,
|
|
9304
|
+
inputs: args,
|
|
9305
|
+
jobId
|
|
9306
|
+
});
|
|
9307
|
+
}
|
|
9308
|
+
}
|
|
9309
|
+
async function runConsensusVoteInBackground(jobId, deps, args) {
|
|
9310
|
+
try {
|
|
9311
|
+
const result = await handleConsensusVote(deps, args);
|
|
9312
|
+
writeJobComplete(jobId, "consensus_vote", result);
|
|
9313
|
+
} catch (err2) {
|
|
9314
|
+
const errObj = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
9315
|
+
deps.logger?.error("Async consensus_vote dispatch failed", errObj, { jobId });
|
|
9316
|
+
writeJobFailed(jobId, "consensus_vote", errObj.message);
|
|
9317
|
+
} finally {
|
|
9318
|
+
release("consensus_vote");
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
9321
|
+
async function runSyncConsensusVote(deps, notifier, args) {
|
|
9322
|
+
const result = await withProgressHeartbeat(
|
|
9323
|
+
"consensus_vote",
|
|
9324
|
+
notifier,
|
|
9325
|
+
() => handleConsensusVote(deps, args)
|
|
9326
|
+
);
|
|
9327
|
+
if (!result.ok) {
|
|
9328
|
+
return toolStructuredError({ errorCategory: "internal", message: result.error });
|
|
9329
|
+
}
|
|
9330
|
+
for (const vote of result.value.votes) {
|
|
9331
|
+
notifier.debug("consensus_vote", {
|
|
9332
|
+
event: "vote_collected",
|
|
9333
|
+
role: vote.role,
|
|
9334
|
+
decision: vote.decision
|
|
9335
|
+
});
|
|
9336
|
+
}
|
|
9337
|
+
notifier.info("consensus_vote", {
|
|
9338
|
+
event: "vote_complete",
|
|
9339
|
+
decision: result.value.decision,
|
|
9340
|
+
approvalPercentage: result.value.approvalPercentage,
|
|
9341
|
+
voteCount: result.value.votes.length
|
|
9342
|
+
});
|
|
9343
|
+
const data = result.value;
|
|
9344
|
+
return {
|
|
9345
|
+
...toolSuccess(JSON.stringify(result.value, null, 2)),
|
|
9346
|
+
structuredContent: data
|
|
9347
|
+
};
|
|
9348
|
+
}
|
|
8874
9349
|
function createConsensusVoteHandler(deps) {
|
|
8875
9350
|
const notifier = deps.notifier ?? NOOP_NOTIFIER;
|
|
8876
9351
|
return async (args, ctx) => {
|
|
@@ -8884,63 +9359,48 @@ function createConsensusVoteHandler(deps) {
|
|
|
8884
9359
|
const strategy = validationResult.data.strategy ?? "simple_majority";
|
|
8885
9360
|
ctx.logger.debug("Starting consensus vote", {
|
|
8886
9361
|
strategy,
|
|
8887
|
-
quickMode: validationResult.data.quickMode
|
|
9362
|
+
quickMode: validationResult.data.quickMode,
|
|
9363
|
+
...validationResult.data.mode !== void 0 ? { mode: validationResult.data.mode } : {}
|
|
8888
9364
|
});
|
|
8889
9365
|
notifier.info("consensus_vote", {
|
|
8890
9366
|
event: "vote_start",
|
|
8891
9367
|
proposalLength: validationResult.data.proposal.length,
|
|
8892
9368
|
strategy
|
|
8893
9369
|
});
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
notifier,
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
return toolStructuredError({ errorCategory: "internal", message: result.error });
|
|
8901
|
-
}
|
|
8902
|
-
for (const vote of result.value.votes) {
|
|
8903
|
-
notifier.debug("consensus_vote", {
|
|
8904
|
-
event: "vote_collected",
|
|
8905
|
-
role: vote.role,
|
|
8906
|
-
decision: vote.decision
|
|
9370
|
+
if (validationResult.data.mode === "async") {
|
|
9371
|
+
const asyncResult = dispatchAsyncConsensusVote(deps, validationResult.data);
|
|
9372
|
+
notifier.info("consensus_vote", {
|
|
9373
|
+
event: "vote_dispatched_async",
|
|
9374
|
+
proposalLength: validationResult.data.proposal.length,
|
|
9375
|
+
strategy
|
|
8907
9376
|
});
|
|
9377
|
+
return asyncResult;
|
|
8908
9378
|
}
|
|
8909
|
-
|
|
8910
|
-
event: "vote_complete",
|
|
8911
|
-
decision: result.value.decision,
|
|
8912
|
-
approvalPercentage: result.value.approvalPercentage,
|
|
8913
|
-
voteCount: result.value.votes.length
|
|
8914
|
-
});
|
|
8915
|
-
const data = result.value;
|
|
8916
|
-
return {
|
|
8917
|
-
...toolSuccess(JSON.stringify(result.value, null, 2)),
|
|
8918
|
-
structuredContent: data
|
|
8919
|
-
};
|
|
9379
|
+
return runSyncConsensusVote(deps, notifier, validationResult.data);
|
|
8920
9380
|
};
|
|
8921
9381
|
}
|
|
8922
9382
|
var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
8923
|
-
proposal:
|
|
9383
|
+
proposal: z12.string(),
|
|
8924
9384
|
strategy: VotingStrategySchema,
|
|
8925
|
-
decision:
|
|
8926
|
-
approvalPercentage:
|
|
8927
|
-
voteCounts:
|
|
8928
|
-
approve:
|
|
8929
|
-
reject:
|
|
8930
|
-
abstain:
|
|
8931
|
-
error:
|
|
9385
|
+
decision: z12.enum(["approved", "rejected", "no_quorum"]),
|
|
9386
|
+
approvalPercentage: z12.number(),
|
|
9387
|
+
voteCounts: z12.object({
|
|
9388
|
+
approve: z12.number(),
|
|
9389
|
+
reject: z12.number(),
|
|
9390
|
+
abstain: z12.number(),
|
|
9391
|
+
error: z12.number()
|
|
8932
9392
|
}),
|
|
8933
|
-
votes:
|
|
8934
|
-
|
|
8935
|
-
role:
|
|
8936
|
-
decision:
|
|
8937
|
-
confidence:
|
|
8938
|
-
reasoning:
|
|
8939
|
-
simulated:
|
|
8940
|
-
error:
|
|
8941
|
-
modelUsed:
|
|
8942
|
-
rejectionCategories:
|
|
8943
|
-
|
|
9393
|
+
votes: z12.array(
|
|
9394
|
+
z12.object({
|
|
9395
|
+
role: z12.string().max(100),
|
|
9396
|
+
decision: z12.enum(["approve", "reject", "abstain"]),
|
|
9397
|
+
confidence: z12.number(),
|
|
9398
|
+
reasoning: z12.string().max(4e3),
|
|
9399
|
+
simulated: z12.boolean(),
|
|
9400
|
+
error: z12.boolean(),
|
|
9401
|
+
modelUsed: z12.string().max(100).optional(),
|
|
9402
|
+
rejectionCategories: z12.array(
|
|
9403
|
+
z12.enum([
|
|
8944
9404
|
"YAGNI",
|
|
8945
9405
|
"DRY_VIOLATION",
|
|
8946
9406
|
"OVER_ENGINEERING",
|
|
@@ -8953,42 +9413,42 @@ var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
|
8953
9413
|
})
|
|
8954
9414
|
),
|
|
8955
9415
|
threshold: VoteThresholdSchema.optional(),
|
|
8956
|
-
durationMs:
|
|
8957
|
-
simulateVotes:
|
|
8958
|
-
higherOrderMetadata:
|
|
8959
|
-
posteriorApproval:
|
|
8960
|
-
posteriorRejection:
|
|
8961
|
-
effectiveVoteCount:
|
|
8962
|
-
method:
|
|
8963
|
-
usedCorrelationData:
|
|
8964
|
-
improvementOverBaseline:
|
|
8965
|
-
downweightedAgents:
|
|
8966
|
-
reasoning:
|
|
9416
|
+
durationMs: z12.number(),
|
|
9417
|
+
simulateVotes: z12.boolean(),
|
|
9418
|
+
higherOrderMetadata: z12.object({
|
|
9419
|
+
posteriorApproval: z12.number(),
|
|
9420
|
+
posteriorRejection: z12.number(),
|
|
9421
|
+
effectiveVoteCount: z12.number(),
|
|
9422
|
+
method: z12.enum(["ow", "isp", "simple"]),
|
|
9423
|
+
usedCorrelationData: z12.boolean(),
|
|
9424
|
+
improvementOverBaseline: z12.number(),
|
|
9425
|
+
downweightedAgents: z12.array(z12.string().max(100)).max(10),
|
|
9426
|
+
reasoning: z12.string().max(2e3)
|
|
8967
9427
|
}).optional()
|
|
8968
9428
|
};
|
|
8969
9429
|
function registerConsensusVoteTool(server, deps) {
|
|
8970
|
-
const
|
|
9430
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
8971
9431
|
const notifier = deps.notifier ?? createMcpNotifier(server);
|
|
8972
9432
|
const depsWithNotifier = { ...deps, notifier };
|
|
8973
9433
|
const toolSchema = {
|
|
8974
|
-
proposal:
|
|
8975
|
-
threshold:
|
|
9434
|
+
proposal: z12.string().min(1).max(MAX_PROPOSAL_LENGTH).describe("Proposal text to vote on"),
|
|
9435
|
+
threshold: z12.enum(["majority", "supermajority", "unanimous"]).optional().describe("Voting threshold (legacy). Use strategy instead."),
|
|
8976
9436
|
strategy: VotingStrategySchema.optional().describe(
|
|
8977
9437
|
"Voting strategy: simple_majority (default), supermajority, unanimous, proof_of_learning, or higher_order"
|
|
8978
9438
|
),
|
|
8979
|
-
quickMode:
|
|
8980
|
-
simulateVotes:
|
|
9439
|
+
quickMode: z12.boolean().optional().default(false).describe("Use 3 agents instead of 7"),
|
|
9440
|
+
simulateVotes: z12.boolean().optional().default(false).describe("TESTS ONLY \u2014 random output, must not be used for real decisions (#2319)")
|
|
8981
9441
|
};
|
|
8982
9442
|
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).";
|
|
8983
9443
|
const secureHandler = createSecureHandler(createConsensusVoteHandler(depsWithNotifier), {
|
|
8984
9444
|
toolName: "consensus_vote",
|
|
8985
9445
|
rateLimiter: deps.rateLimiter,
|
|
8986
|
-
logger:
|
|
9446
|
+
logger: logger11
|
|
8987
9447
|
});
|
|
8988
9448
|
const timeoutMs = getToolTimeout("consensus_vote", deps.security);
|
|
8989
9449
|
const wrappedHandler = wrapToolWithTimeout("consensus_vote", secureHandler, {
|
|
8990
9450
|
timeoutMs,
|
|
8991
|
-
logger:
|
|
9451
|
+
logger: logger11
|
|
8992
9452
|
});
|
|
8993
9453
|
server.registerTool(
|
|
8994
9454
|
"consensus_vote",
|
|
@@ -8998,9 +9458,9 @@ function registerConsensusVoteTool(server, deps) {
|
|
|
8998
9458
|
outputSchema: CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
8999
9459
|
annotations: getToolAnnotations("consensus_vote")
|
|
9000
9460
|
},
|
|
9001
|
-
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs,
|
|
9461
|
+
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs, logger11)
|
|
9002
9462
|
);
|
|
9003
|
-
|
|
9463
|
+
logger11.info("Registered consensus_vote tool with secure handler and timeout protection");
|
|
9004
9464
|
}
|
|
9005
9465
|
|
|
9006
9466
|
export {
|
|
@@ -9053,6 +9513,18 @@ export {
|
|
|
9053
9513
|
toSdkCallbackWithBudgetCheck,
|
|
9054
9514
|
getToolAnnotations,
|
|
9055
9515
|
PersistentOutcomeStore,
|
|
9516
|
+
JobStatusSchema,
|
|
9517
|
+
writeJobPending,
|
|
9518
|
+
writeJobComplete,
|
|
9519
|
+
writeJobFailed,
|
|
9520
|
+
writeJobCancelled,
|
|
9521
|
+
readJobResult,
|
|
9522
|
+
listJobs,
|
|
9523
|
+
registerIdempotentJob,
|
|
9524
|
+
shortCircuitOrFreshJobId,
|
|
9525
|
+
tryAcquire,
|
|
9526
|
+
release,
|
|
9527
|
+
suggestRetryAfterMs,
|
|
9056
9528
|
DEFAULT_VOTE_TIMEOUT_MS,
|
|
9057
9529
|
isRateLimitError,
|
|
9058
9530
|
VOTER_ROLES,
|
|
@@ -9122,4 +9594,4 @@ export {
|
|
|
9122
9594
|
CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
9123
9595
|
registerConsensusVoteTool
|
|
9124
9596
|
};
|
|
9125
|
-
//# sourceMappingURL=chunk-
|
|
9597
|
+
//# sourceMappingURL=chunk-EHBSXLX6.js.map
|