nexus-agents 3.3.2 → 3.4.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/{chunk-UNDYHLRR.js → chunk-2KVTXC7Y.js} +6 -1
- package/dist/{chunk-UNDYHLRR.js.map → chunk-2KVTXC7Y.js.map} +1 -1
- package/dist/{chunk-VSFWQHRX.js → chunk-CFK4VK2M.js} +6 -6
- package/dist/{chunk-LVFYELQU.js → chunk-FHTU4BSP.js} +3 -3
- package/dist/{chunk-3T3TZZKG.js → chunk-JEV6RUUH.js} +2 -2
- package/dist/{chunk-RA5L4BVA.js → chunk-QAEBYGSP.js} +2 -2
- package/dist/{chunk-RAYKASM4.js → chunk-XL6T3HTN.js} +261 -31
- package/dist/chunk-XL6T3HTN.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +7 -7
- package/dist/{consensus-vote-UF73LBG6.js → consensus-vote-EIEPPKAD.js} +3 -3
- package/dist/{consensus-vote-types-C7kiyyqD.d.ts → consensus-vote-types-jDyddCzg.d.ts} +27 -0
- package/dist/{improvement-review-B7DR4Y3C.js → improvement-review-YYHQ2D5L.js} +3 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -6
- package/dist/{setup-command-COUBH2NS.js → setup-command-CYZO22RO.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-RAYKASM4.js.map +0 -1
- /package/dist/{chunk-VSFWQHRX.js.map → chunk-CFK4VK2M.js.map} +0 -0
- /package/dist/{chunk-LVFYELQU.js.map → chunk-FHTU4BSP.js.map} +0 -0
- /package/dist/{chunk-3T3TZZKG.js.map → chunk-JEV6RUUH.js.map} +0 -0
- /package/dist/{chunk-RA5L4BVA.js.map → chunk-QAEBYGSP.js.map} +0 -0
- /package/dist/{consensus-vote-UF73LBG6.js.map → consensus-vote-EIEPPKAD.js.map} +0 -0
- /package/dist/{improvement-review-B7DR4Y3C.js.map → improvement-review-YYHQ2D5L.js.map} +0 -0
- /package/dist/{setup-command-COUBH2NS.js.map → setup-command-CYZO22RO.js.map} +0 -0
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
DEFAULT_TASK_TTL_MS,
|
|
22
22
|
DEFAULT_TOOL_RATE_LIMITS,
|
|
23
23
|
clampTaskTtl
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-FHTU4BSP.js";
|
|
25
25
|
import {
|
|
26
26
|
executeExpert
|
|
27
27
|
} from "./chunk-H3B6BSBZ.js";
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
simulationDeniedResult,
|
|
46
46
|
toJobSummary,
|
|
47
47
|
writeJobCancelled
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-XL6T3HTN.js";
|
|
49
49
|
import {
|
|
50
50
|
normalizeTopicToCanonical,
|
|
51
51
|
synthesizeResearch
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
} from "./chunk-G2C4WNK7.js";
|
|
60
60
|
import {
|
|
61
61
|
withPrerequisite
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-JEV6RUUH.js";
|
|
63
63
|
import {
|
|
64
64
|
NOOP_NOTIFIER,
|
|
65
65
|
RateLimiter,
|
|
@@ -82,7 +82,7 @@ import {
|
|
|
82
82
|
withAuditTrail,
|
|
83
83
|
withProgressHeartbeat,
|
|
84
84
|
wrapToolWithTimeout
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-2KVTXC7Y.js";
|
|
86
86
|
import {
|
|
87
87
|
createAuditTrail,
|
|
88
88
|
createGraphAuditBridge,
|
|
@@ -45787,7 +45787,7 @@ ${contextBlock}`;
|
|
|
45787
45787
|
const strategy = config.votingStrategy ?? "higher_order";
|
|
45788
45788
|
await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
|
|
45789
45789
|
try {
|
|
45790
|
-
const { executeVoting } = await import("./consensus-vote-
|
|
45790
|
+
const { executeVoting } = await import("./consensus-vote-EIEPPKAD.js");
|
|
45791
45791
|
const votingResult = await executeVoting(
|
|
45792
45792
|
{
|
|
45793
45793
|
proposal: buildVoteProposal(plan, research),
|
|
@@ -54166,4 +54166,4 @@ export {
|
|
|
54166
54166
|
shutdownFeedbackSubscriber,
|
|
54167
54167
|
createEventBusBridge
|
|
54168
54168
|
};
|
|
54169
|
-
//# sourceMappingURL=chunk-
|
|
54169
|
+
//# sourceMappingURL=chunk-CFK4VK2M.js.map
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
} from "./chunk-OQLFRJCW.js";
|
|
50
50
|
|
|
51
51
|
// src/version.ts
|
|
52
|
-
var VERSION = true ? "3.
|
|
52
|
+
var VERSION = true ? "3.4.0" : "dev";
|
|
53
53
|
|
|
54
54
|
// src/config/schemas-core.ts
|
|
55
55
|
import { z } from "zod";
|
|
@@ -2253,7 +2253,7 @@ async function runDoctorFix(result) {
|
|
|
2253
2253
|
writeLine2("\u2500".repeat(40));
|
|
2254
2254
|
let fixCount = 0;
|
|
2255
2255
|
if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
|
|
2256
|
-
const { runSetup } = await import("./setup-command-
|
|
2256
|
+
const { runSetup } = await import("./setup-command-CYZO22RO.js");
|
|
2257
2257
|
const setupResult = runSetup({
|
|
2258
2258
|
skipMcp: true,
|
|
2259
2259
|
skipRules: true,
|
|
@@ -2366,4 +2366,4 @@ export {
|
|
|
2366
2366
|
startStdioServer,
|
|
2367
2367
|
closeServer
|
|
2368
2368
|
};
|
|
2369
|
-
//# sourceMappingURL=chunk-
|
|
2369
|
+
//# sourceMappingURL=chunk-FHTU4BSP.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
toolStructuredError,
|
|
8
8
|
toolSuccessStructured,
|
|
9
9
|
wrapToolWithTimeout
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2KVTXC7Y.js";
|
|
11
11
|
import {
|
|
12
12
|
JsonlStore,
|
|
13
13
|
SECURITY_KEYWORDS,
|
|
@@ -2132,4 +2132,4 @@ export {
|
|
|
2132
2132
|
runImprovementReview,
|
|
2133
2133
|
registerImprovementReviewTool
|
|
2134
2134
|
};
|
|
2135
|
-
//# sourceMappingURL=chunk-
|
|
2135
|
+
//# sourceMappingURL=chunk-JEV6RUUH.js.map
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
checkSqlite,
|
|
9
9
|
defaultConfig,
|
|
10
10
|
initDataDirectories
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FHTU4BSP.js";
|
|
12
12
|
import {
|
|
13
13
|
BUILT_IN_EXPERTS
|
|
14
14
|
} from "./chunk-YPNQPT2F.js";
|
|
@@ -2001,4 +2001,4 @@ export {
|
|
|
2001
2001
|
setupCommand,
|
|
2002
2002
|
setupCommandAsync
|
|
2003
2003
|
};
|
|
2004
|
-
//# sourceMappingURL=chunk-
|
|
2004
|
+
//# sourceMappingURL=chunk-QAEBYGSP.js.map
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
toolSuccess,
|
|
19
19
|
withProgressHeartbeat,
|
|
20
20
|
wrapToolWithTimeout
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-2KVTXC7Y.js";
|
|
22
22
|
import {
|
|
23
23
|
createEvent,
|
|
24
24
|
getGlobalRegistry
|
|
@@ -369,6 +369,57 @@ var SIMULATED_VOTE_REASONING = {
|
|
|
369
369
|
|
|
370
370
|
// src/cli/voter-response.ts
|
|
371
371
|
import { z } from "zod";
|
|
372
|
+
|
|
373
|
+
// src/consensus/option-tally.ts
|
|
374
|
+
var SUPERMAJORITY_SHARE = 2 / 3;
|
|
375
|
+
var MAJORITY_SHARE = 0.5;
|
|
376
|
+
function matchDeclaredOption(raw, declaredOptions) {
|
|
377
|
+
if (raw === void 0) return void 0;
|
|
378
|
+
const normalized = raw.trim().toLowerCase();
|
|
379
|
+
if (normalized === "") return void 0;
|
|
380
|
+
return declaredOptions.find((option) => option.trim().toLowerCase() === normalized);
|
|
381
|
+
}
|
|
382
|
+
function tallyOptions(votes, declaredOptions) {
|
|
383
|
+
const approvers = votes.filter((v) => v.decision === "approve");
|
|
384
|
+
const counts = /* @__PURE__ */ new Map();
|
|
385
|
+
let selectedCount = 0;
|
|
386
|
+
for (const vote of approvers) {
|
|
387
|
+
const matched = matchDeclaredOption(vote.selectedOption, declaredOptions);
|
|
388
|
+
if (matched === void 0) continue;
|
|
389
|
+
selectedCount += 1;
|
|
390
|
+
counts.set(matched, (counts.get(matched) ?? 0) + 1);
|
|
391
|
+
}
|
|
392
|
+
const tally = [...counts.entries()].map(([option, count]) => ({ option, count })).sort((a, b) => b.count !== a.count ? b.count - a.count : a.option.localeCompare(b.option));
|
|
393
|
+
const leader = tally[0];
|
|
394
|
+
const approverCount = approvers.length;
|
|
395
|
+
const leadingCount = leader?.count ?? 0;
|
|
396
|
+
return {
|
|
397
|
+
tally,
|
|
398
|
+
...leader !== void 0 ? { leadingOption: leader.option } : {},
|
|
399
|
+
leadingCount,
|
|
400
|
+
approverCount,
|
|
401
|
+
selectedCount,
|
|
402
|
+
unattributedApprovals: approverCount - selectedCount,
|
|
403
|
+
leadingShare: approverCount > 0 ? leadingCount / approverCount : 0
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function evaluateOptionThreshold(tally, threshold) {
|
|
407
|
+
const approved = clears(tally, threshold);
|
|
408
|
+
return { ...tally, threshold, approved };
|
|
409
|
+
}
|
|
410
|
+
function clears(tally, threshold) {
|
|
411
|
+
if (tally.approverCount === 0 || tally.leadingCount === 0) return false;
|
|
412
|
+
switch (threshold) {
|
|
413
|
+
case "unanimous":
|
|
414
|
+
return tally.leadingCount === tally.approverCount;
|
|
415
|
+
case "supermajority":
|
|
416
|
+
return tally.leadingShare >= SUPERMAJORITY_SHARE;
|
|
417
|
+
case "majority":
|
|
418
|
+
return tally.leadingShare > MAJORITY_SHARE;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/cli/voter-response.ts
|
|
372
423
|
var SyntheticVoteError = class extends Error {
|
|
373
424
|
constructor(reason, rawOutput) {
|
|
374
425
|
super(`Vote response parsing failed: ${reason}`);
|
|
@@ -408,7 +459,11 @@ var VoteResponseSchema = z.object({
|
|
|
408
459
|
).optional().describe("Rejection reason categories when decision is reject"),
|
|
409
460
|
/** Top-level structured findings for PR-review mode (#2245 v4 follow-up).
|
|
410
461
|
* Replaces the YAML-in-reasoning encoding that proved lossy. */
|
|
411
|
-
findings: z.array(RawFindingSchema).optional().describe("Structured findings (PR review only)")
|
|
462
|
+
findings: z.array(RawFindingSchema).optional().describe("Structured findings (PR review only)"),
|
|
463
|
+
/** Chosen option when the proposal declared `options` (#4472). Validated
|
|
464
|
+
* against the declared list by the caller; an unmatched value is dropped
|
|
465
|
+
* rather than recorded, so a parse miss surfaces as absent. */
|
|
466
|
+
selectedOption: z.string().optional().describe("Which declared option you choose")
|
|
412
467
|
});
|
|
413
468
|
var VOTE_JSON_SCHEMA = {
|
|
414
469
|
type: "object",
|
|
@@ -437,6 +492,12 @@ var VOTE_JSON_SCHEMA = {
|
|
|
437
492
|
items: { type: "string" },
|
|
438
493
|
description: "Optional conditions for approval"
|
|
439
494
|
},
|
|
495
|
+
// #4472: without this, `additionalProperties: false` makes it impossible
|
|
496
|
+
// for a structured-output voter to emit a selection at all.
|
|
497
|
+
selectedOption: {
|
|
498
|
+
type: "string",
|
|
499
|
+
description: "Which declared option you choose (multi-option proposals only)"
|
|
500
|
+
},
|
|
440
501
|
rejectionCategories: {
|
|
441
502
|
type: "array",
|
|
442
503
|
items: {
|
|
@@ -544,12 +605,12 @@ Example PR-review request_changes response with structured findings:
|
|
|
544
605
|
}
|
|
545
606
|
]
|
|
546
607
|
}`;
|
|
547
|
-
function buildVotePrompt(proposal) {
|
|
608
|
+
function buildVotePrompt(proposal, options) {
|
|
548
609
|
return `Evaluate the following proposal and provide your vote.
|
|
549
610
|
|
|
550
611
|
PROPOSAL:
|
|
551
612
|
${proposal}
|
|
552
|
-
|
|
613
|
+
${buildOptionsBlock(options)}
|
|
553
614
|
In addition to your role-specific criteria, assess these workflow-test dimensions:
|
|
554
615
|
- Testability: Can the proposed changes be verified with automated tests?
|
|
555
616
|
- Workflow integration: Does this fit into existing CI/make/test workflows?
|
|
@@ -562,9 +623,21 @@ Respond with a JSON object containing:
|
|
|
562
623
|
- conditions: Optional array of conditions for approval
|
|
563
624
|
- rejectionCategories: Required when rejecting. Array of categories from: YAGNI, DRY_VIOLATION, OVER_ENGINEERING, SCOPE_CREEP, SECURITY_RISK, MISALIGNED, INSUFFICIENT_EVIDENCE
|
|
564
625
|
- findings: PR-REVIEW MODE ONLY. Optional top-level array of structured findings \u2014 see "PR-review mode" in the system prompt. OMIT this field entirely when reviewing a non-diff proposal or when approving a diff.
|
|
565
|
-
|
|
626
|
+
${buildSelectionFieldDoc(options)}
|
|
566
627
|
${VOTE_PROMPT_EXAMPLES}`;
|
|
567
628
|
}
|
|
629
|
+
function buildOptionsBlock(options) {
|
|
630
|
+
if (options === void 0 || options.length === 0) return "";
|
|
631
|
+
const list = options.map((o) => `- ${o}`).join("\n");
|
|
632
|
+
return `
|
|
633
|
+
OPTIONS \u2014 choose exactly ONE of these and name it verbatim in \`selectedOption\`:
|
|
634
|
+
${list}
|
|
635
|
+
`;
|
|
636
|
+
}
|
|
637
|
+
function buildSelectionFieldDoc(options) {
|
|
638
|
+
if (options === void 0 || options.length === 0) return "";
|
|
639
|
+
return "- selectedOption: REQUIRED. Exactly one option from the OPTIONS list above, copied verbatim. Do not invent an option, combine two, or leave it blank \u2014 your selection is what the threshold is measured over.";
|
|
640
|
+
}
|
|
568
641
|
function stepInString(state, ch) {
|
|
569
642
|
if (ch === "\\") state.escaped = true;
|
|
570
643
|
else if (ch === '"') state.inString = false;
|
|
@@ -647,7 +720,8 @@ function clampOversizeVoteStrings(parsed) {
|
|
|
647
720
|
}
|
|
648
721
|
return obj;
|
|
649
722
|
}
|
|
650
|
-
function buildParsedVote(data) {
|
|
723
|
+
function buildParsedVote(data, options) {
|
|
724
|
+
const selectedOption = options === void 0 ? void 0 : matchDeclaredOption(data.selectedOption, options);
|
|
651
725
|
return {
|
|
652
726
|
decision: data.decision,
|
|
653
727
|
reasoning: data.reasoning,
|
|
@@ -655,16 +729,17 @@ function buildParsedVote(data) {
|
|
|
655
729
|
...data.conditions !== void 0 ? { conditions: data.conditions } : {},
|
|
656
730
|
...data.rejectionCategories !== void 0 ? { rejectionCategories: data.rejectionCategories } : {},
|
|
657
731
|
...data.findings !== void 0 ? { findings: data.findings } : {},
|
|
732
|
+
...selectedOption !== void 0 ? { selectedOption } : {},
|
|
658
733
|
source: "parsed"
|
|
659
734
|
};
|
|
660
735
|
}
|
|
661
|
-
function parseVoteResponse(output, _role) {
|
|
736
|
+
function parseVoteResponse(output, _role, options) {
|
|
662
737
|
try {
|
|
663
738
|
const jsonStr = extractJsonFromResponse(output);
|
|
664
739
|
const parsed = JSON.parse(jsonStr);
|
|
665
740
|
const validated = VoteResponseSchema.safeParse(clampOversizeVoteStrings(parsed));
|
|
666
741
|
if (validated.success) {
|
|
667
|
-
return buildParsedVote(validated.data);
|
|
742
|
+
return buildParsedVote(validated.data, options);
|
|
668
743
|
}
|
|
669
744
|
const reason = `Validation failed: ${validated.error.issues.map((e) => e.message).join(", ")}`;
|
|
670
745
|
throw new SyntheticVoteError(reason, output);
|
|
@@ -773,11 +848,11 @@ function extractTextFromResponse(content) {
|
|
|
773
848
|
}
|
|
774
849
|
return String(content);
|
|
775
850
|
}
|
|
776
|
-
function buildVoteRequest(role, proposal, timeoutMs, withResponseFormat) {
|
|
851
|
+
function buildVoteRequest(role, proposal, timeoutMs, withResponseFormat, options) {
|
|
777
852
|
const base = {
|
|
778
853
|
messages: [
|
|
779
854
|
{ role: "system", content: VOTER_SYSTEM_PROMPTS[role] },
|
|
780
|
-
{ role: "user", content: buildVotePrompt(proposal) }
|
|
855
|
+
{ role: "user", content: buildVotePrompt(proposal, options) }
|
|
781
856
|
],
|
|
782
857
|
// 4000 (#4131): headroom so a findings-bearing verdict (JSON envelope +
|
|
783
858
|
// reasoning + structured findings) isn't cut mid-JSON by the token cap and
|
|
@@ -800,8 +875,15 @@ function isStructuredOutputUnsupported(errorMessage) {
|
|
|
800
875
|
function readTokenCount(value) {
|
|
801
876
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
802
877
|
}
|
|
803
|
-
async function runVoteCompletion(
|
|
804
|
-
|
|
878
|
+
async function runVoteCompletion({
|
|
879
|
+
role,
|
|
880
|
+
proposal,
|
|
881
|
+
adapter,
|
|
882
|
+
timeoutMs,
|
|
883
|
+
withResponseFormat,
|
|
884
|
+
options
|
|
885
|
+
}) {
|
|
886
|
+
const request = buildVoteRequest(role, proposal, timeoutMs, withResponseFormat, options);
|
|
805
887
|
const timeoutResult = await withTimeout(
|
|
806
888
|
adapter.complete(request),
|
|
807
889
|
timeoutMs,
|
|
@@ -821,14 +903,15 @@ async function runVoteCompletion(role, proposal, adapter, timeoutMs, withRespons
|
|
|
821
903
|
};
|
|
822
904
|
return { ok: true, output: extractTextFromResponse(response.value.content), usage };
|
|
823
905
|
}
|
|
824
|
-
async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs) {
|
|
825
|
-
|
|
906
|
+
async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, options) {
|
|
907
|
+
const completionArgs = { role, proposal, adapter, timeoutMs, options };
|
|
908
|
+
let completion = await runVoteCompletion({ ...completionArgs, withResponseFormat: true });
|
|
826
909
|
if (!completion.ok && isStructuredOutputUnsupported(completion.error)) {
|
|
827
|
-
completion = await runVoteCompletion(
|
|
910
|
+
completion = await runVoteCompletion({ ...completionArgs, withResponseFormat: false });
|
|
828
911
|
}
|
|
829
912
|
if (!completion.ok) return { ok: false, error: completion.error };
|
|
830
913
|
try {
|
|
831
|
-
const vote = parseVoteResponse(completion.output, role);
|
|
914
|
+
const vote = parseVoteResponse(completion.output, role, options);
|
|
832
915
|
return { ok: true, vote, output: completion.output, usage: completion.usage };
|
|
833
916
|
} catch (error) {
|
|
834
917
|
if (error instanceof SyntheticVoteError) {
|
|
@@ -838,7 +921,7 @@ async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs) {
|
|
|
838
921
|
}
|
|
839
922
|
}
|
|
840
923
|
async function executeWithRetries(opts) {
|
|
841
|
-
const { role, proposal, adapter, logger: logger10, timeoutMs, maxRetries } = opts;
|
|
924
|
+
const { role, proposal, adapter, logger: logger10, timeoutMs, maxRetries, options } = opts;
|
|
842
925
|
let lastError = "";
|
|
843
926
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
844
927
|
if (attempt > 0) {
|
|
@@ -849,7 +932,7 @@ async function executeWithRetries(opts) {
|
|
|
849
932
|
await delay(delayMs);
|
|
850
933
|
}
|
|
851
934
|
const attemptStart = Date.now();
|
|
852
|
-
const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs);
|
|
935
|
+
const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, options);
|
|
853
936
|
const attemptMs = Date.now() - attemptStart;
|
|
854
937
|
if (result.ok) {
|
|
855
938
|
logger10.info("Vote attempt timing", {
|
|
@@ -982,17 +1065,27 @@ function buildLlmVoteResult(role, vote, usage, adapter, processingTimeMs) {
|
|
|
982
1065
|
source: "llm",
|
|
983
1066
|
cli: adapter.providerId,
|
|
984
1067
|
model: adapter.modelId,
|
|
1068
|
+
// #4472: surface the voter's choice at the result level, where the tally
|
|
1069
|
+
// and the record read it. Absent when no options were declared or the
|
|
1070
|
+
// selection matched none of them.
|
|
1071
|
+
...vote.selectedOption !== void 0 ? { selectedOption: vote.selectedOption } : {},
|
|
985
1072
|
...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
986
1073
|
...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
987
1074
|
...usage.cachedInputTokens !== void 0 ? { cachedInputTokens: usage.cachedInputTokens } : {},
|
|
988
1075
|
...usage.cacheCreationInputTokens !== void 0 ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {}
|
|
989
1076
|
};
|
|
990
1077
|
}
|
|
1078
|
+
function resolveVoteExecution(options) {
|
|
1079
|
+
return {
|
|
1080
|
+
timeoutMs: options?.timeoutMs ?? resolveVoteTimeout(),
|
|
1081
|
+
maxRetries: options?.maxRetries ?? VOTE_TIMEOUTS.maxRetries,
|
|
1082
|
+
allowSimulation: options?.allowSimulation ?? false,
|
|
1083
|
+
declaredOptions: options?.declaredOptions
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
991
1086
|
async function executeAgentVote(role, proposal, adapter, logger10, options) {
|
|
992
1087
|
const start = getTimeProvider().now();
|
|
993
|
-
const timeoutMs
|
|
994
|
-
const maxRetries = options?.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
995
|
-
const allowSimulation = options?.allowSimulation ?? false;
|
|
1088
|
+
const { timeoutMs, maxRetries, allowSimulation, declaredOptions } = resolveVoteExecution(options);
|
|
996
1089
|
logger10.info("Executing vote", { role, model: adapter.modelId, provider: adapter.providerId });
|
|
997
1090
|
const result = await executeWithRetries({
|
|
998
1091
|
role,
|
|
@@ -1000,7 +1093,8 @@ async function executeAgentVote(role, proposal, adapter, logger10, options) {
|
|
|
1000
1093
|
adapter,
|
|
1001
1094
|
logger: logger10,
|
|
1002
1095
|
timeoutMs,
|
|
1003
|
-
maxRetries
|
|
1096
|
+
maxRetries,
|
|
1097
|
+
options: declaredOptions
|
|
1004
1098
|
});
|
|
1005
1099
|
const processingTimeMs = getTimeProvider().now() - start;
|
|
1006
1100
|
if (result.ok) {
|
|
@@ -1191,7 +1285,14 @@ async function collectRealVotes(options) {
|
|
|
1191
1285
|
}
|
|
1192
1286
|
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles, logger10, adapterResult.adapter, options.gatewayAdapters);
|
|
1193
1287
|
warnIfCodexConcurrencyExceeded(roleAdapters, logger10);
|
|
1194
|
-
const voteOptions = {
|
|
1288
|
+
const voteOptions = {
|
|
1289
|
+
timeoutMs,
|
|
1290
|
+
maxRetries,
|
|
1291
|
+
allowSimulation: allowSimulation ?? false,
|
|
1292
|
+
// #4472: reaches executeAgentVote, which puts the declared options in the
|
|
1293
|
+
// prompt and resolves each voter's selection against them.
|
|
1294
|
+
declaredOptions: options.declaredOptions
|
|
1295
|
+
};
|
|
1195
1296
|
const interDelay = options.interAgentDelayMs ?? DEFAULT_INTER_AGENT_DELAY_MS;
|
|
1196
1297
|
return launchStaggeredVotes({
|
|
1197
1298
|
roles,
|
|
@@ -1204,6 +1305,43 @@ async function collectRealVotes(options) {
|
|
|
1204
1305
|
});
|
|
1205
1306
|
}
|
|
1206
1307
|
|
|
1308
|
+
// src/mcp/tools/consensus-vote-option-gate.ts
|
|
1309
|
+
var STRATEGY_TO_OPTION_THRESHOLD = {
|
|
1310
|
+
unanimous: "unanimous",
|
|
1311
|
+
supermajority: "supermajority",
|
|
1312
|
+
simple_majority: "majority",
|
|
1313
|
+
// higher_order and proof_of_learning aggregate differently; they are gated at
|
|
1314
|
+
// the majority bar so a declared-option split still cannot pass unnoticed.
|
|
1315
|
+
higher_order: "majority",
|
|
1316
|
+
proof_of_learning: "majority",
|
|
1317
|
+
opinion_wise: "majority"
|
|
1318
|
+
};
|
|
1319
|
+
function optionThresholdFor(strategy, legacyThreshold) {
|
|
1320
|
+
if (legacyThreshold === "unanimous") return "unanimous";
|
|
1321
|
+
if (legacyThreshold === "supermajority") return "supermajority";
|
|
1322
|
+
return STRATEGY_TO_OPTION_THRESHOLD[strategy] ?? "majority";
|
|
1323
|
+
}
|
|
1324
|
+
function evaluateOptionGate(votes, declaredOptions, threshold, approvedByStrategy) {
|
|
1325
|
+
const tally = tallyOptions(
|
|
1326
|
+
votes.map((v) => ({
|
|
1327
|
+
decision: v.vote.decision,
|
|
1328
|
+
...v.selectedOption !== void 0 ? { selectedOption: v.selectedOption } : {}
|
|
1329
|
+
})),
|
|
1330
|
+
declaredOptions
|
|
1331
|
+
);
|
|
1332
|
+
const verdict = evaluateOptionThreshold(tally, threshold);
|
|
1333
|
+
if (!approvedByStrategy || verdict.approved) {
|
|
1334
|
+
return { verdict, vetoed: false };
|
|
1335
|
+
}
|
|
1336
|
+
return { verdict, vetoed: true, reason: describeVeto(verdict) };
|
|
1337
|
+
}
|
|
1338
|
+
function describeVeto(verdict) {
|
|
1339
|
+
const pct = (verdict.leadingShare * 100).toFixed(1);
|
|
1340
|
+
const leader = verdict.leadingOption !== void 0 ? `leading option "${verdict.leadingOption}" held ${String(verdict.leadingCount)}/${String(verdict.approverCount)} approvers (${pct}%)` : "no voter selected a declared option";
|
|
1341
|
+
const coverage = verdict.unattributedApprovals > 0 ? ` ${String(verdict.unattributedApprovals)} of ${String(verdict.approverCount)} approvers recorded no usable selection, which lowers the leading share; their choice is unmeasured, not counted as dissent.` : "";
|
|
1342
|
+
return `Approvals cleared the ${verdict.threshold} bar, but the option split did not: ${leader}.${coverage}`;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1207
1345
|
// src/consensus/types-voting-protocol.ts
|
|
1208
1346
|
import { z as z2 } from "zod";
|
|
1209
1347
|
var VOTING_ROUND_DEFAULT_TIMEOUT_MS = OPERATION_CLASSES["multi-llm-panel"].guardMs;
|
|
@@ -4322,7 +4460,10 @@ function getDefaultErrorPolicy(strategy) {
|
|
|
4322
4460
|
}
|
|
4323
4461
|
var ConsensusVoteInputSchema = z6.object({
|
|
4324
4462
|
proposal: z6.string().min(1).max(MAX_PROPOSAL_LENGTH).describe(
|
|
4325
|
-
"Proposal text to vote on.
|
|
4463
|
+
"Proposal text to vote on. If the proposal asks voters to choose among named alternatives, you MUST declare them in `options` (#4472). Without `options` the tally records approve/reject/abstain only, so every voter who engages returns `approve` and a 6-1 split on WHICH option persists as 7-0, 100% (#4452)."
|
|
4464
|
+
),
|
|
4465
|
+
options: z6.array(z6.string().min(1).max(200)).min(2).max(10).optional().describe(
|
|
4466
|
+
"Named alternatives for a multi-option proposal (#4472). When present, the threshold must ALSO be cleared by the leading option, in addition to the ordinary approve/reject bar: `unanimous` requires every approver to have chosen the SAME option, and `supermajority`/`majority` measure the leading option's share of approvers. An approving voter whose selection is absent or matches no declared option stays in the denominator and credits no option, so a degraded response can only lower the leading share, never raise it. Omit for an ordinary yes/no vote \u2014 behaviour is then unchanged."
|
|
4326
4467
|
),
|
|
4327
4468
|
threshold: VoteThresholdSchema.optional().describe(
|
|
4328
4469
|
"Voting threshold (legacy): majority, supermajority, unanimous. Use strategy instead."
|
|
@@ -4500,6 +4641,18 @@ function buildResponse(input, result, costSummary, voteRecord) {
|
|
|
4500
4641
|
if (voteRecord !== void 0 && !voteRecord.persisted) {
|
|
4501
4642
|
response.voteRecordNote = voteRecord.detail;
|
|
4502
4643
|
}
|
|
4644
|
+
if (result.optionGate !== void 0) {
|
|
4645
|
+
const g = result.optionGate;
|
|
4646
|
+
response.optionOutcome = {
|
|
4647
|
+
tally: g.tally.map((t) => ({ option: t.option, count: t.count })),
|
|
4648
|
+
...g.leadingOption !== void 0 ? { leadingOption: g.leadingOption } : {},
|
|
4649
|
+
leadingShare: g.leadingShare,
|
|
4650
|
+
approverCount: g.approverCount,
|
|
4651
|
+
selectedCount: g.selectedCount,
|
|
4652
|
+
unattributedApprovals: g.unattributedApprovals,
|
|
4653
|
+
thresholdMet: g.approved
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4503
4656
|
applyOptionalResponseFields(response, input, result, errorCount, costSummary);
|
|
4504
4657
|
applyAbsoluteQuorumTelemetry(response, input, decision, degradeReason);
|
|
4505
4658
|
return response;
|
|
@@ -4580,6 +4733,14 @@ import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
|
4580
4733
|
import * as crypto from "crypto";
|
|
4581
4734
|
import { z as z7 } from "zod";
|
|
4582
4735
|
var VoteRecordDecisionSchema = z7.enum(["approved", "rejected", "no_quorum"]);
|
|
4736
|
+
var VoteRecordOptionCoverageSchema = z7.object({
|
|
4737
|
+
/** Every approving voter, selecting or not — the tally's denominator. */
|
|
4738
|
+
approverCount: z7.number().int().nonnegative(),
|
|
4739
|
+
/** Approvers whose selection matched a declared option. */
|
|
4740
|
+
selectedCount: z7.number().int().nonnegative(),
|
|
4741
|
+
/** Approvers with no usable selection; their choice is unmeasured. */
|
|
4742
|
+
unattributedApprovals: z7.number().int().nonnegative()
|
|
4743
|
+
}).strict();
|
|
4583
4744
|
var VoterSummarySchema = z7.object({
|
|
4584
4745
|
role: z7.string().min(1).max(100),
|
|
4585
4746
|
decision: z7.enum(["approve", "reject", "abstain"]),
|
|
@@ -4605,7 +4766,7 @@ var VoteRecordSchema = z7.object({
|
|
|
4605
4766
|
* `ratifies` is folded into the self-hash ONLY when present (see
|
|
4606
4767
|
* {@link computeVoteRecordHash}).
|
|
4607
4768
|
*/
|
|
4608
|
-
version: z7.enum(["1.1", "1.2", "1.3"]),
|
|
4769
|
+
version: z7.enum(["1.1", "1.2", "1.3", "1.4"]),
|
|
4609
4770
|
/** Unique record id (also usable as a `ratificationVoteRef`). */
|
|
4610
4771
|
id: z7.string().min(1),
|
|
4611
4772
|
/**
|
|
@@ -4659,6 +4820,16 @@ var VoteRecordSchema = z7.object({
|
|
|
4659
4820
|
* choosing different things.
|
|
4660
4821
|
*/
|
|
4661
4822
|
optionTally: z7.array(VoteRecordOptionCountSchema).min(1).optional(),
|
|
4823
|
+
/**
|
|
4824
|
+
* Selection coverage for a multi-option vote (#4472, schema 1.4).
|
|
4825
|
+
*
|
|
4826
|
+
* `optionTally` alone cannot distinguish dissent from absence: `4 pick X
|
|
4827
|
+
* + 3 unreadable` and a genuine 4/3 split both leave a leading share of
|
|
4828
|
+
* 57%. Recording how many approvers produced no usable selection is what
|
|
4829
|
+
* makes a partial measurement legible AS partial — the condition every
|
|
4830
|
+
* voter attached to adopting the "credit no option" rule.
|
|
4831
|
+
*/
|
|
4832
|
+
optionCoverage: VoteRecordOptionCoverageSchema.optional(),
|
|
4662
4833
|
/**
|
|
4663
4834
|
* The loop/strategy subject this vote RATIFIES (#3927 item 1). Present only on
|
|
4664
4835
|
* a ratification vote; set at vote time and bound into the self-hash (so it is
|
|
@@ -4708,8 +4879,16 @@ function computeVoteRecordHash(payload) {
|
|
|
4708
4879
|
...base,
|
|
4709
4880
|
optionTally: payload.optionTally.map((o) => ({ option: o.option, count: o.count }))
|
|
4710
4881
|
} : base;
|
|
4882
|
+
const withCoverage = payload.optionCoverage !== void 0 ? {
|
|
4883
|
+
...withTally,
|
|
4884
|
+
optionCoverage: {
|
|
4885
|
+
approverCount: payload.optionCoverage.approverCount,
|
|
4886
|
+
selectedCount: payload.optionCoverage.selectedCount,
|
|
4887
|
+
unattributedApprovals: payload.optionCoverage.unattributedApprovals
|
|
4888
|
+
}
|
|
4889
|
+
} : withTally;
|
|
4711
4890
|
const canonical = JSON.stringify(
|
|
4712
|
-
payload.ratifies !== void 0 ? { ...
|
|
4891
|
+
payload.ratifies !== void 0 ? { ...withCoverage, ratifies: payload.ratifies } : withCoverage
|
|
4713
4892
|
);
|
|
4714
4893
|
return crypto.createHash("sha256").update(canonical).digest("hex");
|
|
4715
4894
|
}
|
|
@@ -4741,6 +4920,26 @@ function toVoterSummaries(votes) {
|
|
|
4741
4920
|
}
|
|
4742
4921
|
return summaries;
|
|
4743
4922
|
}
|
|
4923
|
+
function deriveOptionFields(votes) {
|
|
4924
|
+
const optionTally = tallySelectedOptions(votes);
|
|
4925
|
+
return {
|
|
4926
|
+
optionTally,
|
|
4927
|
+
optionCoverage: optionTally === void 0 ? void 0 : coverageOf(votes)
|
|
4928
|
+
};
|
|
4929
|
+
}
|
|
4930
|
+
function recordVersion(optionTally, optionCoverage) {
|
|
4931
|
+
if (optionCoverage !== void 0) return "1.4";
|
|
4932
|
+
return optionTally !== void 0 ? "1.3" : "1.2";
|
|
4933
|
+
}
|
|
4934
|
+
function coverageOf(votes) {
|
|
4935
|
+
const approvers = votes.filter((v) => v.vote.decision === "approve");
|
|
4936
|
+
const selectedCount = approvers.filter((v) => v.selectedOption !== void 0).length;
|
|
4937
|
+
return {
|
|
4938
|
+
approverCount: approvers.length,
|
|
4939
|
+
selectedCount,
|
|
4940
|
+
unattributedApprovals: approvers.length - selectedCount
|
|
4941
|
+
};
|
|
4942
|
+
}
|
|
4744
4943
|
function tallySelectedOptions(votes) {
|
|
4745
4944
|
const counts = /* @__PURE__ */ new Map();
|
|
4746
4945
|
for (const v of votes) {
|
|
@@ -4752,9 +4951,9 @@ function tallySelectedOptions(votes) {
|
|
|
4752
4951
|
}
|
|
4753
4952
|
function buildVoteRecord(input) {
|
|
4754
4953
|
const proposalTruncated = input.proposal.length > MAX_PROPOSAL_RECORD_CHARS ? input.proposal.slice(0, MAX_PROPOSAL_RECORD_CHARS) + "..." : input.proposal;
|
|
4755
|
-
const optionTally =
|
|
4954
|
+
const { optionTally, optionCoverage } = deriveOptionFields(input.votes);
|
|
4756
4955
|
const payload = {
|
|
4757
|
-
version: optionTally
|
|
4956
|
+
version: recordVersion(optionTally, optionCoverage),
|
|
4758
4957
|
id: input.id,
|
|
4759
4958
|
sequence: input.sequence ?? 0,
|
|
4760
4959
|
recordedAt: input.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4773,6 +4972,7 @@ function buildVoteRecord(input) {
|
|
|
4773
4972
|
...input.correlationId !== void 0 ? { correlationId: input.correlationId } : {},
|
|
4774
4973
|
...input.ratifies !== void 0 ? { ratifies: input.ratifies } : {},
|
|
4775
4974
|
...optionTally !== void 0 ? { optionTally } : {},
|
|
4975
|
+
...optionCoverage !== void 0 ? { optionCoverage } : {},
|
|
4776
4976
|
...input.previousHash !== void 0 ? { previousHash: input.previousHash } : {}
|
|
4777
4977
|
};
|
|
4778
4978
|
return { ...payload, hash: computeVoteRecordHash(payload) };
|
|
@@ -6060,9 +6260,26 @@ async function maybeEscalateContrarian(input, outcome, ctx, logger10, opts) {
|
|
|
6060
6260
|
async function executeVoting(input, logger10, opts) {
|
|
6061
6261
|
const result = await executeVotingInner(input, logger10, opts);
|
|
6062
6262
|
const errorCount = result.votes.filter((v) => v.source === "error").length;
|
|
6263
|
+
applyOptionGate(input, result);
|
|
6063
6264
|
result.decision = resolveVoteDecision(input, result, errorCount).decision;
|
|
6064
6265
|
return result;
|
|
6065
6266
|
}
|
|
6267
|
+
function applyOptionGate(input, result) {
|
|
6268
|
+
const declaredOptions = input.options;
|
|
6269
|
+
if (declaredOptions === void 0 || declaredOptions.length === 0) return;
|
|
6270
|
+
const outcome = evaluateOptionGate(
|
|
6271
|
+
result.votes,
|
|
6272
|
+
declaredOptions,
|
|
6273
|
+
optionThresholdFor(result.strategy, input.threshold),
|
|
6274
|
+
result.result.outcome === "approved"
|
|
6275
|
+
);
|
|
6276
|
+
result.optionGate = outcome.verdict;
|
|
6277
|
+
if (!outcome.vetoed) return;
|
|
6278
|
+
result.result.outcome = "rejected";
|
|
6279
|
+
if (result.policyReason === void 0 && outcome.reason !== void 0) {
|
|
6280
|
+
result.policyReason = outcome.reason;
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6066
6283
|
async function executeVotingInner(input, logger10, opts) {
|
|
6067
6284
|
const strategy = resolveStrategy(input);
|
|
6068
6285
|
const algorithm = strategyToAlgorithm(strategy);
|
|
@@ -6080,7 +6297,8 @@ async function executeVotingInner(input, logger10, opts) {
|
|
|
6080
6297
|
proposal: input.proposal,
|
|
6081
6298
|
simulate: input.simulateVotes,
|
|
6082
6299
|
...opts?.voteTimeoutMs !== void 0 && { timeoutMs: opts.voteTimeoutMs },
|
|
6083
|
-
...opts?.gatewayAdapters !== void 0 && { gatewayAdapters: opts.gatewayAdapters }
|
|
6300
|
+
...opts?.gatewayAdapters !== void 0 && { gatewayAdapters: opts.gatewayAdapters },
|
|
6301
|
+
declaredOptions: input.options
|
|
6084
6302
|
});
|
|
6085
6303
|
const policyDecision = applyErrorPolicy(votes, errorPolicy);
|
|
6086
6304
|
if (policyDecision.shortCircuit) {
|
|
@@ -6379,7 +6597,19 @@ var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
|
6379
6597
|
// #3855: per-decision cost rollup. Same omission as panelWarning (#4032) — it
|
|
6380
6598
|
// is present on the response whenever cost recording succeeds (common in `api`
|
|
6381
6599
|
// billing mode). Shared schema lives with the type (single source of truth).
|
|
6382
|
-
costSummary: DecisionCostSummarySchema.optional()
|
|
6600
|
+
costSummary: DecisionCostSummarySchema.optional(),
|
|
6601
|
+
// #4472: declared-option outcome. Present whenever the proposal declared
|
|
6602
|
+
// `options`. `unattributedApprovals` is part of the contract, not telemetry:
|
|
6603
|
+
// without it a caller cannot tell a real split from unreadable responses.
|
|
6604
|
+
optionOutcome: z10.object({
|
|
6605
|
+
tally: z10.array(z10.object({ option: z10.string(), count: z10.number().int().nonnegative() })),
|
|
6606
|
+
leadingOption: z10.string().optional(),
|
|
6607
|
+
leadingShare: z10.number().min(0).max(1),
|
|
6608
|
+
approverCount: z10.number().int().nonnegative(),
|
|
6609
|
+
selectedCount: z10.number().int().nonnegative(),
|
|
6610
|
+
unattributedApprovals: z10.number().int().nonnegative(),
|
|
6611
|
+
thresholdMet: z10.boolean()
|
|
6612
|
+
}).optional()
|
|
6383
6613
|
};
|
|
6384
6614
|
var CONSENSUS_VOTE_TOOL_SCHEMA = {
|
|
6385
6615
|
proposal: z10.string().min(1).max(MAX_PROPOSAL_LENGTH).describe("Proposal text to vote on"),
|
|
@@ -6503,4 +6733,4 @@ export {
|
|
|
6503
6733
|
CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
6504
6734
|
registerConsensusVoteTool
|
|
6505
6735
|
};
|
|
6506
|
-
//# sourceMappingURL=chunk-
|
|
6736
|
+
//# sourceMappingURL=chunk-XL6T3HTN.js.map
|