nexus-agents 8.23.2 → 8.23.4
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-KIAMP743.js → chunk-7JD5344Q.js} +12 -5
- package/dist/chunk-7JD5344Q.js.map +1 -0
- package/dist/{chunk-M7TUO6O7.js → chunk-DEVL62JD.js} +686 -209
- package/dist/chunk-DEVL62JD.js.map +1 -0
- package/dist/{chunk-OXUJBY4W.js → chunk-FKCE4X4F.js} +2 -2
- package/dist/{chunk-3CHPU46R.js → chunk-MQW3SXKC.js} +2 -2
- package/dist/{chunk-ZFZJ6LTJ.js → chunk-NUYJ4VW5.js} +2 -2
- package/dist/{chunk-TBOM7PJ7.js → chunk-TJFF2CHD.js} +2117 -2500
- package/dist/chunk-TJFF2CHD.js.map +1 -0
- package/dist/{chunk-TCUEFOPW.js → chunk-WCCU6D5G.js} +3 -3
- package/dist/cli.js +9 -9
- package/dist/{consensus-vote-NQPSFVEF.js → consensus-vote-SSTSB3ET.js} +4 -4
- package/dist/{improvement-review-5HMJ2NCH.js → improvement-review-3QHRGD4C.js} +4 -4
- package/dist/index.d.ts +16 -2
- package/dist/index.js +6 -6
- package/dist/{pr-reviewer-helpers-3VQNHIXI.js → pr-reviewer-helpers-HT3LU55R.js} +2 -2
- package/dist/{setup-data-dir-IJ6CYVCL.js → setup-data-dir-ECSNPFPF.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-KIAMP743.js.map +0 -1
- package/dist/chunk-M7TUO6O7.js.map +0 -1
- package/dist/chunk-TBOM7PJ7.js.map +0 -1
- /package/dist/{chunk-OXUJBY4W.js.map → chunk-FKCE4X4F.js.map} +0 -0
- /package/dist/{chunk-3CHPU46R.js.map → chunk-MQW3SXKC.js.map} +0 -0
- /package/dist/{chunk-ZFZJ6LTJ.js.map → chunk-NUYJ4VW5.js.map} +0 -0
- /package/dist/{chunk-TCUEFOPW.js.map → chunk-WCCU6D5G.js.map} +0 -0
- /package/dist/{consensus-vote-NQPSFVEF.js.map → consensus-vote-SSTSB3ET.js.map} +0 -0
- /package/dist/{improvement-review-5HMJ2NCH.js.map → improvement-review-3QHRGD4C.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-3VQNHIXI.js.map → pr-reviewer-helpers-HT3LU55R.js.map} +0 -0
- /package/dist/{setup-data-dir-IJ6CYVCL.js.map → setup-data-dir-ECSNPFPF.js.map} +0 -0
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
toolSuccessStructured,
|
|
24
24
|
withProgressHeartbeat,
|
|
25
25
|
wrapToolWithTimeout
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-NUYJ4VW5.js";
|
|
27
27
|
import {
|
|
28
28
|
VERSION
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-FKCE4X4F.js";
|
|
30
30
|
import {
|
|
31
31
|
assessPanelIndependence,
|
|
32
32
|
countDistinctModels,
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
} from "./chunk-J7QCZRVK.js";
|
|
82
82
|
|
|
83
83
|
// src/mcp/tools/consensus-vote.ts
|
|
84
|
-
import { z as
|
|
84
|
+
import { z as z11 } from "zod";
|
|
85
85
|
|
|
86
86
|
// src/utils/bounded-artifact.ts
|
|
87
87
|
function boundArtifactForReview(text, budget, label) {
|
|
@@ -99,7 +99,7 @@ function boundArtifactForReview(text, budget, label) {
|
|
|
99
99
|
function voterModelOverrideEnvKey(role) {
|
|
100
100
|
return `NEXUS_VOTER_MODEL_${role.toUpperCase()}`;
|
|
101
101
|
}
|
|
102
|
-
function resolveVoterModelOverrides(roles, gatewayAdapters,
|
|
102
|
+
function resolveVoterModelOverrides(roles, gatewayAdapters, logger11) {
|
|
103
103
|
const overrides = /* @__PURE__ */ new Map();
|
|
104
104
|
if (gatewayAdapters.length === 0) return overrides;
|
|
105
105
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -111,7 +111,7 @@ function resolveVoterModelOverrides(roles, gatewayAdapters, logger10) {
|
|
|
111
111
|
const requested = raw.trim();
|
|
112
112
|
const adapter = byId.get(requested) ?? gatewayAdapters.find((a) => a.modelId.toLowerCase() === requested.toLowerCase());
|
|
113
113
|
if (adapter === void 0) {
|
|
114
|
-
|
|
114
|
+
logger11.warn(
|
|
115
115
|
`Voter model override ${envKey}="${requested}" is not a discovered gateway model \u2014 falling back to round-robin for role "${role}".`,
|
|
116
116
|
{ role, requested, available: [...byId.keys()] }
|
|
117
117
|
);
|
|
@@ -120,7 +120,7 @@ function resolveVoterModelOverrides(roles, gatewayAdapters, logger10) {
|
|
|
120
120
|
overrides.set(role, adapter);
|
|
121
121
|
}
|
|
122
122
|
if (overrides.size > 0) {
|
|
123
|
-
|
|
123
|
+
logger11.info("Applied per-role voter model overrides (#4055)", {
|
|
124
124
|
overrides: Object.fromEntries([...overrides].map(([r, a]) => [r, a.modelId]))
|
|
125
125
|
});
|
|
126
126
|
}
|
|
@@ -146,28 +146,28 @@ function checkCodexConcurrency(codexBoundConcurrency) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// src/cli/panel-independence.ts
|
|
149
|
-
function reportPanelIndependence(adapters, roleCount, phase,
|
|
149
|
+
function reportPanelIndependence(adapters, roleCount, phase, logger11) {
|
|
150
150
|
reportPanelModelIndependence(
|
|
151
151
|
adapters.map((adapter) => adapter.modelId),
|
|
152
152
|
roleCount,
|
|
153
153
|
phase,
|
|
154
|
-
|
|
154
|
+
logger11
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
|
-
function reportVoteIndependence(results,
|
|
157
|
+
function reportVoteIndependence(results, logger11) {
|
|
158
158
|
const successful = results.filter((result) => result.source === "llm");
|
|
159
159
|
reportPanelModelIndependence(
|
|
160
160
|
successful.map((result) => result.model),
|
|
161
161
|
successful.length,
|
|
162
162
|
"post-vote",
|
|
163
|
-
|
|
163
|
+
logger11
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
|
-
function reportPanelModelIndependence(modelIds, roleCount, phase,
|
|
166
|
+
function reportPanelModelIndependence(modelIds, roleCount, phase, logger11) {
|
|
167
167
|
if (roleCount <= 1) return;
|
|
168
168
|
const verdict = assessPanelIndependence(modelIds);
|
|
169
169
|
if (verdict.kind === "collapsed") {
|
|
170
|
-
|
|
170
|
+
logger11.warn("Consensus panel ran on ONE model \u2014 votes may correlate", {
|
|
171
171
|
model: verdict.model,
|
|
172
172
|
roleCount,
|
|
173
173
|
phase
|
|
@@ -175,7 +175,7 @@ function reportPanelModelIndependence(modelIds, roleCount, phase, logger10) {
|
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
177
|
if (verdict.kind === "diverse") {
|
|
178
|
-
|
|
178
|
+
logger11.debug("Consensus panel spans distinct models", {
|
|
179
179
|
distinctModels: verdict.distinct,
|
|
180
180
|
roleCount,
|
|
181
181
|
phase
|
|
@@ -184,13 +184,13 @@ function reportPanelModelIndependence(modelIds, roleCount, phase, logger10) {
|
|
|
184
184
|
}
|
|
185
185
|
const context = { unresolved: verdict.unresolved, total: verdict.total, roleCount };
|
|
186
186
|
if (phase === "post-vote") {
|
|
187
|
-
|
|
187
|
+
logger11.warn(
|
|
188
188
|
"Panel independence UNMEASURED after voting \u2014 cannot say whether votes correlate",
|
|
189
189
|
context
|
|
190
190
|
);
|
|
191
191
|
return;
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
logger11.debug(
|
|
194
194
|
"Panel independence not yet measurable (adapters have not detected their model)",
|
|
195
195
|
context
|
|
196
196
|
);
|
|
@@ -979,29 +979,29 @@ async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, opti
|
|
|
979
979
|
throw error;
|
|
980
980
|
}
|
|
981
981
|
}
|
|
982
|
-
function logAbandonedRetries(
|
|
983
|
-
|
|
982
|
+
function logAbandonedRetries(logger11, role, attempt, maxRetries) {
|
|
983
|
+
logger11.warn("Durable capacity cap \u2014 abandoning retries for this voter", {
|
|
984
984
|
role,
|
|
985
985
|
attempt: attempt + 1,
|
|
986
986
|
remainingAttemptsSkipped: maxRetries - attempt
|
|
987
987
|
});
|
|
988
988
|
}
|
|
989
989
|
async function executeWithRetries(opts) {
|
|
990
|
-
const { role, proposal, adapter, logger:
|
|
990
|
+
const { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries, options } = opts;
|
|
991
991
|
let lastError = "";
|
|
992
992
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
993
993
|
if (attempt > 0) {
|
|
994
994
|
const isRateLimit = isRateLimitError(lastError);
|
|
995
995
|
const baseDelay = isRateLimit ? RATE_LIMIT_RETRY_DELAY_MS : INITIAL_RETRY_DELAY_MS;
|
|
996
996
|
const delayMs = baseDelay * Math.pow(2, attempt - 1);
|
|
997
|
-
|
|
997
|
+
logger11.debug("Retrying vote execution", { role, attempt, delayMs, isRateLimit });
|
|
998
998
|
await delay(delayMs);
|
|
999
999
|
}
|
|
1000
1000
|
const attemptStart = Date.now();
|
|
1001
1001
|
const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, options);
|
|
1002
1002
|
const attemptMs = Date.now() - attemptStart;
|
|
1003
1003
|
if (result.ok) {
|
|
1004
|
-
|
|
1004
|
+
logger11.info("Vote attempt timing", {
|
|
1005
1005
|
role,
|
|
1006
1006
|
attempt: attempt + 1,
|
|
1007
1007
|
attemptMs,
|
|
@@ -1012,14 +1012,14 @@ async function executeWithRetries(opts) {
|
|
|
1012
1012
|
lastError = result.error;
|
|
1013
1013
|
const rateLimited = isRateLimitError(lastError);
|
|
1014
1014
|
const durableCap = isDurableCapacityText(lastError);
|
|
1015
|
-
|
|
1015
|
+
logger11.info("Vote attempt timing", {
|
|
1016
1016
|
role,
|
|
1017
1017
|
attempt: attempt + 1,
|
|
1018
1018
|
attemptMs,
|
|
1019
1019
|
succeeded: false,
|
|
1020
1020
|
rateLimited
|
|
1021
1021
|
});
|
|
1022
|
-
|
|
1022
|
+
logger11.warn("Vote attempt failed", {
|
|
1023
1023
|
role,
|
|
1024
1024
|
attempt: attempt + 1,
|
|
1025
1025
|
maxRetries: maxRetries + 1,
|
|
@@ -1028,7 +1028,7 @@ async function executeWithRetries(opts) {
|
|
|
1028
1028
|
...durableCap ? { durableCap: true } : {}
|
|
1029
1029
|
});
|
|
1030
1030
|
if (durableCap) {
|
|
1031
|
-
logAbandonedRetries(
|
|
1031
|
+
logAbandonedRetries(logger11, role, attempt, maxRetries);
|
|
1032
1032
|
break;
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
@@ -1064,9 +1064,9 @@ function createKeyedSerializer() {
|
|
|
1064
1064
|
}
|
|
1065
1065
|
function raceWithDeadline(p, role, deadlineMs) {
|
|
1066
1066
|
let timer;
|
|
1067
|
-
const timeoutP = new Promise((
|
|
1067
|
+
const timeoutP = new Promise((resolve3) => {
|
|
1068
1068
|
timer = setTimeout(() => {
|
|
1069
|
-
|
|
1069
|
+
resolve3(createErrorVoteResult(role, DEADLINE_MESSAGE, deadlineMs));
|
|
1070
1070
|
}, deadlineMs);
|
|
1071
1071
|
});
|
|
1072
1072
|
return Promise.race([p, timeoutP]).finally(() => {
|
|
@@ -1097,7 +1097,7 @@ async function launchRoleVote(role, index, input, voteOnAdapter) {
|
|
|
1097
1097
|
return withPinnedModel(await voteOnAdapter(role, input.fallbackAdapter), pinnedModel);
|
|
1098
1098
|
}
|
|
1099
1099
|
async function launchVotesWithOverallDeadline(input) {
|
|
1100
|
-
const { roles, proposal, logger:
|
|
1100
|
+
const { roles, proposal, logger: logger11, voteOptions, overallDeadlineMs, voteFn } = input;
|
|
1101
1101
|
const startedAt = Date.now();
|
|
1102
1102
|
const serialize = createKeyedSerializer();
|
|
1103
1103
|
const voteOnAdapter = (role, adapter) => (
|
|
@@ -1107,7 +1107,7 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
1107
1107
|
serialize(adapterCliKey(adapter), () => {
|
|
1108
1108
|
const remaining = Math.max(1, overallDeadlineMs - (Date.now() - startedAt));
|
|
1109
1109
|
return raceWithDeadline(
|
|
1110
|
-
voteFn(role, proposal, adapter,
|
|
1110
|
+
voteFn(role, proposal, adapter, logger11, voteOptions),
|
|
1111
1111
|
role,
|
|
1112
1112
|
remaining
|
|
1113
1113
|
);
|
|
@@ -1117,7 +1117,7 @@ async function launchVotesWithOverallDeadline(input) {
|
|
|
1117
1117
|
const results = await Promise.all(wrapped);
|
|
1118
1118
|
const expired = results.filter((r) => r.source === "error" && r.error === DEADLINE_MESSAGE);
|
|
1119
1119
|
if (expired.length > 0) {
|
|
1120
|
-
|
|
1120
|
+
logger11.warn("Consensus overall deadline reached; returning partial results", {
|
|
1121
1121
|
totalRoles: roles.length,
|
|
1122
1122
|
expiredRoles: expired.map((r) => r.role),
|
|
1123
1123
|
overallDeadlineMs
|
|
@@ -1160,31 +1160,31 @@ function resolveVoteExecution(options) {
|
|
|
1160
1160
|
declaredOptions: options?.declaredOptions
|
|
1161
1161
|
};
|
|
1162
1162
|
}
|
|
1163
|
-
async function executeAgentVote(role, proposal, adapter,
|
|
1163
|
+
async function executeAgentVote(role, proposal, adapter, logger11, options) {
|
|
1164
1164
|
const start = getTimeProvider().now();
|
|
1165
1165
|
const { timeoutMs, maxRetries, allowSimulation, declaredOptions } = resolveVoteExecution(options);
|
|
1166
|
-
|
|
1166
|
+
logger11.info("Executing vote", { role, model: adapter.modelId, provider: adapter.providerId });
|
|
1167
1167
|
const result = await executeWithRetries({
|
|
1168
1168
|
role,
|
|
1169
1169
|
proposal,
|
|
1170
1170
|
adapter,
|
|
1171
|
-
logger:
|
|
1171
|
+
logger: logger11,
|
|
1172
1172
|
timeoutMs,
|
|
1173
1173
|
maxRetries,
|
|
1174
1174
|
options: declaredOptions
|
|
1175
1175
|
});
|
|
1176
1176
|
const processingTimeMs = getTimeProvider().now() - start;
|
|
1177
1177
|
if (result.ok) {
|
|
1178
|
-
|
|
1178
|
+
logger11.info("Vote completed", { role, model: adapter.modelId, decision: result.vote.decision });
|
|
1179
1179
|
return buildLlmVoteResult(role, result.vote, result.usage, adapter, processingTimeMs);
|
|
1180
1180
|
}
|
|
1181
|
-
|
|
1181
|
+
logger11.error("Vote execution failed after all retries", void 0, {
|
|
1182
1182
|
role,
|
|
1183
1183
|
model: adapter.modelId,
|
|
1184
1184
|
errorMessage: result.error
|
|
1185
1185
|
});
|
|
1186
1186
|
if (allowSimulation) {
|
|
1187
|
-
|
|
1187
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)", { role });
|
|
1188
1188
|
return createSimulationVoteResult(role, proposal, processingTimeMs, result.error);
|
|
1189
1189
|
}
|
|
1190
1190
|
const remediation = authRemediation(result.error, adapter.providerId);
|
|
@@ -1199,26 +1199,26 @@ var NoAdapterError = class extends Error {
|
|
|
1199
1199
|
this.name = "NoAdapterError";
|
|
1200
1200
|
}
|
|
1201
1201
|
};
|
|
1202
|
-
function resolveAdapter(options,
|
|
1202
|
+
function resolveAdapter(options, logger11) {
|
|
1203
1203
|
try {
|
|
1204
1204
|
if (options.adapter !== void 0) return { adapter: options.adapter };
|
|
1205
1205
|
const gateway = options.gatewayAdapters;
|
|
1206
1206
|
if (gateway !== void 0 && gateway.length > 0 && gateway[0] !== void 0) {
|
|
1207
1207
|
return { adapter: gateway[0] };
|
|
1208
1208
|
}
|
|
1209
|
-
const registry = getGlobalRegistry({ logger:
|
|
1209
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
1210
1210
|
return { adapter: registry.getDefault() };
|
|
1211
1211
|
} catch (error) {
|
|
1212
1212
|
return { error: getErrorMessage(error) };
|
|
1213
1213
|
}
|
|
1214
1214
|
}
|
|
1215
|
-
function warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
1215
|
+
function warnIfCodexConcurrencyExceeded(roleAdapters, logger11) {
|
|
1216
1216
|
const codexBound = [...roleAdapters.values()].filter(
|
|
1217
1217
|
(a) => a.name === "codex"
|
|
1218
1218
|
).length;
|
|
1219
1219
|
const warning = checkCodexConcurrency(codexBound);
|
|
1220
1220
|
if (warning !== null) {
|
|
1221
|
-
|
|
1221
|
+
logger11.warn("Codex concurrency limit may be exceeded", { detail: warning });
|
|
1222
1222
|
}
|
|
1223
1223
|
}
|
|
1224
1224
|
function assignUniformAdapter(roles, adapter) {
|
|
@@ -1226,15 +1226,15 @@ function assignUniformAdapter(roles, adapter) {
|
|
|
1226
1226
|
for (const role of roles) adapters.set(role, adapter);
|
|
1227
1227
|
return adapters;
|
|
1228
1228
|
}
|
|
1229
|
-
function createCliAdapterMap(clis,
|
|
1230
|
-
const registry = getGlobalRegistry({ logger:
|
|
1229
|
+
function createCliAdapterMap(clis, logger11) {
|
|
1230
|
+
const registry = getGlobalRegistry({ logger: logger11 });
|
|
1231
1231
|
const result = /* @__PURE__ */ new Map();
|
|
1232
1232
|
for (const cli of clis) {
|
|
1233
1233
|
result.set(cli, registry.getAdapterForCli(cli));
|
|
1234
1234
|
}
|
|
1235
1235
|
return result;
|
|
1236
1236
|
}
|
|
1237
|
-
function assignRoundRobinAdapters(roles, entries,
|
|
1237
|
+
function assignRoundRobinAdapters(roles, entries, logger11, source) {
|
|
1238
1238
|
const adapters = /* @__PURE__ */ new Map();
|
|
1239
1239
|
const assignments = {};
|
|
1240
1240
|
for (let i = 0; i < roles.length; i++) {
|
|
@@ -1244,70 +1244,70 @@ function assignRoundRobinAdapters(roles, entries, logger10, source) {
|
|
|
1244
1244
|
adapters.set(role, entry.adapter);
|
|
1245
1245
|
assignments[role] = entry.label;
|
|
1246
1246
|
}
|
|
1247
|
-
|
|
1247
|
+
logger11.info("Diverse adapters assigned", {
|
|
1248
1248
|
source,
|
|
1249
1249
|
count: entries.length,
|
|
1250
1250
|
roleAssignments: assignments
|
|
1251
1251
|
});
|
|
1252
1252
|
return adapters;
|
|
1253
1253
|
}
|
|
1254
|
-
function resolveGatewayRoleAdapters(roles, gatewayAdapters, fallbackAdapter,
|
|
1254
|
+
function resolveGatewayRoleAdapters(roles, gatewayAdapters, fallbackAdapter, logger11) {
|
|
1255
1255
|
if (gatewayAdapters.length === 1) {
|
|
1256
1256
|
if (roles.length > 1) {
|
|
1257
|
-
|
|
1257
|
+
logger11.warn("Consensus panel collapsed to a single gateway model \u2014 votes may correlate", {
|
|
1258
1258
|
model: gatewayAdapters[0]?.modelId,
|
|
1259
1259
|
roleCount: roles.length
|
|
1260
1260
|
});
|
|
1261
1261
|
}
|
|
1262
1262
|
return assignUniformAdapter(roles, gatewayAdapters[0] ?? fallbackAdapter);
|
|
1263
1263
|
}
|
|
1264
|
-
const overrides = resolveVoterModelOverrides(roles, gatewayAdapters,
|
|
1264
|
+
const overrides = resolveVoterModelOverrides(roles, gatewayAdapters, logger11);
|
|
1265
1265
|
const roundRobinRoles = roles.filter((r) => !overrides.has(r));
|
|
1266
1266
|
const assigned = assignRoundRobinAdapters(
|
|
1267
1267
|
roundRobinRoles,
|
|
1268
1268
|
gatewayAdapters.map((a) => ({ label: a.modelId, adapter: a })),
|
|
1269
|
-
|
|
1269
|
+
logger11,
|
|
1270
1270
|
"gateway"
|
|
1271
1271
|
);
|
|
1272
1272
|
for (const [role, adapter] of overrides) assigned.set(role, adapter);
|
|
1273
1273
|
const assignedModels = [...assigned.values()].map((a) => a.modelId);
|
|
1274
1274
|
if (roles.length > 1 && countDistinctModels(assignedModels) === 1) {
|
|
1275
|
-
|
|
1275
|
+
logger11.warn(
|
|
1276
1276
|
"Consensus panel collapsed to a single gateway model via overrides \u2014 votes may correlate",
|
|
1277
1277
|
{ model: assignedModels[0], roleCount: roles.length }
|
|
1278
1278
|
);
|
|
1279
1279
|
}
|
|
1280
1280
|
return assigned;
|
|
1281
1281
|
}
|
|
1282
|
-
async function resolveDiverseAdapters(roles,
|
|
1282
|
+
async function resolveDiverseAdapters(roles, logger11, fallbackAdapter, gatewayAdapters) {
|
|
1283
1283
|
if (gatewayAdapters !== void 0 && gatewayAdapters.length > 0) {
|
|
1284
|
-
return resolveGatewayRoleAdapters(roles, gatewayAdapters, fallbackAdapter,
|
|
1284
|
+
return resolveGatewayRoleAdapters(roles, gatewayAdapters, fallbackAdapter, logger11);
|
|
1285
1285
|
}
|
|
1286
1286
|
let availableClis;
|
|
1287
1287
|
try {
|
|
1288
1288
|
availableClis = await getAvailableClis();
|
|
1289
1289
|
} catch (e) {
|
|
1290
|
-
|
|
1290
|
+
logger11.warn("Failed to resolve available CLIs; falling back to single adapter", {
|
|
1291
1291
|
error: String(e)
|
|
1292
1292
|
});
|
|
1293
1293
|
availableClis = [];
|
|
1294
1294
|
}
|
|
1295
1295
|
if (availableClis.length <= 1) {
|
|
1296
|
-
|
|
1296
|
+
logger11.info("Using single adapter for all roles", { cliCount: availableClis.length });
|
|
1297
1297
|
return assignUniformAdapter(roles, fallbackAdapter);
|
|
1298
1298
|
}
|
|
1299
|
-
const cliAdapters = createCliAdapterMap(availableClis,
|
|
1299
|
+
const cliAdapters = createCliAdapterMap(availableClis, logger11);
|
|
1300
1300
|
if (cliAdapters.size <= 1) return assignUniformAdapter(roles, fallbackAdapter);
|
|
1301
|
-
reportPanelIndependence([...cliAdapters.values()], roles.length, "assignment",
|
|
1301
|
+
reportPanelIndependence([...cliAdapters.values()], roles.length, "assignment", logger11);
|
|
1302
1302
|
return assignRoundRobinAdapters(
|
|
1303
1303
|
roles,
|
|
1304
1304
|
[...cliAdapters.entries()].map(([cli, adapter]) => ({ label: cli, adapter })),
|
|
1305
|
-
|
|
1305
|
+
logger11,
|
|
1306
1306
|
"cli"
|
|
1307
1307
|
);
|
|
1308
1308
|
}
|
|
1309
1309
|
async function launchStaggeredVotes(input) {
|
|
1310
|
-
const { roles, proposal, roleAdapters, fallbackAdapter, logger:
|
|
1310
|
+
const { roles, proposal, roleAdapters, fallbackAdapter, logger: logger11, voteOptions, interDelay } = input;
|
|
1311
1311
|
const computedDeadlineMs = computeOverallConsensusDeadlineMs(
|
|
1312
1312
|
voteOptions.timeoutMs,
|
|
1313
1313
|
voteOptions.maxRetries,
|
|
@@ -1316,7 +1316,7 @@ async function launchStaggeredVotes(input) {
|
|
|
1316
1316
|
);
|
|
1317
1317
|
const overallDeadlineMs = getMcpSafeDeadlineMs(computedDeadlineMs, "consensus_vote");
|
|
1318
1318
|
if (overallDeadlineMs < computedDeadlineMs) {
|
|
1319
|
-
|
|
1319
|
+
logger11.debug("Consensus deadline clamped to MCP wrapper timeout", {
|
|
1320
1320
|
computedDeadlineMs,
|
|
1321
1321
|
overallDeadlineMs
|
|
1322
1322
|
});
|
|
@@ -1326,7 +1326,7 @@ async function launchStaggeredVotes(input) {
|
|
|
1326
1326
|
proposal,
|
|
1327
1327
|
roleAdapters,
|
|
1328
1328
|
fallbackAdapter,
|
|
1329
|
-
logger:
|
|
1329
|
+
logger: logger11,
|
|
1330
1330
|
voteOptions,
|
|
1331
1331
|
interDelay,
|
|
1332
1332
|
overallDeadlineMs,
|
|
@@ -1335,27 +1335,27 @@ async function launchStaggeredVotes(input) {
|
|
|
1335
1335
|
});
|
|
1336
1336
|
}
|
|
1337
1337
|
async function collectRealVotes(options) {
|
|
1338
|
-
const
|
|
1338
|
+
const logger11 = options.logger ?? defaultLogger;
|
|
1339
1339
|
const { roles, proposal, simulate, allowSimulation } = options;
|
|
1340
1340
|
const timeoutMs = options.timeoutMs ?? resolveVoteTimeout();
|
|
1341
1341
|
const maxRetries = options.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
|
|
1342
1342
|
if (simulate === true) {
|
|
1343
|
-
|
|
1343
|
+
logger11.info("Using simulation mode (explicitly requested)");
|
|
1344
1344
|
return createSimulatedVotes(roles, proposal);
|
|
1345
1345
|
}
|
|
1346
|
-
const adapterResult = resolveAdapter(options,
|
|
1346
|
+
const adapterResult = resolveAdapter(options, logger11);
|
|
1347
1347
|
if ("error" in adapterResult) {
|
|
1348
|
-
|
|
1348
|
+
logger11.error("No adapter available for voting", void 0, { error: adapterResult.error });
|
|
1349
1349
|
if (allowSimulation === true) {
|
|
1350
|
-
|
|
1350
|
+
logger11.warn("Falling back to simulation (allowSimulation=true)");
|
|
1351
1351
|
return createSimulatedVotes(roles, proposal, "No adapter available");
|
|
1352
1352
|
}
|
|
1353
1353
|
throw new NoAdapterError(
|
|
1354
1354
|
`No adapter available for voting: ${adapterResult.error}. Install a CLI (claude/gemini/codex) or set ANTHROPIC_API_KEY.`
|
|
1355
1355
|
);
|
|
1356
1356
|
}
|
|
1357
|
-
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles,
|
|
1358
|
-
warnIfCodexConcurrencyExceeded(roleAdapters,
|
|
1357
|
+
const roleAdapters = options.adapter !== void 0 ? assignUniformAdapter(roles, adapterResult.adapter) : await resolveDiverseAdapters(roles, logger11, adapterResult.adapter, options.gatewayAdapters);
|
|
1358
|
+
warnIfCodexConcurrencyExceeded(roleAdapters, logger11);
|
|
1359
1359
|
const voteOptions = {
|
|
1360
1360
|
timeoutMs,
|
|
1361
1361
|
maxRetries,
|
|
@@ -1370,12 +1370,12 @@ async function collectRealVotes(options) {
|
|
|
1370
1370
|
proposal,
|
|
1371
1371
|
roleAdapters,
|
|
1372
1372
|
fallbackAdapter: adapterResult.adapter,
|
|
1373
|
-
logger:
|
|
1373
|
+
logger: logger11,
|
|
1374
1374
|
voteOptions,
|
|
1375
1375
|
interDelay,
|
|
1376
1376
|
signal: options.signal
|
|
1377
1377
|
});
|
|
1378
|
-
reportVoteIndependence(results,
|
|
1378
|
+
reportVoteIndependence(results, logger11);
|
|
1379
1379
|
return results;
|
|
1380
1380
|
}
|
|
1381
1381
|
|
|
@@ -2312,11 +2312,11 @@ var ConsensusEngine = class {
|
|
|
2312
2312
|
logger;
|
|
2313
2313
|
metrics;
|
|
2314
2314
|
voterExpansionCallback;
|
|
2315
|
-
constructor(config,
|
|
2315
|
+
constructor(config, logger11) {
|
|
2316
2316
|
this.config = { ...DEFAULT_CONSENSUS_CONFIG, ...config };
|
|
2317
2317
|
this.cacheConfig = { ...DEFAULT_PROPOSAL_CACHE_CONFIG, ...config?.proposalCache };
|
|
2318
2318
|
this.quorumConfig = { ...DEFAULT_INCREMENTAL_QUORUM_CONFIG, ...config?.incrementalQuorum };
|
|
2319
|
-
this.logger =
|
|
2319
|
+
this.logger = logger11 ?? createLogger({ component: "ConsensusEngine" });
|
|
2320
2320
|
this.strategyFactory = new VotingStrategyFactory();
|
|
2321
2321
|
this.metrics = this.createInitialMetrics();
|
|
2322
2322
|
}
|
|
@@ -2760,15 +2760,15 @@ var ConsensusEngine = class {
|
|
|
2760
2760
|
this.logger.debug("Proposal cache cleared");
|
|
2761
2761
|
}
|
|
2762
2762
|
};
|
|
2763
|
-
function createConsensusEngine(config,
|
|
2764
|
-
return new ConsensusEngine(config,
|
|
2763
|
+
function createConsensusEngine(config, logger11) {
|
|
2764
|
+
return new ConsensusEngine(config, logger11);
|
|
2765
2765
|
}
|
|
2766
2766
|
|
|
2767
2767
|
// src/consensus/quorum-validator.ts
|
|
2768
2768
|
var QuorumValidator = class {
|
|
2769
2769
|
logger;
|
|
2770
|
-
constructor(
|
|
2771
|
-
this.logger =
|
|
2770
|
+
constructor(logger11) {
|
|
2771
|
+
this.logger = logger11 ?? createLogger({ component: "QuorumValidator" });
|
|
2772
2772
|
}
|
|
2773
2773
|
validateQuorum(input) {
|
|
2774
2774
|
const { votes, config } = input;
|
|
@@ -2971,8 +2971,8 @@ var QuorumValidator = class {
|
|
|
2971
2971
|
};
|
|
2972
2972
|
}
|
|
2973
2973
|
};
|
|
2974
|
-
function createQuorumValidator(
|
|
2975
|
-
return new QuorumValidator(
|
|
2974
|
+
function createQuorumValidator(logger11) {
|
|
2975
|
+
return new QuorumValidator(logger11);
|
|
2976
2976
|
}
|
|
2977
2977
|
|
|
2978
2978
|
// src/consensus/voting-protocol-helpers.ts
|
|
@@ -4060,9 +4060,9 @@ function mergeHistory(target, source) {
|
|
|
4060
4060
|
return target;
|
|
4061
4061
|
}
|
|
4062
4062
|
var CorrelationModelPartitions = class {
|
|
4063
|
-
constructor(maxTrackedPairs,
|
|
4063
|
+
constructor(maxTrackedPairs, logger11) {
|
|
4064
4064
|
this.maxTrackedPairs = maxTrackedPairs;
|
|
4065
|
-
this.logger =
|
|
4065
|
+
this.logger = logger11;
|
|
4066
4066
|
}
|
|
4067
4067
|
maxTrackedPairs;
|
|
4068
4068
|
logger;
|
|
@@ -5238,8 +5238,8 @@ var VOTE_RECORDS_DATA_CATEGORY = "governance";
|
|
|
5238
5238
|
var VOTE_RECORDS_FILENAME = "vote-records.jsonl";
|
|
5239
5239
|
var VOTE_RECORDS_PATH_ENV = "NEXUS_VOTE_RECORDS_PATH";
|
|
5240
5240
|
var MAX_PROPOSAL_RECORD_CHARS = 500;
|
|
5241
|
-
function voteRecordWriteFailedMessage(
|
|
5242
|
-
return `Authentic vote record NOT persisted: the resolved data directory is not writable (${
|
|
5241
|
+
function voteRecordWriteFailedMessage(path2) {
|
|
5242
|
+
return `Authentic vote record NOT persisted: the resolved data directory is not writable (${path2}). Check filesystem permissions on the .nexus-agents data dir, or set ${VOTE_RECORDS_PATH_ENV} to a writable absolute file path. See server logs for the underlying filesystem error.`;
|
|
5243
5243
|
}
|
|
5244
5244
|
function outcomeToDecision(result, votes, errorVoided) {
|
|
5245
5245
|
const errorCount = votes.filter((v) => v.source === "error").length;
|
|
@@ -5319,7 +5319,7 @@ function buildVoteRecord(input) {
|
|
|
5319
5319
|
};
|
|
5320
5320
|
return { ...payload, hash: computeVoteRecordHash(payload) };
|
|
5321
5321
|
}
|
|
5322
|
-
function readLedgerTip(filePath,
|
|
5322
|
+
function readLedgerTip(filePath, logger11) {
|
|
5323
5323
|
if (!existsSync2(filePath)) return { maxSequence: -1, lastHash: void 0 };
|
|
5324
5324
|
try {
|
|
5325
5325
|
const { records } = readVoteRecords(filePath);
|
|
@@ -5331,7 +5331,7 @@ function readLedgerTip(filePath, logger10) {
|
|
|
5331
5331
|
const last = records[records.length - 1];
|
|
5332
5332
|
return { maxSequence, lastHash: last?.hash };
|
|
5333
5333
|
} catch (error) {
|
|
5334
|
-
|
|
5334
|
+
logger11.warn("Failed to read vote-record ledger tip", { error: getErrorMessage(error) });
|
|
5335
5335
|
return { maxSequence: -1, lastHash: void 0 };
|
|
5336
5336
|
}
|
|
5337
5337
|
}
|
|
@@ -5363,29 +5363,29 @@ function resolveVoteRecordsPath() {
|
|
|
5363
5363
|
return resolved;
|
|
5364
5364
|
}
|
|
5365
5365
|
function persistVoteRecord(opts) {
|
|
5366
|
-
const
|
|
5366
|
+
const logger11 = opts.logger ?? createLogger({ component: "vote-record-store" });
|
|
5367
5367
|
const filePath = opts.filePath ?? resolveVoteRecordsPath();
|
|
5368
5368
|
if (filePath === void 0) {
|
|
5369
|
-
|
|
5369
|
+
logger11.warn(voteRecordWriteFailedMessage("<unresolved>"), { id: opts.id });
|
|
5370
5370
|
return void 0;
|
|
5371
5371
|
}
|
|
5372
5372
|
try {
|
|
5373
5373
|
mkdirSync2(dirname(filePath), { recursive: true });
|
|
5374
|
-
const { maxSequence, lastHash } = readLedgerTip(filePath,
|
|
5374
|
+
const { maxSequence, lastHash } = readLedgerTip(filePath, logger11);
|
|
5375
5375
|
const record = buildVoteRecord({
|
|
5376
5376
|
...opts,
|
|
5377
5377
|
sequence: maxSequence + 1,
|
|
5378
5378
|
previousHash: lastHash
|
|
5379
5379
|
});
|
|
5380
5380
|
appendFileSync2(filePath, JSON.stringify(record) + "\n", "utf-8");
|
|
5381
|
-
|
|
5381
|
+
logger11.info("Persisted authentic vote record", {
|
|
5382
5382
|
id: record.id,
|
|
5383
5383
|
decision: record.decision,
|
|
5384
5384
|
path: filePath
|
|
5385
5385
|
});
|
|
5386
5386
|
return record;
|
|
5387
5387
|
} catch (error) {
|
|
5388
|
-
|
|
5388
|
+
logger11.warn("Failed to persist authentic vote record", {
|
|
5389
5389
|
error: getErrorMessage(error),
|
|
5390
5390
|
path: filePath
|
|
5391
5391
|
});
|
|
@@ -5417,10 +5417,10 @@ import { appendFileSync as appendFileSync3, readFileSync as readFileSync3, write
|
|
|
5417
5417
|
var PersistentOutcomeStore = class extends OutcomeStore {
|
|
5418
5418
|
filePath;
|
|
5419
5419
|
logger;
|
|
5420
|
-
constructor(config,
|
|
5420
|
+
constructor(config, logger11) {
|
|
5421
5421
|
super(config);
|
|
5422
5422
|
this.filePath = config?.filePath ?? getOutcomesFile();
|
|
5423
|
-
this.logger =
|
|
5423
|
+
this.logger = logger11 ?? createLogger({ component: "PersistentOutcomeStore" });
|
|
5424
5424
|
const dataDir = config?.dataDir;
|
|
5425
5425
|
ensureLearningDir(dataDir);
|
|
5426
5426
|
this.hydrate();
|
|
@@ -5720,7 +5720,7 @@ function recordDecisionCost(options) {
|
|
|
5720
5720
|
const billingMode = options.billingMode ?? resolveBillingMode();
|
|
5721
5721
|
const voters = votesToCostInputs(options.votes);
|
|
5722
5722
|
const store = options.store ?? new DecisionCostStore();
|
|
5723
|
-
const
|
|
5723
|
+
const logger11 = options.logger ?? defaultLogger2;
|
|
5724
5724
|
const timestamp = new Date(getTimeProvider().now()).toISOString();
|
|
5725
5725
|
const { record, persisted } = store.record({
|
|
5726
5726
|
decisionId: options.decisionId,
|
|
@@ -5734,7 +5734,7 @@ function recordDecisionCost(options) {
|
|
|
5734
5734
|
if (shouldWarnForDrop(droppedCostRecordCount)) {
|
|
5735
5735
|
const suppressedSinceLastWarn = droppedCostRecordCount - warnCountSinceReset - 1;
|
|
5736
5736
|
warnCountSinceReset += 1;
|
|
5737
|
-
|
|
5737
|
+
logger11.warn("Decision-cost rollup dropped (failed to persist) \u2014 billing telemetry lost", {
|
|
5738
5738
|
decisionId: options.decisionId,
|
|
5739
5739
|
gate: options.gate,
|
|
5740
5740
|
totalCostUsd: record.summary.totalCostUsd,
|
|
@@ -5757,7 +5757,7 @@ function rejectionRulesFrom(result) {
|
|
|
5757
5757
|
}
|
|
5758
5758
|
return rules.size > 0 ? [...rules] : void 0;
|
|
5759
5759
|
}
|
|
5760
|
-
function emitVoteRejectedSignal(result, bus,
|
|
5760
|
+
function emitVoteRejectedSignal(result, bus, logger11) {
|
|
5761
5761
|
if (result.outcome !== "rejected") return;
|
|
5762
5762
|
try {
|
|
5763
5763
|
const rejectionRules = rejectionRulesFrom(result);
|
|
@@ -5769,7 +5769,7 @@ function emitVoteRejectedSignal(result, bus, logger10) {
|
|
|
5769
5769
|
...rejectionRules !== void 0 ? { rejectionRules } : {}
|
|
5770
5770
|
});
|
|
5771
5771
|
} catch (error) {
|
|
5772
|
-
|
|
5772
|
+
logger11.warn("Failed to emit signal.vote_rejected", { error: getErrorMessage(error) });
|
|
5773
5773
|
}
|
|
5774
5774
|
}
|
|
5775
5775
|
|
|
@@ -5779,10 +5779,10 @@ var ALLOW_SIMULATE_ENV = "NEXUS_ALLOW_SIMULATE";
|
|
|
5779
5779
|
function isTestRunner() {
|
|
5780
5780
|
return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
5781
5781
|
}
|
|
5782
|
-
function checkSimulationAllowed(toolName,
|
|
5782
|
+
function checkSimulationAllowed(toolName, logger11) {
|
|
5783
5783
|
if (isTestRunner()) return { allowed: true, optedIn: false };
|
|
5784
5784
|
if (process.env[ALLOW_SIMULATE_ENV] === "1") {
|
|
5785
|
-
warnOnce(toolName,
|
|
5785
|
+
warnOnce(toolName, logger11);
|
|
5786
5786
|
return { allowed: true, optedIn: true };
|
|
5787
5787
|
}
|
|
5788
5788
|
return {
|
|
@@ -5797,10 +5797,10 @@ function simulationDeniedResult(reason) {
|
|
|
5797
5797
|
detail: { optIn: `${ALLOW_SIMULATE_ENV}=1` }
|
|
5798
5798
|
});
|
|
5799
5799
|
}
|
|
5800
|
-
function warnOnce(toolName,
|
|
5800
|
+
function warnOnce(toolName, logger11) {
|
|
5801
5801
|
if (WARNED.has(toolName)) return;
|
|
5802
5802
|
WARNED.add(toolName);
|
|
5803
|
-
|
|
5803
|
+
logger11.warn(
|
|
5804
5804
|
`[${toolName}] simulateVotes=true: output is RANDOM and reserved for tests/demos. Do not treat the result as a real decision.`
|
|
5805
5805
|
);
|
|
5806
5806
|
}
|
|
@@ -5881,13 +5881,13 @@ function isAbandonedJob(record, nowMs) {
|
|
|
5881
5881
|
function jobResultPath(jobId) {
|
|
5882
5882
|
return nexusDataPathEnsure("jobs", `result-${jobId}.json`);
|
|
5883
5883
|
}
|
|
5884
|
-
function persistJobRecord(
|
|
5885
|
-
writeFileSync3(
|
|
5886
|
-
chmodSync(
|
|
5884
|
+
function persistJobRecord(path2, record) {
|
|
5885
|
+
writeFileSync3(path2, JSON.stringify(record, null, 2), { mode: 384 });
|
|
5886
|
+
chmodSync(path2, 384);
|
|
5887
5887
|
}
|
|
5888
5888
|
function writeJobPending(jobId, toolName, signalAccepted, producerVersion = VERSION) {
|
|
5889
|
-
const
|
|
5890
|
-
if (existsSync4(
|
|
5889
|
+
const path2 = jobResultPath(jobId);
|
|
5890
|
+
if (existsSync4(path2)) {
|
|
5891
5891
|
logger7.debug("Job result file already exists \u2014 leaving in place", { jobId });
|
|
5892
5892
|
return;
|
|
5893
5893
|
}
|
|
@@ -5900,7 +5900,7 @@ function writeJobPending(jobId, toolName, signalAccepted, producerVersion = VERS
|
|
|
5900
5900
|
...signalAccepted !== void 0 ? { signalAccepted } : {},
|
|
5901
5901
|
producerVersion
|
|
5902
5902
|
};
|
|
5903
|
-
persistJobRecord(
|
|
5903
|
+
persistJobRecord(path2, record);
|
|
5904
5904
|
logger7.debug("Wrote pending job record", { jobId, toolName, signalAccepted });
|
|
5905
5905
|
}
|
|
5906
5906
|
function writeJobComplete(jobId, toolName, result, producerVersion = VERSION) {
|
|
@@ -5962,20 +5962,20 @@ function writeJobCancelled(jobId, toolName, reason, producerVersion = VERSION) {
|
|
|
5962
5962
|
logger7.debug("Wrote cancelled job record", { jobId, toolName, reason });
|
|
5963
5963
|
}
|
|
5964
5964
|
function readJobResult(jobId) {
|
|
5965
|
-
const
|
|
5966
|
-
if (!existsSync4(
|
|
5965
|
+
const path2 = nexusDataPath("jobs", `result-${jobId}.json`);
|
|
5966
|
+
if (!existsSync4(path2)) return null;
|
|
5967
5967
|
try {
|
|
5968
|
-
const raw = JSON.parse(readFileSync4(
|
|
5968
|
+
const raw = JSON.parse(readFileSync4(path2, "utf-8"));
|
|
5969
5969
|
const parsed = JobResultSchema.safeParse(raw);
|
|
5970
5970
|
if (!parsed.success) {
|
|
5971
|
-
logger7.warn("Job result file failed schema check", { jobId, path });
|
|
5971
|
+
logger7.warn("Job result file failed schema check", { jobId, path: path2 });
|
|
5972
5972
|
return null;
|
|
5973
5973
|
}
|
|
5974
5974
|
return parsed.data;
|
|
5975
5975
|
} catch (err2) {
|
|
5976
5976
|
logger7.warn("Job result file unreadable", {
|
|
5977
5977
|
jobId,
|
|
5978
|
-
path,
|
|
5978
|
+
path: path2,
|
|
5979
5979
|
error: err2 instanceof Error ? err2.message : String(err2)
|
|
5980
5980
|
});
|
|
5981
5981
|
return null;
|
|
@@ -6078,9 +6078,9 @@ function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = rando
|
|
|
6078
6078
|
return { kind: "fresh", jobId: randomFallback() };
|
|
6079
6079
|
}
|
|
6080
6080
|
const incomingInputsHash = computeInputsHash(inputs);
|
|
6081
|
-
const
|
|
6082
|
-
if (existsSync5(
|
|
6083
|
-
const entry = readIndexEntry(
|
|
6081
|
+
const path2 = indexFilePathReadOnly(tool, idempotencyKey);
|
|
6082
|
+
if (existsSync5(path2)) {
|
|
6083
|
+
const entry = readIndexEntry(path2);
|
|
6084
6084
|
if (entry !== null) {
|
|
6085
6085
|
if (entry.inputsHash === incomingInputsHash) {
|
|
6086
6086
|
return { kind: "replay", jobId: entry.jobId, entry };
|
|
@@ -6092,14 +6092,14 @@ function resolveIdempotency(tool, idempotencyKey, inputs, randomFallback = rando
|
|
|
6092
6092
|
existingJobId: entry.jobId
|
|
6093
6093
|
};
|
|
6094
6094
|
}
|
|
6095
|
-
logger8.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path });
|
|
6095
|
+
logger8.warn("Idempotency index file unreadable; treating as fresh dispatch", { tool, path: path2 });
|
|
6096
6096
|
}
|
|
6097
6097
|
const jobId = `job-${tool}-${sha256Hex(`${tool}:${idempotencyKey}:${incomingInputsHash}`).slice(0, 16)}`;
|
|
6098
6098
|
return { kind: "fresh", jobId };
|
|
6099
6099
|
}
|
|
6100
6100
|
function registerIdempotentJob(params) {
|
|
6101
|
-
const
|
|
6102
|
-
if (existsSync5(
|
|
6101
|
+
const path2 = indexFilePath(params.tool, params.idempotencyKey);
|
|
6102
|
+
if (existsSync5(path2)) {
|
|
6103
6103
|
logger8.debug("Idempotency index entry already present; skipping write", {
|
|
6104
6104
|
tool: params.tool,
|
|
6105
6105
|
jobId: params.jobId
|
|
@@ -6114,7 +6114,7 @@ function registerIdempotentJob(params) {
|
|
|
6114
6114
|
jobId: params.jobId,
|
|
6115
6115
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6116
6116
|
};
|
|
6117
|
-
writeFileSync4(
|
|
6117
|
+
writeFileSync4(path2, JSON.stringify(entry, null, 2));
|
|
6118
6118
|
logger8.debug("Wrote idempotency index entry", { tool: params.tool, jobId: params.jobId });
|
|
6119
6119
|
}
|
|
6120
6120
|
function shortCircuitOrFreshJobId(params) {
|
|
@@ -6132,18 +6132,18 @@ function shortCircuitOrFreshJobId(params) {
|
|
|
6132
6132
|
}
|
|
6133
6133
|
return { kind: "continue", jobId: r.jobId };
|
|
6134
6134
|
}
|
|
6135
|
-
function readIndexEntry(
|
|
6135
|
+
function readIndexEntry(path2) {
|
|
6136
6136
|
try {
|
|
6137
|
-
const raw = JSON.parse(readFileSync5(
|
|
6137
|
+
const raw = JSON.parse(readFileSync5(path2, "utf-8"));
|
|
6138
6138
|
const parsed = IdempotencyIndexEntrySchema.safeParse(raw);
|
|
6139
6139
|
if (!parsed.success) {
|
|
6140
|
-
logger8.warn("Idempotency index file failed schema check", { path });
|
|
6140
|
+
logger8.warn("Idempotency index file failed schema check", { path: path2 });
|
|
6141
6141
|
return null;
|
|
6142
6142
|
}
|
|
6143
6143
|
return parsed.data;
|
|
6144
6144
|
} catch (err2) {
|
|
6145
6145
|
logger8.warn("Idempotency index file read failed", {
|
|
6146
|
-
path,
|
|
6146
|
+
path: path2,
|
|
6147
6147
|
error: err2 instanceof Error ? err2.message : String(err2)
|
|
6148
6148
|
});
|
|
6149
6149
|
return null;
|
|
@@ -6244,17 +6244,486 @@ function suggestRetryAfterMs(toolName) {
|
|
|
6244
6244
|
return Math.min(6e4, Math.max(5e3, base));
|
|
6245
6245
|
}
|
|
6246
6246
|
|
|
6247
|
+
// src/context/structured-task-state.ts
|
|
6248
|
+
import * as fs2 from "node:fs";
|
|
6249
|
+
import * as path from "node:path";
|
|
6250
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6251
|
+
|
|
6252
|
+
// src/context/structured-task-state-types.ts
|
|
6253
|
+
import { z as z10 } from "zod";
|
|
6254
|
+
var TaskStageSchema = z10.enum([
|
|
6255
|
+
"planning",
|
|
6256
|
+
"executing",
|
|
6257
|
+
"verifying",
|
|
6258
|
+
"complete",
|
|
6259
|
+
"blocked",
|
|
6260
|
+
// #3091 / epic #2631: terminal failure stage. Distinct from `blocked`
|
|
6261
|
+
// (recoverable, still in-flight) — `failed` is terminal and maps to the
|
|
6262
|
+
// job-result `failed` status. Added so async-mode writers can record a
|
|
6263
|
+
// failed run in StructuredTaskState instead of the Stage-1 sidecar.
|
|
6264
|
+
"failed"
|
|
6265
|
+
]);
|
|
6266
|
+
var TaskDecisionSchema = z10.object({
|
|
6267
|
+
ts: z10.iso.datetime(),
|
|
6268
|
+
decision: z10.string().min(1),
|
|
6269
|
+
rationale: z10.string().min(1)
|
|
6270
|
+
});
|
|
6271
|
+
var TaskBlockerSchema = z10.object({
|
|
6272
|
+
ts: z10.iso.datetime(),
|
|
6273
|
+
blocker: z10.string().min(1),
|
|
6274
|
+
resolved: z10.iso.datetime().optional()
|
|
6275
|
+
});
|
|
6276
|
+
var TaskPositionSchema = z10.object({
|
|
6277
|
+
currentStep: z10.string().min(1),
|
|
6278
|
+
nextStep: z10.string().optional()
|
|
6279
|
+
});
|
|
6280
|
+
var TaskLedgerSchema = z10.object({
|
|
6281
|
+
/** Verifiable observations: what we have observed/measured/confirmed. */
|
|
6282
|
+
facts: z10.array(z10.string()),
|
|
6283
|
+
/** Working assumptions: useful guesses we proceed under, marked separately. */
|
|
6284
|
+
guesses: z10.array(z10.string()),
|
|
6285
|
+
/** Things we still need to figure out before the plan is sound. */
|
|
6286
|
+
openQuestions: z10.array(z10.string()),
|
|
6287
|
+
updatedAt: z10.iso.datetime()
|
|
6288
|
+
});
|
|
6289
|
+
var ReflectActionSchema = z10.enum([
|
|
6290
|
+
"continue",
|
|
6291
|
+
"revise_plan",
|
|
6292
|
+
"escalate_to_human",
|
|
6293
|
+
"abort"
|
|
6294
|
+
]);
|
|
6295
|
+
var ProgressLedgerEntrySchema = z10.object({
|
|
6296
|
+
ts: z10.iso.datetime(),
|
|
6297
|
+
/** What just happened (the step we are reflecting on). */
|
|
6298
|
+
step: z10.string().min(1),
|
|
6299
|
+
/** Is the outer Task Ledger plan still right after this step? */
|
|
6300
|
+
planStillValid: z10.boolean(),
|
|
6301
|
+
/** Are we making progress, or have we stalled? */
|
|
6302
|
+
stuck: z10.boolean(),
|
|
6303
|
+
/** What the orchestrator should do next based on this reflection. */
|
|
6304
|
+
suggestedAction: ReflectActionSchema,
|
|
6305
|
+
/** Why this action was chosen — required so future readers can audit the call. */
|
|
6306
|
+
rationale: z10.string().min(1)
|
|
6307
|
+
});
|
|
6308
|
+
var TaskCancellationSchema = z10.object({
|
|
6309
|
+
requestedAt: z10.iso.datetime(),
|
|
6310
|
+
/** Optional human-readable note carried back to the caller. */
|
|
6311
|
+
reason: z10.string().max(1e3).optional()
|
|
6312
|
+
});
|
|
6313
|
+
var TASK_RESULT_MAX_BYTES = 1048576;
|
|
6314
|
+
var StructuredTaskStateSchema = z10.object({
|
|
6315
|
+
taskId: z10.string().min(1),
|
|
6316
|
+
/**
|
|
6317
|
+
* Marks state created by the shared async-job dispatcher (#5622).
|
|
6318
|
+
* Optional for backward compatibility; absence does not prove async dispatch.
|
|
6319
|
+
*/
|
|
6320
|
+
dispatch: z10.literal("async").optional(),
|
|
6321
|
+
stage: TaskStageSchema,
|
|
6322
|
+
decisions: z10.array(TaskDecisionSchema),
|
|
6323
|
+
blockers: z10.array(TaskBlockerSchema),
|
|
6324
|
+
position: TaskPositionSchema,
|
|
6325
|
+
/**
|
|
6326
|
+
* Magentic-One Task Ledger (#2278). Mutable single-object outer-loop state:
|
|
6327
|
+
* facts/guesses/openQuestions revised together when the plan is replanned.
|
|
6328
|
+
* Optional so the field can be added later without breaking existing logs.
|
|
6329
|
+
*/
|
|
6330
|
+
taskLedger: TaskLedgerSchema.optional(),
|
|
6331
|
+
/**
|
|
6332
|
+
* Magentic-One Progress Ledger (#2278). Append-only inner-loop reflections.
|
|
6333
|
+
* Most recent entry's `suggestedAction` is what `reflect()` returns. Optional
|
|
6334
|
+
* for the same backward-compat reason as `taskLedger`.
|
|
6335
|
+
*/
|
|
6336
|
+
progressLedger: z10.array(ProgressLedgerEntrySchema).optional(),
|
|
6337
|
+
/**
|
|
6338
|
+
* Monotonic version counter (#3043 / epic #2631 Stage 2). Increments by
|
|
6339
|
+
* 1 on every write; polling clients use this to detect "I missed a
|
|
6340
|
+
* transition" and resync rather than silently observe a stale snapshot.
|
|
6341
|
+
*
|
|
6342
|
+
* Optional + defaulting-to-0 in the reducer for backward-compat — old
|
|
6343
|
+
* state logs (pre-Stage-2) don't carry it; the reducer treats their
|
|
6344
|
+
* baseline as v0 and starts counting from there.
|
|
6345
|
+
*/
|
|
6346
|
+
version: z10.number().int().nonnegative().optional(),
|
|
6347
|
+
/**
|
|
6348
|
+
* Tool result payload (#3043 / epic #2631 Stage 2). Set atomically
|
|
6349
|
+
* via the `result` log event when `stage === 'complete'`. Carries the
|
|
6350
|
+
* same shape the synchronous mode would have returned inline.
|
|
6351
|
+
*
|
|
6352
|
+
* Capped at `TASK_RESULT_MAX_BYTES` at write time; over-cap payloads
|
|
6353
|
+
* are replaced with `{ truncated: true, originalBytes }` so the
|
|
6354
|
+
* cap is observable rather than silent.
|
|
6355
|
+
*
|
|
6356
|
+
* Once Stage 2 (this) ships, Stage 3+ (#3044 / #3045) migrate the
|
|
6357
|
+
* orchestrate / run_workflow / consensus_vote async-mode writers
|
|
6358
|
+
* from the Stage-1 sidecar (`mcp/jobs/job-result-store.ts`) to this
|
|
6359
|
+
* field, then the sidecar files become legacy that the next cleanup
|
|
6360
|
+
* sweep can remove.
|
|
6361
|
+
*/
|
|
6362
|
+
result: z10.unknown().optional(),
|
|
6363
|
+
/**
|
|
6364
|
+
* Cancellation marker (#3043 / epic #2631 Stage 2). Set by `cancel_job`
|
|
6365
|
+
* — the running tool checks for this at safe-point boundaries (between
|
|
6366
|
+
* steps, between waves) and aborts via the AbortSignal plumbing from
|
|
6367
|
+
* #3035 / #3038. Append-only by construction (the reducer ignores a
|
|
6368
|
+
* second cancellation event).
|
|
6369
|
+
*/
|
|
6370
|
+
cancellation: TaskCancellationSchema.optional(),
|
|
6371
|
+
/**
|
|
6372
|
+
* Creation timestamp (#3090 / epic #2631). Set by the reducer to the
|
|
6373
|
+
* `init` entry's `ts` and never mutated thereafter — `updatedAt` advances
|
|
6374
|
+
* with each write, but `createdAt` is fixed. Job-result readers need the
|
|
6375
|
+
* original creation time; `updatedAt` alone can't supply it once any
|
|
6376
|
+
* transition has been recorded. Optional for backward-compat with
|
|
6377
|
+
* pre-#3090 logs (the reducer backfills it from the init entry).
|
|
6378
|
+
*/
|
|
6379
|
+
createdAt: z10.iso.datetime().optional(),
|
|
6380
|
+
updatedAt: z10.iso.datetime()
|
|
6381
|
+
});
|
|
6382
|
+
var StructuredTaskLogEntrySchema = z10.discriminatedUnion("event", [
|
|
6383
|
+
z10.object({
|
|
6384
|
+
event: z10.literal("init"),
|
|
6385
|
+
ts: z10.iso.datetime(),
|
|
6386
|
+
state: StructuredTaskStateSchema
|
|
6387
|
+
}),
|
|
6388
|
+
z10.object({
|
|
6389
|
+
event: z10.literal("decision"),
|
|
6390
|
+
ts: z10.iso.datetime(),
|
|
6391
|
+
decision: TaskDecisionSchema
|
|
6392
|
+
}),
|
|
6393
|
+
z10.object({
|
|
6394
|
+
event: z10.literal("blocker"),
|
|
6395
|
+
ts: z10.iso.datetime(),
|
|
6396
|
+
blocker: TaskBlockerSchema
|
|
6397
|
+
}),
|
|
6398
|
+
z10.object({
|
|
6399
|
+
event: z10.literal("blocker_resolved"),
|
|
6400
|
+
ts: z10.iso.datetime(),
|
|
6401
|
+
/** Index of the blocker in the state's blockers[] array. */
|
|
6402
|
+
blockerIndex: z10.number().int().nonnegative(),
|
|
6403
|
+
resolvedAt: z10.iso.datetime()
|
|
6404
|
+
}),
|
|
6405
|
+
z10.object({
|
|
6406
|
+
event: z10.literal("stage"),
|
|
6407
|
+
ts: z10.iso.datetime(),
|
|
6408
|
+
stage: TaskStageSchema
|
|
6409
|
+
}),
|
|
6410
|
+
z10.object({
|
|
6411
|
+
event: z10.literal("position"),
|
|
6412
|
+
ts: z10.iso.datetime(),
|
|
6413
|
+
position: TaskPositionSchema
|
|
6414
|
+
}),
|
|
6415
|
+
// Magentic-One Task Ledger replacement event — the outer loop revises the
|
|
6416
|
+
// entire ledger atomically when replanning. Always replaces, never partial.
|
|
6417
|
+
z10.object({
|
|
6418
|
+
event: z10.literal("task_ledger"),
|
|
6419
|
+
ts: z10.iso.datetime(),
|
|
6420
|
+
ledger: TaskLedgerSchema
|
|
6421
|
+
}),
|
|
6422
|
+
// Magentic-One Progress Ledger append event — one self-reflection per step.
|
|
6423
|
+
z10.object({
|
|
6424
|
+
event: z10.literal("progress_ledger"),
|
|
6425
|
+
ts: z10.iso.datetime(),
|
|
6426
|
+
entry: ProgressLedgerEntrySchema
|
|
6427
|
+
}),
|
|
6428
|
+
// #3043 / epic #2631 Stage 2 — tool result payload set when stage flips
|
|
6429
|
+
// to 'complete'. Written by appendResult; replayed into state.result.
|
|
6430
|
+
// The payload itself is `unknown` because each tool's result shape is
|
|
6431
|
+
// its own; readers cast after a status check.
|
|
6432
|
+
z10.object({
|
|
6433
|
+
event: z10.literal("result"),
|
|
6434
|
+
ts: z10.iso.datetime(),
|
|
6435
|
+
result: z10.unknown()
|
|
6436
|
+
}),
|
|
6437
|
+
// #3043 / epic #2631 Stage 2 — cancellation marker. Append-only: the
|
|
6438
|
+
// reducer ignores a second cancellation event so a malicious or buggy
|
|
6439
|
+
// double-cancel can't rewrite the requestedAt timestamp.
|
|
6440
|
+
z10.object({
|
|
6441
|
+
event: z10.literal("cancellation"),
|
|
6442
|
+
ts: z10.iso.datetime(),
|
|
6443
|
+
cancellation: TaskCancellationSchema
|
|
6444
|
+
})
|
|
6445
|
+
]);
|
|
6446
|
+
|
|
6447
|
+
// src/context/structured-task-state.ts
|
|
6448
|
+
var logger10 = createLogger({ component: "structured-task-state" });
|
|
6449
|
+
var TASKS_SUBDIR = "tasks";
|
|
6450
|
+
var FILE_MODE2 = 384;
|
|
6451
|
+
var DIR_MODE2 = 448;
|
|
6452
|
+
var asyncTaskStateJob = new AsyncLocalStorage();
|
|
6453
|
+
function withAsyncTaskStateDispatch(jobId, operation) {
|
|
6454
|
+
return asyncTaskStateJob.run(jobId, operation);
|
|
6455
|
+
}
|
|
6456
|
+
function getTasksDir(customDir) {
|
|
6457
|
+
if (customDir !== void 0) return path.resolve(customDir);
|
|
6458
|
+
return nexusDataPath(TASKS_SUBDIR);
|
|
6459
|
+
}
|
|
6460
|
+
function getLogPath(taskId, customDir) {
|
|
6461
|
+
return path.join(getTasksDir(customDir), `state-${taskId}.jsonl`);
|
|
6462
|
+
}
|
|
6463
|
+
function listTaskStateIds(customDir) {
|
|
6464
|
+
const dir = getTasksDir(customDir);
|
|
6465
|
+
if (!fs2.existsSync(dir)) return [];
|
|
6466
|
+
let entries;
|
|
6467
|
+
try {
|
|
6468
|
+
entries = fs2.readdirSync(dir);
|
|
6469
|
+
} catch (cause) {
|
|
6470
|
+
logger10.warn("tasks directory unreadable", {
|
|
6471
|
+
dir,
|
|
6472
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
6473
|
+
});
|
|
6474
|
+
return [];
|
|
6475
|
+
}
|
|
6476
|
+
const ids = [];
|
|
6477
|
+
for (const entry of entries) {
|
|
6478
|
+
const match = /^state-(.+)\.jsonl$/.exec(entry);
|
|
6479
|
+
if (match?.[1] !== void 0) ids.push(match[1]);
|
|
6480
|
+
}
|
|
6481
|
+
return ids;
|
|
6482
|
+
}
|
|
6483
|
+
function validateTaskId(taskId) {
|
|
6484
|
+
if (taskId.includes("..") || taskId.includes("/") || taskId.includes("\\")) {
|
|
6485
|
+
return err(new Error("Invalid task ID: contains path traversal characters"));
|
|
6486
|
+
}
|
|
6487
|
+
if (taskId.length === 0 || taskId.length > 128) {
|
|
6488
|
+
return err(new Error("Invalid task ID: must be 1-128 characters"));
|
|
6489
|
+
}
|
|
6490
|
+
return ok(taskId);
|
|
6491
|
+
}
|
|
6492
|
+
function ensureTasksDir(customDir) {
|
|
6493
|
+
const dirPath = getTasksDir(customDir);
|
|
6494
|
+
try {
|
|
6495
|
+
fs2.mkdirSync(dirPath, { recursive: true, mode: DIR_MODE2 });
|
|
6496
|
+
return ok(dirPath);
|
|
6497
|
+
} catch (cause) {
|
|
6498
|
+
const e = cause instanceof Error ? cause : new Error(String(cause));
|
|
6499
|
+
return err(new Error(`Failed to create tasks directory at ${dirPath}: ${e.message}`));
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
function appendLogEntry(taskId, entry, customDir) {
|
|
6503
|
+
const idCheck = validateTaskId(taskId);
|
|
6504
|
+
if (!idCheck.ok) return err(idCheck.error);
|
|
6505
|
+
const dirResult = ensureTasksDir(customDir);
|
|
6506
|
+
if (!dirResult.ok) return err(dirResult.error);
|
|
6507
|
+
const filePath = getLogPath(taskId, customDir);
|
|
6508
|
+
const line = JSON.stringify(entry) + "\n";
|
|
6509
|
+
try {
|
|
6510
|
+
fs2.appendFileSync(filePath, line, { mode: FILE_MODE2 });
|
|
6511
|
+
return ok(void 0);
|
|
6512
|
+
} catch (cause) {
|
|
6513
|
+
const e = cause instanceof Error ? cause : new Error(String(cause));
|
|
6514
|
+
return err(new Error(`Failed to write task state entry: ${e.message}`));
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
function initTaskState(state, customDir) {
|
|
6518
|
+
const isAsyncJob = asyncTaskStateJob.getStore() === state.taskId;
|
|
6519
|
+
const persistedState = isAsyncJob ? { ...state, dispatch: "async" } : state;
|
|
6520
|
+
return appendLogEntry(
|
|
6521
|
+
state.taskId,
|
|
6522
|
+
{ event: "init", ts: state.updatedAt, state: persistedState },
|
|
6523
|
+
customDir
|
|
6524
|
+
);
|
|
6525
|
+
}
|
|
6526
|
+
function appendBlocker(taskId, blocker, customDir) {
|
|
6527
|
+
return appendLogEntry(taskId, { event: "blocker", ts: blocker.ts, blocker }, customDir);
|
|
6528
|
+
}
|
|
6529
|
+
function updateStage(taskId, stage, ts, customDir) {
|
|
6530
|
+
return appendLogEntry(taskId, { event: "stage", ts, stage }, customDir);
|
|
6531
|
+
}
|
|
6532
|
+
function appendResult(taskId, result, ts, customDir) {
|
|
6533
|
+
let payload = result;
|
|
6534
|
+
let serialized;
|
|
6535
|
+
try {
|
|
6536
|
+
serialized = JSON.stringify(result ?? null);
|
|
6537
|
+
} catch (cause) {
|
|
6538
|
+
const e = cause instanceof Error ? cause : new Error(String(cause));
|
|
6539
|
+
return err(new Error(`Failed to serialize result for task ${taskId}: ${e.message}`));
|
|
6540
|
+
}
|
|
6541
|
+
const bytes = Buffer.byteLength(serialized, "utf-8");
|
|
6542
|
+
if (bytes > TASK_RESULT_MAX_BYTES) {
|
|
6543
|
+
payload = {
|
|
6544
|
+
truncated: true,
|
|
6545
|
+
originalBytes: bytes,
|
|
6546
|
+
maxBytes: TASK_RESULT_MAX_BYTES,
|
|
6547
|
+
note: "Result exceeded TASK_RESULT_MAX_BYTES \u2014 original dropped at write time."
|
|
6548
|
+
};
|
|
6549
|
+
logger10.warn("Task result truncated at write", {
|
|
6550
|
+
taskId,
|
|
6551
|
+
bytes,
|
|
6552
|
+
max: TASK_RESULT_MAX_BYTES
|
|
6553
|
+
});
|
|
6554
|
+
}
|
|
6555
|
+
return appendLogEntry(taskId, { event: "result", ts, result: payload }, customDir);
|
|
6556
|
+
}
|
|
6557
|
+
function appendCancellation(taskId, cancellation, customDir) {
|
|
6558
|
+
return appendLogEntry(
|
|
6559
|
+
taskId,
|
|
6560
|
+
{ event: "cancellation", ts: cancellation.requestedAt, cancellation },
|
|
6561
|
+
customDir
|
|
6562
|
+
);
|
|
6563
|
+
}
|
|
6564
|
+
function readTaskState(taskId, customDir) {
|
|
6565
|
+
const idCheck = validateTaskId(taskId);
|
|
6566
|
+
if (!idCheck.ok) return err(idCheck.error);
|
|
6567
|
+
const filePath = getLogPath(taskId, customDir);
|
|
6568
|
+
if (!fs2.existsSync(filePath)) {
|
|
6569
|
+
return err(new Error(`No state log for task: ${taskId}`));
|
|
6570
|
+
}
|
|
6571
|
+
let content;
|
|
6572
|
+
try {
|
|
6573
|
+
content = fs2.readFileSync(filePath, "utf-8");
|
|
6574
|
+
} catch (cause) {
|
|
6575
|
+
const e = cause instanceof Error ? cause : new Error(String(cause));
|
|
6576
|
+
return err(new Error(`Failed to read state log: ${e.message}`));
|
|
6577
|
+
}
|
|
6578
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
6579
|
+
const entries = [];
|
|
6580
|
+
for (const line of lines) {
|
|
6581
|
+
try {
|
|
6582
|
+
const parsed = JSON.parse(line);
|
|
6583
|
+
const validated = StructuredTaskLogEntrySchema.safeParse(parsed);
|
|
6584
|
+
if (validated.success) {
|
|
6585
|
+
entries.push(validated.data);
|
|
6586
|
+
} else {
|
|
6587
|
+
logger10.warn("Skipping malformed task-state log entry", {
|
|
6588
|
+
taskId,
|
|
6589
|
+
error: validated.error.message
|
|
6590
|
+
});
|
|
6591
|
+
}
|
|
6592
|
+
} catch (cause) {
|
|
6593
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
6594
|
+
logger10.warn("Skipping unparseable task-state log entry", { taskId, error: msg });
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
return reduceLogEntries(taskId, entries);
|
|
6598
|
+
}
|
|
6599
|
+
function reduceLogEntries(taskId, entries) {
|
|
6600
|
+
const init = entries.find((e) => e.event === "init");
|
|
6601
|
+
if (init === void 0) {
|
|
6602
|
+
return err(new Error(`No init entry found for task: ${taskId}`));
|
|
6603
|
+
}
|
|
6604
|
+
const initState = init.state;
|
|
6605
|
+
if (initState.taskId !== taskId) {
|
|
6606
|
+
return err(
|
|
6607
|
+
new Error(`Task ID mismatch: log contains state for ${initState.taskId}, expected ${taskId}`)
|
|
6608
|
+
);
|
|
6609
|
+
}
|
|
6610
|
+
let state = {
|
|
6611
|
+
...initState,
|
|
6612
|
+
decisions: [...initState.decisions],
|
|
6613
|
+
blockers: initState.blockers.map((b) => ({ ...b })),
|
|
6614
|
+
version: initState.version ?? 0,
|
|
6615
|
+
// #3090: createdAt is fixed at the init entry's ts and never advances
|
|
6616
|
+
// (unlike updatedAt). Prefer an explicit createdAt on the init state,
|
|
6617
|
+
// else fall back to the init entry's ts.
|
|
6618
|
+
createdAt: initState.createdAt ?? init.ts
|
|
6619
|
+
};
|
|
6620
|
+
for (const entry of entries) {
|
|
6621
|
+
if (entry === init) continue;
|
|
6622
|
+
state = applyLogEntry(state, entry);
|
|
6623
|
+
}
|
|
6624
|
+
return ok(state);
|
|
6625
|
+
}
|
|
6626
|
+
function applyBlockerEvent(state, entry, nextVersion) {
|
|
6627
|
+
if (entry.event === "blocker") {
|
|
6628
|
+
return {
|
|
6629
|
+
...state,
|
|
6630
|
+
blockers: [...state.blockers, entry.blocker],
|
|
6631
|
+
updatedAt: entry.ts,
|
|
6632
|
+
version: nextVersion
|
|
6633
|
+
};
|
|
6634
|
+
}
|
|
6635
|
+
return {
|
|
6636
|
+
...state,
|
|
6637
|
+
blockers: state.blockers.map(
|
|
6638
|
+
(b, i) => i === entry.blockerIndex ? { ...b, resolved: entry.resolvedAt } : b
|
|
6639
|
+
),
|
|
6640
|
+
updatedAt: entry.ts,
|
|
6641
|
+
version: nextVersion
|
|
6642
|
+
};
|
|
6643
|
+
}
|
|
6644
|
+
function applyReplacementEvent(state, entry, nextVersion) {
|
|
6645
|
+
switch (entry.event) {
|
|
6646
|
+
case "decision":
|
|
6647
|
+
return {
|
|
6648
|
+
...state,
|
|
6649
|
+
decisions: [...state.decisions, entry.decision],
|
|
6650
|
+
updatedAt: entry.ts,
|
|
6651
|
+
version: nextVersion
|
|
6652
|
+
};
|
|
6653
|
+
case "stage":
|
|
6654
|
+
return { ...state, stage: entry.stage, updatedAt: entry.ts, version: nextVersion };
|
|
6655
|
+
case "position":
|
|
6656
|
+
return { ...state, position: entry.position, updatedAt: entry.ts, version: nextVersion };
|
|
6657
|
+
case "task_ledger":
|
|
6658
|
+
return { ...state, taskLedger: entry.ledger, updatedAt: entry.ts, version: nextVersion };
|
|
6659
|
+
case "progress_ledger":
|
|
6660
|
+
return {
|
|
6661
|
+
...state,
|
|
6662
|
+
progressLedger: [...state.progressLedger ?? [], entry.entry],
|
|
6663
|
+
updatedAt: entry.ts,
|
|
6664
|
+
version: nextVersion
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
function applyTerminalEvent(state, entry, nextVersion) {
|
|
6669
|
+
if (entry.event === "result") {
|
|
6670
|
+
return { ...state, result: entry.result, updatedAt: entry.ts, version: nextVersion };
|
|
6671
|
+
}
|
|
6672
|
+
return {
|
|
6673
|
+
...state,
|
|
6674
|
+
cancellation: state.cancellation ?? entry.cancellation,
|
|
6675
|
+
updatedAt: entry.ts,
|
|
6676
|
+
version: nextVersion
|
|
6677
|
+
};
|
|
6678
|
+
}
|
|
6679
|
+
var EVENT_CATEGORY = {
|
|
6680
|
+
init: "init",
|
|
6681
|
+
blocker: "blocker",
|
|
6682
|
+
blocker_resolved: "blocker",
|
|
6683
|
+
decision: "replace",
|
|
6684
|
+
stage: "replace",
|
|
6685
|
+
position: "replace",
|
|
6686
|
+
task_ledger: "replace",
|
|
6687
|
+
progress_ledger: "replace",
|
|
6688
|
+
result: "terminal",
|
|
6689
|
+
cancellation: "terminal"
|
|
6690
|
+
};
|
|
6691
|
+
function applyLogEntry(state, entry) {
|
|
6692
|
+
const category = EVENT_CATEGORY[entry.event];
|
|
6693
|
+
if (category === "init") return state;
|
|
6694
|
+
const nextVersion = (state.version ?? 0) + 1;
|
|
6695
|
+
if (category === "blocker") {
|
|
6696
|
+
return applyBlockerEvent(
|
|
6697
|
+
state,
|
|
6698
|
+
entry,
|
|
6699
|
+
nextVersion
|
|
6700
|
+
);
|
|
6701
|
+
}
|
|
6702
|
+
if (category === "terminal") {
|
|
6703
|
+
return applyTerminalEvent(
|
|
6704
|
+
state,
|
|
6705
|
+
entry,
|
|
6706
|
+
nextVersion
|
|
6707
|
+
);
|
|
6708
|
+
}
|
|
6709
|
+
return applyReplacementEvent(
|
|
6710
|
+
state,
|
|
6711
|
+
entry,
|
|
6712
|
+
nextVersion
|
|
6713
|
+
);
|
|
6714
|
+
}
|
|
6715
|
+
|
|
6247
6716
|
// src/mcp/jobs/run-as-job.ts
|
|
6248
6717
|
var ASYNC_JOB_BODY_GUARD_CLASS = "async-job-body";
|
|
6249
6718
|
var ASYNC_JOB_BODY_NEAR_TIMEOUT_THRESHOLD = 0.5;
|
|
6250
6719
|
var ASYNC_JOB_BODY_RUNAWAY_MESSAGE = "runaway guard exceeded";
|
|
6251
|
-
function makeAsyncBodyGuard(jobId, toolName, guardMs,
|
|
6720
|
+
function makeAsyncBodyGuard(jobId, toolName, guardMs, logger11) {
|
|
6252
6721
|
let guardTimer;
|
|
6253
6722
|
let warnTimer;
|
|
6254
6723
|
const expired = new Promise((_resolve, reject) => {
|
|
6255
6724
|
warnTimer = setTimeout(
|
|
6256
6725
|
() => {
|
|
6257
|
-
|
|
6726
|
+
logger11?.warn(`Async ${toolName} job approaching runaway guard`, {
|
|
6258
6727
|
jobId,
|
|
6259
6728
|
guardMs,
|
|
6260
6729
|
thresholdFraction: ASYNC_JOB_BODY_NEAR_TIMEOUT_THRESHOLD
|
|
@@ -6373,10 +6842,11 @@ async function runJobInBackground(jobId, params) {
|
|
|
6373
6842
|
const guard = makeAsyncBodyGuard(jobId, params.toolName, guardMs, params.logger);
|
|
6374
6843
|
const controller = registerJobAbort(jobId);
|
|
6375
6844
|
try {
|
|
6376
|
-
const
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6845
|
+
const body = withAsyncTaskStateDispatch(
|
|
6846
|
+
jobId,
|
|
6847
|
+
() => params.run(jobId, params.input, controller.signal)
|
|
6848
|
+
);
|
|
6849
|
+
const result = await Promise.race([body, guard.expired]);
|
|
6380
6850
|
const failure = detectFailureShapedResult(result);
|
|
6381
6851
|
if (failure !== null && params.allowFailureShapedResult === void 0) {
|
|
6382
6852
|
writeJobFailed(jobId, params.toolName, describeFailureShape(failure), params.producerVersion);
|
|
@@ -6522,13 +6992,13 @@ async function processVotesThroughEngine(engineVotes, proposal, algorithm) {
|
|
|
6522
6992
|
});
|
|
6523
6993
|
return resultRes.value;
|
|
6524
6994
|
}
|
|
6525
|
-
function runHigherOrderVoting(strategy, voteMap,
|
|
6995
|
+
function runHigherOrderVoting(strategy, voteMap, logger11, context) {
|
|
6526
6996
|
if (!isHigherOrderStrategy(strategy)) return void 0;
|
|
6527
6997
|
const hovStrategy = new HigherOrderVotingStrategy();
|
|
6528
6998
|
const tracker = getOrCreateCorrelationTracker();
|
|
6529
6999
|
tracker.setCurrentModelPins?.(context.modelPins);
|
|
6530
7000
|
const result = hovStrategy.aggregate(voteMap, tracker);
|
|
6531
|
-
|
|
7001
|
+
logger11.info("Higher-Order Voting complete", {
|
|
6532
7002
|
method: result.method,
|
|
6533
7003
|
decision: result.decision,
|
|
6534
7004
|
posteriorApproval: result.posteriorApproval.toFixed(3)
|
|
@@ -6544,10 +7014,10 @@ function buildCorrelationContext(votes) {
|
|
|
6544
7014
|
}
|
|
6545
7015
|
return { modelPins, observedModels };
|
|
6546
7016
|
}
|
|
6547
|
-
function recordVotesToTracker(votes, outcome,
|
|
7017
|
+
function recordVotesToTracker(votes, outcome, logger11) {
|
|
6548
7018
|
const llmVotes = votes.filter((v) => v.source === "llm");
|
|
6549
7019
|
if (llmVotes.length < votes.length) {
|
|
6550
|
-
|
|
7020
|
+
logger11.warn("Recording only LLM votes to correlation tracker; excluding non-LLM votes", {
|
|
6551
7021
|
recorded: llmVotes.length,
|
|
6552
7022
|
excluded: votes.length - llmVotes.length
|
|
6553
7023
|
});
|
|
@@ -6559,16 +7029,16 @@ function recordVotesToTracker(votes, outcome, logger10) {
|
|
|
6559
7029
|
const tracker = getOrCreateCorrelationTracker();
|
|
6560
7030
|
const id = `consensus-${String(getTimeProvider().now())}-${getRandomProvider().random().toString(36).slice(2, 9)}`;
|
|
6561
7031
|
tracker.recordProposalVotes(id, llmVoteMap, outcome, context);
|
|
6562
|
-
|
|
7032
|
+
logger11.debug("Recorded votes to tracker", { proposalId: id, outcome });
|
|
6563
7033
|
try {
|
|
6564
7034
|
const persisted = createPersistedProposal(id, llmVoteMap, outcome, context);
|
|
6565
7035
|
const saveResult = saveCorrelationData([persisted]);
|
|
6566
7036
|
if (!saveResult.ok) {
|
|
6567
|
-
|
|
7037
|
+
logger11.warn("Failed to persist correlation data", { error: saveResult.error.message });
|
|
6568
7038
|
}
|
|
6569
7039
|
} catch (error) {
|
|
6570
7040
|
const message = getErrorMessage(error);
|
|
6571
|
-
|
|
7041
|
+
logger11.warn("Error persisting correlation data", { error: message });
|
|
6572
7042
|
}
|
|
6573
7043
|
}
|
|
6574
7044
|
async function processVotesWithCascade(engineVotes, opts) {
|
|
@@ -6664,37 +7134,37 @@ function buildPolicyShortCircuitResult(args) {
|
|
|
6664
7134
|
policyReason: args.reason
|
|
6665
7135
|
};
|
|
6666
7136
|
}
|
|
6667
|
-
async function maybeEscalateContrarian(input, outcome, ctx,
|
|
7137
|
+
async function maybeEscalateContrarian(input, outcome, ctx, logger11, opts) {
|
|
6668
7138
|
if (!input.quickMode || outcome !== "approved" || input.simulateVotes) return {};
|
|
6669
7139
|
if (shouldEscalateLowPosterior(ctx.strategy, outcome, input.quickMode, ctx.posteriorApproval)) {
|
|
6670
|
-
|
|
7140
|
+
logger11.warn("Posterior-confidence escalation: re-running with full vote (#3174)", {
|
|
6671
7141
|
strategy: ctx.strategy,
|
|
6672
7142
|
posteriorApproval: ctx.posteriorApproval
|
|
6673
7143
|
});
|
|
6674
|
-
return { escalated: await executeVoting({ ...input, quickMode: false },
|
|
7144
|
+
return { escalated: await executeVoting({ ...input, quickMode: false }, logger11, opts) };
|
|
6675
7145
|
}
|
|
6676
|
-
const escalation = await runContrarianCheck(input.proposal,
|
|
7146
|
+
const escalation = await runContrarianCheck(input.proposal, logger11);
|
|
6677
7147
|
if (escalation.errored && input.errorPolicy === "absolute_quorum") {
|
|
6678
|
-
|
|
7148
|
+
logger11.warn("Contrarian check errored under absolute_quorum \u2014 degrading to no_quorum (#4132)");
|
|
6679
7149
|
return {
|
|
6680
7150
|
degradeReason: "no_quorum: re-run \u2014 contrarian check errored (absolute_quorum quick-mode)"
|
|
6681
7151
|
};
|
|
6682
7152
|
}
|
|
6683
7153
|
if (!escalation.shouldEscalate) return {};
|
|
6684
|
-
|
|
7154
|
+
logger11.warn("Contrarian escalation: re-running with full vote", {
|
|
6685
7155
|
reason: escalation.reason,
|
|
6686
7156
|
confidence: escalation.confidence
|
|
6687
7157
|
});
|
|
6688
|
-
return { escalated: await executeVoting({ ...input, quickMode: false },
|
|
7158
|
+
return { escalated: await executeVoting({ ...input, quickMode: false }, logger11, opts) };
|
|
6689
7159
|
}
|
|
6690
|
-
async function executeVoting(input,
|
|
6691
|
-
const result = await executeVotingInner(input,
|
|
7160
|
+
async function executeVoting(input, logger11, opts) {
|
|
7161
|
+
const result = await executeVotingInner(input, logger11, opts ?? {});
|
|
6692
7162
|
const correlationAlreadyRecorded = result.decision !== void 0;
|
|
6693
7163
|
const errorCount = result.votes.filter((v) => v.source === "error").length;
|
|
6694
7164
|
applyOptionGate(input, result);
|
|
6695
7165
|
result.decision = resolveVoteDecision(input, result, errorCount).decision;
|
|
6696
7166
|
if (!correlationAlreadyRecorded && (result.decision === "approved" || result.decision === "rejected")) {
|
|
6697
|
-
recordVotesToTracker(result.votes, result.decision,
|
|
7167
|
+
recordVotesToTracker(result.votes, result.decision, logger11);
|
|
6698
7168
|
}
|
|
6699
7169
|
return result;
|
|
6700
7170
|
}
|
|
@@ -6714,13 +7184,13 @@ function applyOptionGate(input, result) {
|
|
|
6714
7184
|
result.result.outcome = "rejected";
|
|
6715
7185
|
result.optionGate = outcome.reason !== void 0 ? { ...outcome.verdict, reason: outcome.reason } : outcome.verdict;
|
|
6716
7186
|
}
|
|
6717
|
-
async function executeVotingInner(input,
|
|
7187
|
+
async function executeVotingInner(input, logger11, opts) {
|
|
6718
7188
|
const strategy = resolveStrategy(input);
|
|
6719
7189
|
const algorithm = strategyToAlgorithm(strategy);
|
|
6720
7190
|
const roles = getVoterRoles(input.quickMode);
|
|
6721
7191
|
const startTime = getTimeProvider().now();
|
|
6722
7192
|
const errorPolicy = input.errorPolicy ?? getDefaultErrorPolicy(strategy);
|
|
6723
|
-
|
|
7193
|
+
logger11.info("Starting consensus vote", {
|
|
6724
7194
|
strategy,
|
|
6725
7195
|
algorithm,
|
|
6726
7196
|
roleCount: roles.length,
|
|
@@ -6746,7 +7216,7 @@ async function executeVotingInner(input, logger10, opts) {
|
|
|
6746
7216
|
errorPolicy,
|
|
6747
7217
|
reason: policyDecision.reason ?? "error policy short-circuit",
|
|
6748
7218
|
startTime,
|
|
6749
|
-
logger:
|
|
7219
|
+
logger: logger11
|
|
6750
7220
|
});
|
|
6751
7221
|
}
|
|
6752
7222
|
const { engineResult, higherOrderResult, outcome, cascaded } = await processVotesWithCascade(
|
|
@@ -6756,14 +7226,14 @@ async function executeVotingInner(input, logger10, opts) {
|
|
|
6756
7226
|
proposal: input.proposal,
|
|
6757
7227
|
algorithm,
|
|
6758
7228
|
strategy,
|
|
6759
|
-
log:
|
|
7229
|
+
log: logger11
|
|
6760
7230
|
}
|
|
6761
7231
|
);
|
|
6762
7232
|
const escalation = await maybeEscalateContrarian(
|
|
6763
7233
|
input,
|
|
6764
7234
|
outcome,
|
|
6765
7235
|
{ strategy, posteriorApproval: higherOrderResult?.posteriorApproval },
|
|
6766
|
-
|
|
7236
|
+
logger11,
|
|
6767
7237
|
opts
|
|
6768
7238
|
);
|
|
6769
7239
|
if (escalation.escalated !== void 0) return escalation.escalated;
|
|
@@ -6778,7 +7248,7 @@ async function executeVotingInner(input, logger10, opts) {
|
|
|
6778
7248
|
outcome,
|
|
6779
7249
|
cascaded,
|
|
6780
7250
|
startTime,
|
|
6781
|
-
logger:
|
|
7251
|
+
logger: logger11
|
|
6782
7252
|
});
|
|
6783
7253
|
return applyContrarianDegrade(finalized, escalation.degradeReason);
|
|
6784
7254
|
}
|
|
@@ -6786,8 +7256,8 @@ function applyContrarianDegrade(result, degradeReason) {
|
|
|
6786
7256
|
if (degradeReason === void 0 || result.policyReason !== void 0) return result;
|
|
6787
7257
|
return { ...result, policyReason: degradeReason };
|
|
6788
7258
|
}
|
|
6789
|
-
async function runConsensusForGoal(goal,
|
|
6790
|
-
return executeVoting(ConsensusVoteInputSchema.parse({ proposal: goal }),
|
|
7259
|
+
async function runConsensusForGoal(goal, logger11 = createLogger({ tool: "consensus_vote" }), gatewayAdapters) {
|
|
7260
|
+
return executeVoting(ConsensusVoteInputSchema.parse({ proposal: goal }), logger11, {
|
|
6791
7261
|
...gatewayAdapters !== void 0 && { gatewayAdapters }
|
|
6792
7262
|
});
|
|
6793
7263
|
}
|
|
@@ -6814,7 +7284,7 @@ function finalizeVotingResult(args) {
|
|
|
6814
7284
|
if (args.higherOrderResult !== void 0) result.higherOrderResult = args.higherOrderResult;
|
|
6815
7285
|
return result;
|
|
6816
7286
|
}
|
|
6817
|
-
function recordVoteSideEffects(proposal, strategy, result,
|
|
7287
|
+
function recordVoteSideEffects(proposal, strategy, result, logger11, ratifies) {
|
|
6818
7288
|
const decisionId = `consensus-${String(getTimeProvider().now())}-${randomUUID2().slice(0, 8)}`;
|
|
6819
7289
|
const voteRecord = recordAuthenticVote({
|
|
6820
7290
|
proposal,
|
|
@@ -6837,7 +7307,7 @@ function recordVoteSideEffects(proposal, strategy, result, logger10, ratifies) {
|
|
|
6837
7307
|
try {
|
|
6838
7308
|
costSummary = recordDecisionCost({ decisionId, gate: "consensus_vote", votes: result.votes });
|
|
6839
7309
|
} catch (costError) {
|
|
6840
|
-
|
|
7310
|
+
logger11.warn("Per-decision cost rollup failed (non-fatal)", {
|
|
6841
7311
|
error: getErrorMessage(costError)
|
|
6842
7312
|
});
|
|
6843
7313
|
costSummary = void 0;
|
|
@@ -6845,16 +7315,16 @@ function recordVoteSideEffects(proposal, strategy, result, logger10, ratifies) {
|
|
|
6845
7315
|
return { costSummary, voteRecord };
|
|
6846
7316
|
}
|
|
6847
7317
|
async function handleConsensusVote(deps, args, signal) {
|
|
6848
|
-
const
|
|
7318
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
6849
7319
|
try {
|
|
6850
|
-
const result = await executeVoting(args,
|
|
7320
|
+
const result = await executeVoting(args, logger11, {
|
|
6851
7321
|
...deps.gatewayAdapters !== void 0 && { gatewayAdapters: deps.gatewayAdapters },
|
|
6852
7322
|
signal
|
|
6853
7323
|
});
|
|
6854
7324
|
const errorVotes = result.votes.filter((v) => v.source === "error");
|
|
6855
7325
|
if (errorVotes.length === result.votes.length && result.votes.length > 0) {
|
|
6856
7326
|
const failures = errorVotes.map((v) => `${v.role}: ${v.error ?? "unknown error"}`).join("; ");
|
|
6857
|
-
|
|
7327
|
+
logger11.warn("All voters failed", { failureCount: errorVotes.length, failures });
|
|
6858
7328
|
recordVoteError(args.proposal, `All ${String(errorVotes.length)} voters failed: ${failures}`);
|
|
6859
7329
|
return {
|
|
6860
7330
|
ok: false,
|
|
@@ -6876,15 +7346,15 @@ async function handleConsensusVote(deps, args, signal) {
|
|
|
6876
7346
|
args.proposal,
|
|
6877
7347
|
result.strategy,
|
|
6878
7348
|
result,
|
|
6879
|
-
|
|
7349
|
+
logger11,
|
|
6880
7350
|
args.ratifies
|
|
6881
7351
|
);
|
|
6882
|
-
emitVoteRejectedSignal(result.result, getPipelineEventBus(),
|
|
7352
|
+
emitVoteRejectedSignal(result.result, getPipelineEventBus(), logger11);
|
|
6883
7353
|
return { ok: true, value: buildResponse(args, result, costSummary, voteRecord) };
|
|
6884
7354
|
} catch (error) {
|
|
6885
7355
|
const message = getErrorMessage(error);
|
|
6886
7356
|
const cause = error instanceof Error ? error : new Error(message);
|
|
6887
|
-
|
|
7357
|
+
logger11.error("Consensus vote failed", cause);
|
|
6888
7358
|
recordVoteError(args.proposal, message);
|
|
6889
7359
|
return { ok: false, error: `Voting failed: ${message}` };
|
|
6890
7360
|
}
|
|
@@ -7002,35 +7472,35 @@ var ASYNC_ENVELOPES = {
|
|
|
7002
7472
|
};
|
|
7003
7473
|
var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
7004
7474
|
/** Async-dispatch envelope discriminator — absent on a completed vote. */
|
|
7005
|
-
status:
|
|
7006
|
-
jobId:
|
|
7007
|
-
pollTool:
|
|
7008
|
-
note:
|
|
7009
|
-
retryAfterMs:
|
|
7010
|
-
proposal:
|
|
7475
|
+
status: z11.enum(["pending", "busy", "replay"]).optional(),
|
|
7476
|
+
jobId: z11.string().max(200).optional(),
|
|
7477
|
+
pollTool: z11.literal("get_job_result").optional(),
|
|
7478
|
+
note: z11.string().max(500).optional(),
|
|
7479
|
+
retryAfterMs: z11.number().optional(),
|
|
7480
|
+
proposal: z11.string().optional(),
|
|
7011
7481
|
strategy: VotingStrategySchema.optional(),
|
|
7012
7482
|
// Reuses the canonical VoteDecisionStatusSchema (single source) — a hand-listed
|
|
7013
7483
|
// subset here (was ['approved','rejected','no_quorum']) omitted the reachable
|
|
7014
7484
|
// 'timeout'/'pending' outcomes and made strict clients reject those votes (#4032).
|
|
7015
7485
|
decision: VoteDecisionStatusSchema.optional(),
|
|
7016
|
-
approvalPercentage:
|
|
7017
|
-
voteCounts:
|
|
7018
|
-
approve:
|
|
7019
|
-
reject:
|
|
7020
|
-
abstain:
|
|
7021
|
-
error:
|
|
7486
|
+
approvalPercentage: z11.number().optional(),
|
|
7487
|
+
voteCounts: z11.object({
|
|
7488
|
+
approve: z11.number(),
|
|
7489
|
+
reject: z11.number(),
|
|
7490
|
+
abstain: z11.number(),
|
|
7491
|
+
error: z11.number()
|
|
7022
7492
|
}).optional(),
|
|
7023
|
-
votes:
|
|
7024
|
-
|
|
7025
|
-
role:
|
|
7026
|
-
decision:
|
|
7027
|
-
confidence:
|
|
7028
|
-
reasoning:
|
|
7029
|
-
simulated:
|
|
7030
|
-
error:
|
|
7031
|
-
modelUsed:
|
|
7032
|
-
rejectionCategories:
|
|
7033
|
-
|
|
7493
|
+
votes: z11.array(
|
|
7494
|
+
z11.object({
|
|
7495
|
+
role: z11.string().max(100),
|
|
7496
|
+
decision: z11.enum(["approve", "reject", "abstain"]),
|
|
7497
|
+
confidence: z11.number(),
|
|
7498
|
+
reasoning: z11.string().max(4e3),
|
|
7499
|
+
simulated: z11.boolean(),
|
|
7500
|
+
error: z11.boolean(),
|
|
7501
|
+
modelUsed: z11.string().max(100).optional(),
|
|
7502
|
+
rejectionCategories: z11.array(
|
|
7503
|
+
z11.enum([
|
|
7034
7504
|
"YAGNI",
|
|
7035
7505
|
"DRY_VIOLATION",
|
|
7036
7506
|
"OVER_ENGINEERING",
|
|
@@ -7043,33 +7513,33 @@ var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
|
7043
7513
|
})
|
|
7044
7514
|
).optional(),
|
|
7045
7515
|
threshold: VoteThresholdSchema.optional(),
|
|
7046
|
-
durationMs:
|
|
7047
|
-
simulateVotes:
|
|
7048
|
-
higherOrderMetadata:
|
|
7049
|
-
posteriorApproval:
|
|
7050
|
-
posteriorRejection:
|
|
7051
|
-
effectiveVoteCount:
|
|
7052
|
-
method:
|
|
7053
|
-
usedCorrelationData:
|
|
7054
|
-
improvementOverBaseline:
|
|
7055
|
-
downweightedAgents:
|
|
7056
|
-
reasoning:
|
|
7516
|
+
durationMs: z11.number().optional(),
|
|
7517
|
+
simulateVotes: z11.boolean().optional(),
|
|
7518
|
+
higherOrderMetadata: z11.object({
|
|
7519
|
+
posteriorApproval: z11.number(),
|
|
7520
|
+
posteriorRejection: z11.number(),
|
|
7521
|
+
effectiveVoteCount: z11.number(),
|
|
7522
|
+
method: z11.enum(["ow", "isp", "simple"]),
|
|
7523
|
+
usedCorrelationData: z11.boolean(),
|
|
7524
|
+
improvementOverBaseline: z11.number(),
|
|
7525
|
+
downweightedAgents: z11.array(z11.string().max(100)).max(10),
|
|
7526
|
+
reasoning: z11.string().max(2e3)
|
|
7057
7527
|
}).optional(),
|
|
7058
7528
|
// #3124: explains a `rejected` decision that coexists with a high
|
|
7059
7529
|
// approvalPercentage (an error-policy short-circuit, e.g. fail_closed).
|
|
7060
|
-
policyReason:
|
|
7530
|
+
policyReason: z11.string().max(200).optional(),
|
|
7061
7531
|
// #3991: whether the authentic vote record was persisted at vote time. Post-
|
|
7062
7532
|
// #3991 it routes through nexusDataPath under governance/, so true is the
|
|
7063
7533
|
// normal case; false means all-simulated (skipped by design) or write-failed
|
|
7064
7534
|
// (data dir unwritable) — voteRecordNote carries the actionable reason. Makes
|
|
7065
7535
|
// a previously WARN-only skip visible to MCP callers.
|
|
7066
|
-
voteRecordPersisted:
|
|
7067
|
-
voteRecordNote:
|
|
7536
|
+
voteRecordPersisted: z11.boolean().optional(),
|
|
7537
|
+
voteRecordNote: z11.string().max(500).optional(),
|
|
7068
7538
|
// #3587: set when the panel DEGRADED (some voters errored), so the decision
|
|
7069
7539
|
// rests on fewer voters than requested. Part of the response since #3587 but
|
|
7070
7540
|
// omitted from this schema until #4032 — its absence made a strict MCP client
|
|
7071
7541
|
// reject any degraded-panel vote (`-32602 additional properties`).
|
|
7072
|
-
panelWarning:
|
|
7542
|
+
panelWarning: z11.string().optional(),
|
|
7073
7543
|
// #3855: per-decision cost rollup. Same omission as panelWarning (#4032) — it
|
|
7074
7544
|
// is present on the response whenever cost recording succeeds (common in `api`
|
|
7075
7545
|
// billing mode). Shared schema lives with the type (single source of truth).
|
|
@@ -7077,35 +7547,35 @@ var CONSENSUS_VOTE_OUTPUT_SCHEMA = {
|
|
|
7077
7547
|
// #4472: declared-option outcome. Present whenever the proposal declared
|
|
7078
7548
|
// `options`. `unattributedApprovals` is part of the contract, not telemetry:
|
|
7079
7549
|
// without it a caller cannot tell a real split from unreadable responses.
|
|
7080
|
-
optionOutcome:
|
|
7081
|
-
tally:
|
|
7082
|
-
leadingOption:
|
|
7083
|
-
leadingShare:
|
|
7084
|
-
approverCount:
|
|
7085
|
-
selectedCount:
|
|
7086
|
-
unattributedApprovals:
|
|
7087
|
-
thresholdMet:
|
|
7550
|
+
optionOutcome: z11.object({
|
|
7551
|
+
tally: z11.array(z11.object({ option: z11.string(), count: z11.number().int().nonnegative() })),
|
|
7552
|
+
leadingOption: z11.string().optional(),
|
|
7553
|
+
leadingShare: z11.number().min(0).max(1),
|
|
7554
|
+
approverCount: z11.number().int().nonnegative(),
|
|
7555
|
+
selectedCount: z11.number().int().nonnegative(),
|
|
7556
|
+
unattributedApprovals: z11.number().int().nonnegative(),
|
|
7557
|
+
thresholdMet: z11.boolean(),
|
|
7088
7558
|
// #4529: present only on a veto. Declared here because a strict MCP
|
|
7089
7559
|
// client rejects any property the advertised schema omits — the same
|
|
7090
7560
|
// drift that made degraded-panel votes unusable before #4032.
|
|
7091
|
-
vetoReason:
|
|
7561
|
+
vetoReason: z11.string().max(400).optional()
|
|
7092
7562
|
}).optional()
|
|
7093
7563
|
};
|
|
7094
7564
|
var CONSENSUS_VOTE_TOOL_SCHEMA = ConsensusVoteInputSchema.shape;
|
|
7095
7565
|
var CONSENSUS_VOTE_DESCRIPTION = "Execute multi-model consensus voting on a proposal. Uses 7 roles by default (architect, security, devex, ai_ml, pm, catfish, scope_steward) or 3 with quickMode (architect, security, scope_steward), voting with configurable strategies. higher_order does NOT decide the verdict by correlation-aware aggregation (#4701): approve/reject is a simple tally of the panel, so correlated voters each carry full independent weight. The Bayesian correlation analysis is computed and feeds contrarian escalation only. Choose it for the escalation behaviour, not for a weighted verdict. Supports async mode (mode: 'async') \u2014 returns a jobId to poll via get_job_result. Pass ratifies=<subject> to bind an authority-ladder ratification vote into its authentic record. If your proposal asks voters to choose among named alternatives, you MUST pass them in `options` (#4472) \u2014 the threshold is then measured over WHICH option won, and the record carries the per-option tally plus selection coverage. WITHOUT `options` the tally is 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).";
|
|
7096
7566
|
function registerConsensusVoteTool(server, deps) {
|
|
7097
|
-
const
|
|
7567
|
+
const logger11 = deps.logger ?? createLogger({ tool: "consensus_vote" });
|
|
7098
7568
|
const notifier = deps.notifier ?? createMcpNotifier(server);
|
|
7099
7569
|
const depsWithNotifier = { ...deps, notifier };
|
|
7100
7570
|
const secureHandler = createSecureHandler(createConsensusVoteHandler(depsWithNotifier), {
|
|
7101
7571
|
toolName: "consensus_vote",
|
|
7102
7572
|
rateLimiter: deps.rateLimiter,
|
|
7103
|
-
logger:
|
|
7573
|
+
logger: logger11
|
|
7104
7574
|
});
|
|
7105
7575
|
const timeoutMs = getToolTimeout("consensus_vote", deps.security);
|
|
7106
7576
|
const wrappedHandler = wrapToolWithTimeout("consensus_vote", secureHandler, {
|
|
7107
7577
|
timeoutMs,
|
|
7108
|
-
logger:
|
|
7578
|
+
logger: logger11
|
|
7109
7579
|
});
|
|
7110
7580
|
server.registerTool(
|
|
7111
7581
|
"consensus_vote",
|
|
@@ -7115,9 +7585,9 @@ function registerConsensusVoteTool(server, deps) {
|
|
|
7115
7585
|
outputSchema: CONSENSUS_VOTE_OUTPUT_SCHEMA,
|
|
7116
7586
|
annotations: getToolAnnotations("consensus_vote")
|
|
7117
7587
|
},
|
|
7118
|
-
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs,
|
|
7588
|
+
toSdkCallbackWithBudgetCheck(wrappedHandler, "consensus_vote", timeoutMs, logger11)
|
|
7119
7589
|
);
|
|
7120
|
-
|
|
7590
|
+
logger11.info("Registered consensus_vote tool with secure handler and timeout protection");
|
|
7121
7591
|
}
|
|
7122
7592
|
|
|
7123
7593
|
export {
|
|
@@ -7132,6 +7602,13 @@ export {
|
|
|
7132
7602
|
abortJob,
|
|
7133
7603
|
resolveIdempotency,
|
|
7134
7604
|
registerIdempotentJob,
|
|
7605
|
+
listTaskStateIds,
|
|
7606
|
+
initTaskState,
|
|
7607
|
+
appendBlocker,
|
|
7608
|
+
updateStage,
|
|
7609
|
+
appendResult,
|
|
7610
|
+
appendCancellation,
|
|
7611
|
+
readTaskState,
|
|
7135
7612
|
defaultPendingEnvelope,
|
|
7136
7613
|
defaultBusyEnvelope,
|
|
7137
7614
|
defaultReplayEnvelope,
|
|
@@ -7205,4 +7682,4 @@ export {
|
|
|
7205
7682
|
CONSENSUS_VOTE_TOOL_SCHEMA,
|
|
7206
7683
|
registerConsensusVoteTool
|
|
7207
7684
|
};
|
|
7208
|
-
//# sourceMappingURL=chunk-
|
|
7685
|
+
//# sourceMappingURL=chunk-DEVL62JD.js.map
|