@jigyasudham/veto 1.2.12 → 1.2.15
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/README.md +42 -5
- package/dist/memory/config.d.ts +11 -0
- package/dist/memory/config.d.ts.map +1 -0
- package/dist/memory/config.js +40 -0
- package/dist/memory/config.js.map +1 -0
- package/dist/memory/local.d.ts +22 -0
- package/dist/memory/local.d.ts.map +1 -1
- package/dist/memory/local.js +39 -0
- package/dist/memory/local.js.map +1 -1
- package/dist/memory/schema.d.ts +1 -1
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +10 -0
- package/dist/memory/schema.js.map +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -2
- package/dist/router/index.js.map +1 -1
- package/dist/router/rate-monitor.d.ts +3 -1
- package/dist/router/rate-monitor.d.ts.map +1 -1
- package/dist/router/rate-monitor.js +35 -16
- package/dist/router/rate-monitor.js.map +1 -1
- package/dist/server.js +213 -45
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Veto MCP Server — 45 tools,
|
|
2
|
+
// Veto MCP Server — 45 tools, 19 phases complete, auto-learning router
|
|
3
3
|
// Suppress node:sqlite experimental warning — it would corrupt the MCP stdio protocol
|
|
4
4
|
process.removeAllListeners('warning');
|
|
5
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import { buildContextString } from './context/reader.js';
|
|
9
|
-
import { saveSession, restoreSession, listSessions, closeSession, getDbPath, saveCouncilOutcome, storeKnowledge, searchKnowledge, deleteKnowledge, updateProjectMap, getProjectMap, upsertPattern, getPatterns, getContextStatus, fetchAndCacheDocs, saveTaskPlan, getUsageStatus, getAuditLog, getHealthStats, CONTEXT_WINDOWS, } from './memory/local.js';
|
|
9
|
+
import { saveSession, restoreSession, listSessions, closeSession, getDbPath, saveCouncilOutcome, storeKnowledge, searchKnowledge, deleteKnowledge, updateProjectMap, getProjectMap, upsertPattern, getPatterns, getContextStatus, fetchAndCacheDocs, saveTaskPlan, getUsageStatus, getAuditLog, getHealthStats, CONTEXT_WINDOWS, logUsage, getUsageLogs, } from './memory/local.js';
|
|
10
10
|
import { exportMemory, importMemory } from './memory/sync.js';
|
|
11
11
|
import { runDebate } from './council/index.js';
|
|
12
|
-
import { routeTask, getRateStatus, recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights, getRecommendedAgent } from './router/index.js';
|
|
12
|
+
import { routeTask, getRateStatus, trackTokens, recordOutcome, getLearningStats, getLearnedThresholds, applyLearnedThresholds, getAgentPerformanceStats, getTaskTypeBreakdown, getCouncilInsights, getRecommendedAgent } from './router/index.js';
|
|
13
|
+
import { getConfig, setConfig } from './memory/config.js';
|
|
13
14
|
import { executeParallel, executeOne } from './agents/executor.js';
|
|
14
15
|
import { handoff, continueSession, getPlatformSetup } from './adapters/index.js';
|
|
15
16
|
import { startWatch, pollWatch, stopWatch } from './watcher/index.js';
|
|
@@ -63,9 +64,62 @@ const server = new Server({ name: 'veto', version: VERSION }, {
|
|
|
63
64
|
prompts: {},
|
|
64
65
|
},
|
|
65
66
|
});
|
|
67
|
+
// ─── Tool Risk Annotations (#21) ─────────────────────────────────────────────
|
|
68
|
+
// readOnlyHint: tool makes no writes. destructiveHint: writes are irreversible.
|
|
69
|
+
// openWorldHint: tool reaches outside the local DB (network, filesystem, processes).
|
|
70
|
+
const TOOL_ANNOTATIONS = {
|
|
71
|
+
// read-only — query/inspect only
|
|
72
|
+
veto_status: { readOnlyHint: true },
|
|
73
|
+
veto_autosave_status: { readOnlyHint: true },
|
|
74
|
+
veto_sessions_list: { readOnlyHint: true },
|
|
75
|
+
veto_rate_status: { readOnlyHint: true },
|
|
76
|
+
veto_route_task: { readOnlyHint: true },
|
|
77
|
+
veto_agent_plan: { readOnlyHint: true },
|
|
78
|
+
veto_code_review: { readOnlyHint: true },
|
|
79
|
+
veto_diff_review: { readOnlyHint: true },
|
|
80
|
+
veto_security_scan: { readOnlyHint: true },
|
|
81
|
+
veto_secrets_scan: { readOnlyHint: true },
|
|
82
|
+
veto_project_map_get: { readOnlyHint: true },
|
|
83
|
+
veto_patterns_list: { readOnlyHint: true },
|
|
84
|
+
veto_learning_stats: { readOnlyHint: true },
|
|
85
|
+
veto_watch_poll: { readOnlyHint: true },
|
|
86
|
+
veto_plugins: { readOnlyHint: true },
|
|
87
|
+
veto_context_status: { readOnlyHint: true },
|
|
88
|
+
veto_audit_log: { readOnlyHint: true },
|
|
89
|
+
veto_health: { readOnlyHint: true },
|
|
90
|
+
veto_discover: { readOnlyHint: true },
|
|
91
|
+
veto_summarize: { readOnlyHint: true },
|
|
92
|
+
veto_explain: { readOnlyHint: true },
|
|
93
|
+
// read-only + open world (external network)
|
|
94
|
+
veto_docs_fetch: { readOnlyHint: true, openWorldHint: true },
|
|
95
|
+
veto_pr_review: { readOnlyHint: true, openWorldHint: true },
|
|
96
|
+
// reversible writes (local DB — can be deleted/reset)
|
|
97
|
+
veto_council_debate: { readOnlyHint: false, destructiveHint: false },
|
|
98
|
+
veto_execute_parallel: { readOnlyHint: false, destructiveHint: false },
|
|
99
|
+
veto_session_save: { readOnlyHint: false, destructiveHint: false },
|
|
100
|
+
veto_session_restore: { readOnlyHint: false, destructiveHint: false },
|
|
101
|
+
veto_memory_store: { readOnlyHint: false, destructiveHint: false },
|
|
102
|
+
veto_project_map_update: { readOnlyHint: false, destructiveHint: false },
|
|
103
|
+
veto_pattern_store: { readOnlyHint: false, destructiveHint: false },
|
|
104
|
+
veto_memory_export: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
105
|
+
veto_record_outcome: { readOnlyHint: false, destructiveHint: false },
|
|
106
|
+
veto_learning_apply: { readOnlyHint: false, destructiveHint: false },
|
|
107
|
+
veto_handoff: { readOnlyHint: false, destructiveHint: false },
|
|
108
|
+
veto_continue: { readOnlyHint: false, destructiveHint: false },
|
|
109
|
+
veto_task_parse: { readOnlyHint: false, destructiveHint: false },
|
|
110
|
+
veto_watch: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
111
|
+
veto_watch_stop: { readOnlyHint: false, destructiveHint: false },
|
|
112
|
+
veto_workflow: { readOnlyHint: false, destructiveHint: false },
|
|
113
|
+
veto_ci_gate: { readOnlyHint: false, destructiveHint: false },
|
|
114
|
+
veto_usage_status: { readOnlyHint: false, destructiveHint: false },
|
|
115
|
+
// destructive — permanent deletes or config overwrites
|
|
116
|
+
veto_memory_delete: { readOnlyHint: false, destructiveHint: true },
|
|
117
|
+
veto_memory_import: { readOnlyHint: false, destructiveHint: true },
|
|
118
|
+
veto_platform_setup: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
119
|
+
};
|
|
66
120
|
// ─── Tool Definitions ─────────────────────────────────────────────────────────
|
|
67
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
68
|
-
tools
|
|
121
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
122
|
+
const tools = [
|
|
69
123
|
{
|
|
70
124
|
name: 'veto_status',
|
|
71
125
|
description: 'Returns Veto server status, version, and database info. Pass token_count to trigger auto-save if context usage crosses 70%.',
|
|
@@ -240,6 +294,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
240
294
|
type: 'string',
|
|
241
295
|
description: 'Optional: session ID to associate this council outcome with an active session.',
|
|
242
296
|
},
|
|
297
|
+
max_tokens: {
|
|
298
|
+
type: 'number',
|
|
299
|
+
description: 'Optional: token budget for this operation. Veto estimates output tokens and warns in the response if the estimate exceeds this limit. Logged to usage_log for tracking.',
|
|
300
|
+
},
|
|
243
301
|
},
|
|
244
302
|
required: ['task'],
|
|
245
303
|
},
|
|
@@ -333,6 +391,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
333
391
|
},
|
|
334
392
|
},
|
|
335
393
|
project_dir: { type: 'string', description: 'Optional: project directory applied to all tasks (per-task project_dir overrides this). Auto-injects codebase context.' },
|
|
394
|
+
max_tokens: {
|
|
395
|
+
type: 'number',
|
|
396
|
+
description: 'Optional: token budget for this parallel execution. Veto estimates combined output tokens and warns if the estimate exceeds this limit. Logged to usage_log.',
|
|
397
|
+
},
|
|
336
398
|
},
|
|
337
399
|
required: ['tasks'],
|
|
338
400
|
},
|
|
@@ -763,8 +825,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
763
825
|
required: [],
|
|
764
826
|
},
|
|
765
827
|
},
|
|
766
|
-
]
|
|
767
|
-
}));
|
|
828
|
+
];
|
|
829
|
+
return { tools: tools.map(t => ({ ...t, annotations: TOOL_ANNOTATIONS[t.name] ?? {} })) };
|
|
830
|
+
});
|
|
768
831
|
// ─── Shared Scan Utility ──────────────────────────────────────────────────────
|
|
769
832
|
async function runTripleScan(diff, context) {
|
|
770
833
|
const [reviewResult, secResult, secretsResult] = await Promise.all([
|
|
@@ -778,8 +841,18 @@ async function runTripleScan(diff, context) {
|
|
|
778
841
|
const hasWarnings = (reviewResult.analysis?.high_count ?? 0) > 0
|
|
779
842
|
|| (secResult.analysis?.high_count ?? 0) > 0;
|
|
780
843
|
const verdict = hasBlocking ? 'fail' : hasWarnings ? 'warn' : 'pass';
|
|
844
|
+
// auto-record per agent so learning accumulates from every scan (diff_review, ci_gate, pr_review)
|
|
845
|
+
autoRecord('scan', 'reviewer', reviewResult.analysis?.score ?? Math.round(reviewResult.output.confidence * 100));
|
|
846
|
+
autoRecord('scan', 'security-scanner', secResult.analysis?.score ?? Math.round(secResult.output.confidence * 100));
|
|
847
|
+
autoRecord('scan', 'secrets', (secretsResult.analysis?.findings?.length ?? 0) === 0 ? 100 : secretsResult.analysis?.score ?? Math.round(secretsResult.output.confidence * 100));
|
|
781
848
|
return { reviewResult, secResult, secretsResult, verdict };
|
|
782
849
|
}
|
|
850
|
+
// Auto-learning helper — records a learning_data row from any agent result.
|
|
851
|
+
// Keeps call sites to one line rather than repeating the tier/quality logic.
|
|
852
|
+
function autoRecord(taskType, agent, quality, complexity = 50) {
|
|
853
|
+
const tier = quality >= 80 ? 1 : quality >= 40 ? 2 : 3;
|
|
854
|
+
recordOutcome(taskType.slice(0, 50), complexity, tier, agent, quality);
|
|
855
|
+
}
|
|
783
856
|
// ─── Tool Handlers ────────────────────────────────────────────────────────────
|
|
784
857
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
785
858
|
const { name, arguments: args } = request.params;
|
|
@@ -787,6 +860,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
787
860
|
case 'veto_status': {
|
|
788
861
|
const statusTokenCount = typeof args?.token_count === 'number' ? args.token_count : null;
|
|
789
862
|
const statusPlatform = args?.platform ? String(args.platform) : 'claude';
|
|
863
|
+
if (statusTokenCount !== null && statusTokenCount > 0) {
|
|
864
|
+
trackTokens(statusPlatform, statusTokenCount);
|
|
865
|
+
}
|
|
790
866
|
const autoSaveResult = statusTokenCount !== null ? maybeAutoSave(statusTokenCount, statusPlatform) : null;
|
|
791
867
|
return {
|
|
792
868
|
content: [
|
|
@@ -948,6 +1024,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
948
1024
|
preferredPlatform: args?.preferred_platform ? String(args.preferred_platform) : 'claude',
|
|
949
1025
|
});
|
|
950
1026
|
const recommended_agent = getRecommendedAgent(routeTaskStr, fileExt);
|
|
1027
|
+
// #41: auto-record every routing so tier distribution stats are always populated
|
|
1028
|
+
recordOutcome(routeTaskStr.slice(0, 50), result.complexity.score, result.model.tier, 'router', 70);
|
|
951
1029
|
return {
|
|
952
1030
|
content: [{
|
|
953
1031
|
type: 'text',
|
|
@@ -980,8 +1058,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
980
1058
|
project_dir: args?.project_dir ? String(args.project_dir) : undefined,
|
|
981
1059
|
});
|
|
982
1060
|
const debateDuration = Date.now() - debateStart;
|
|
1061
|
+
const sessionId = args?.session_id ? String(args.session_id) : undefined;
|
|
983
1062
|
const outcomeId = saveCouncilOutcome({
|
|
984
|
-
session_id:
|
|
1063
|
+
session_id: sessionId,
|
|
985
1064
|
task,
|
|
986
1065
|
verdict: result.final_verdict,
|
|
987
1066
|
lead_dev: JSON.stringify(result.votes.lead_dev),
|
|
@@ -994,29 +1073,43 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
994
1073
|
recommended: result.recommended,
|
|
995
1074
|
duration_ms: debateDuration,
|
|
996
1075
|
});
|
|
1076
|
+
// #38: auto-record learning outcome from verdict — no manual veto_record_outcome needed
|
|
1077
|
+
{
|
|
1078
|
+
const qMap = { GREEN: 90, YELLOW: 60, RED: 20, DEADLOCK: 50 };
|
|
1079
|
+
const tMap = { GREEN: 1, YELLOW: 2, RED: 3, DEADLOCK: 2 };
|
|
1080
|
+
recordOutcome(task.slice(0, 50), 50, tMap[result.final_verdict] ?? 2, 'council', qMap[result.final_verdict] ?? 50);
|
|
1081
|
+
}
|
|
1082
|
+
const responsePayload = {
|
|
1083
|
+
outcome_id: outcomeId,
|
|
1084
|
+
final_verdict: result.final_verdict,
|
|
1085
|
+
block_reasons: result.block_reasons,
|
|
1086
|
+
warnings: result.warnings,
|
|
1087
|
+
recommended: result.recommended,
|
|
1088
|
+
debated_at: result.debated_at,
|
|
1089
|
+
votes: {
|
|
1090
|
+
lead_dev: result.votes.lead_dev.verdict,
|
|
1091
|
+
pm: result.votes.pm.verdict,
|
|
1092
|
+
architect: result.votes.architect.verdict,
|
|
1093
|
+
ux: result.votes.ux.verdict,
|
|
1094
|
+
devil: result.votes.devil.verdict,
|
|
1095
|
+
legal: result.votes.legal.verdict,
|
|
1096
|
+
security: result.votes.security.verdict,
|
|
1097
|
+
},
|
|
1098
|
+
};
|
|
1099
|
+
const fullText = result.formatted_output + '\n\n' + JSON.stringify(responsePayload, null, 2);
|
|
1100
|
+
if (typeof args?.max_tokens === 'number') {
|
|
1101
|
+
const { exceeded, estimated_tokens } = logUsage({
|
|
1102
|
+
tool_name: 'veto_council_debate',
|
|
1103
|
+
session_id: sessionId,
|
|
1104
|
+
max_tokens: args.max_tokens,
|
|
1105
|
+
output: fullText,
|
|
1106
|
+
});
|
|
1107
|
+
if (exceeded) {
|
|
1108
|
+
responsePayload.budget_warning = `Estimated output tokens (${estimated_tokens}) exceeded max_tokens budget (${args.max_tokens}).`;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
997
1111
|
return {
|
|
998
|
-
content: [
|
|
999
|
-
{
|
|
1000
|
-
type: 'text',
|
|
1001
|
-
text: result.formatted_output + '\n\n' + JSON.stringify({
|
|
1002
|
-
outcome_id: outcomeId,
|
|
1003
|
-
final_verdict: result.final_verdict,
|
|
1004
|
-
block_reasons: result.block_reasons,
|
|
1005
|
-
warnings: result.warnings,
|
|
1006
|
-
recommended: result.recommended,
|
|
1007
|
-
debated_at: result.debated_at,
|
|
1008
|
-
votes: {
|
|
1009
|
-
lead_dev: result.votes.lead_dev.verdict,
|
|
1010
|
-
pm: result.votes.pm.verdict,
|
|
1011
|
-
architect: result.votes.architect.verdict,
|
|
1012
|
-
ux: result.votes.ux.verdict,
|
|
1013
|
-
devil: result.votes.devil.verdict,
|
|
1014
|
-
legal: result.votes.legal.verdict,
|
|
1015
|
-
security: result.votes.security.verdict,
|
|
1016
|
-
},
|
|
1017
|
-
}, null, 2),
|
|
1018
|
-
},
|
|
1019
|
-
],
|
|
1112
|
+
content: [{ type: 'text', text: fullText }],
|
|
1020
1113
|
};
|
|
1021
1114
|
}
|
|
1022
1115
|
case 'veto_agent_plan': {
|
|
@@ -1035,6 +1128,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1035
1128
|
if (result.error) {
|
|
1036
1129
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
1037
1130
|
}
|
|
1131
|
+
autoRecord(task, agentType, Math.round(result.output.confidence * 100));
|
|
1038
1132
|
return { content: [{ type: 'text', text: JSON.stringify({ ...(result.plan ?? result.analysis), output: result.output }, null, 2) }] };
|
|
1039
1133
|
}
|
|
1040
1134
|
case 'veto_code_review': {
|
|
@@ -1046,6 +1140,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1046
1140
|
if (result.error) {
|
|
1047
1141
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
1048
1142
|
}
|
|
1143
|
+
autoRecord('code review', 'reviewer', result.analysis?.score ?? Math.round(result.output.confidence * 100));
|
|
1049
1144
|
return { content: [{ type: 'text', text: JSON.stringify(result.analysis, null, 2) }] };
|
|
1050
1145
|
}
|
|
1051
1146
|
case 'veto_diff_review': {
|
|
@@ -1130,6 +1225,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1130
1225
|
if (result.error) {
|
|
1131
1226
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
1132
1227
|
}
|
|
1228
|
+
autoRecord('security scan', 'security-scanner', result.analysis?.score ?? Math.round(result.output.confidence * 100));
|
|
1133
1229
|
return { content: [{ type: 'text', text: JSON.stringify(result.analysis, null, 2) }] };
|
|
1134
1230
|
}
|
|
1135
1231
|
case 'veto_secrets_scan': {
|
|
@@ -1141,6 +1237,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1141
1237
|
if (result.error) {
|
|
1142
1238
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error }) }], isError: true };
|
|
1143
1239
|
}
|
|
1240
|
+
autoRecord('secrets scan', 'secrets', (result.analysis?.findings?.length ?? 0) === 0 ? 100 : result.analysis?.score ?? Math.round(result.output.confidence * 100));
|
|
1144
1241
|
return { content: [{ type: 'text', text: JSON.stringify(result.analysis, null, 2) }] };
|
|
1145
1242
|
}
|
|
1146
1243
|
case 'veto_execute_parallel': {
|
|
@@ -1158,21 +1255,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1158
1255
|
project_dir: t.project_dir ? String(t.project_dir) : parallelProjectDir,
|
|
1159
1256
|
}));
|
|
1160
1257
|
const results = await executeParallel(tasks);
|
|
1258
|
+
// #40: auto-record learning outcome per completed parallel task
|
|
1259
|
+
for (let i = 0; i < results.length; i++) {
|
|
1260
|
+
const r = results[i];
|
|
1261
|
+
if (r.error)
|
|
1262
|
+
continue;
|
|
1263
|
+
const quality = Math.round(r.output.confidence * 100);
|
|
1264
|
+
const tier = quality >= 80 ? 1 : quality >= 40 ? 2 : 3;
|
|
1265
|
+
recordOutcome(tasks[i]?.task.slice(0, 50) ?? r.agent, 50, tier, r.agent, quality);
|
|
1266
|
+
}
|
|
1267
|
+
const parallelPayload = {
|
|
1268
|
+
count: results.length,
|
|
1269
|
+
total_duration_ms: results.reduce((s, r) => s + r.duration_ms, 0),
|
|
1270
|
+
results: results.map(r => ({
|
|
1271
|
+
id: r.id,
|
|
1272
|
+
agent: r.agent,
|
|
1273
|
+
duration_ms: r.duration_ms,
|
|
1274
|
+
error: r.error,
|
|
1275
|
+
output: { ...(r.plan ?? r.analysis), structured: r.output },
|
|
1276
|
+
})),
|
|
1277
|
+
};
|
|
1278
|
+
if (typeof args?.max_tokens === 'number') {
|
|
1279
|
+
const outputText = JSON.stringify(parallelPayload, null, 2);
|
|
1280
|
+
const { exceeded, estimated_tokens } = logUsage({
|
|
1281
|
+
tool_name: 'veto_execute_parallel',
|
|
1282
|
+
max_tokens: args.max_tokens,
|
|
1283
|
+
output: outputText,
|
|
1284
|
+
});
|
|
1285
|
+
if (exceeded) {
|
|
1286
|
+
parallelPayload.budget_warning = `Estimated output tokens (${estimated_tokens}) exceeded max_tokens budget (${args.max_tokens}).`;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1161
1289
|
return {
|
|
1162
|
-
content: [{
|
|
1163
|
-
type: 'text',
|
|
1164
|
-
text: JSON.stringify({
|
|
1165
|
-
count: results.length,
|
|
1166
|
-
total_duration_ms: results.reduce((s, r) => s + r.duration_ms, 0),
|
|
1167
|
-
results: results.map(r => ({
|
|
1168
|
-
id: r.id,
|
|
1169
|
-
agent: r.agent,
|
|
1170
|
-
duration_ms: r.duration_ms,
|
|
1171
|
-
error: r.error,
|
|
1172
|
-
output: { ...(r.plan ?? r.analysis), structured: r.output },
|
|
1173
|
-
})),
|
|
1174
|
-
}, null, 2),
|
|
1175
|
-
}],
|
|
1290
|
+
content: [{ type: 'text', text: JSON.stringify(parallelPayload, null, 2) }],
|
|
1176
1291
|
};
|
|
1177
1292
|
}
|
|
1178
1293
|
case 'veto_memory_store': {
|
|
@@ -1454,6 +1569,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1454
1569
|
gate: typeof s.gate === 'number' ? s.gate : undefined,
|
|
1455
1570
|
}));
|
|
1456
1571
|
const result = await runPipeline(steps, args?.project_dir ? String(args.project_dir) : undefined);
|
|
1572
|
+
// #39: auto-record learning outcome per executed workflow step
|
|
1573
|
+
for (const step of result.results) {
|
|
1574
|
+
if (step.status === 'skipped')
|
|
1575
|
+
continue;
|
|
1576
|
+
const quality = step.error ? 0 : step.confidence;
|
|
1577
|
+
const tier = quality >= 80 ? 1 : quality >= 40 ? 2 : 3;
|
|
1578
|
+
const taskStr = steps.find(s => s.id === step.id)?.task.slice(0, 50) ?? step.id;
|
|
1579
|
+
recordOutcome(taskStr, 50, tier, step.agent, quality);
|
|
1580
|
+
}
|
|
1457
1581
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1458
1582
|
}
|
|
1459
1583
|
case 'veto_explain': {
|
|
@@ -1489,6 +1613,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1489
1613
|
const userContext = args?.context ? String(args.context) : undefined;
|
|
1490
1614
|
const task = `Explain this ${ext} file at ${depth} depth. File: ${basename(filePath)}${userContext ? `. Focus: ${userContext}` : ''}`;
|
|
1491
1615
|
const result = await executeOne({ id: 'explain-1', agent, task, code: fileContent, project_dir: undefined });
|
|
1616
|
+
autoRecord(`explain ${basename(filePath)}`, agent, Math.round(result.output.confidence * 100));
|
|
1492
1617
|
return {
|
|
1493
1618
|
content: [{ type: 'text', text: JSON.stringify({
|
|
1494
1619
|
file: filePath, agent_used: agent, depth,
|
|
@@ -1594,14 +1719,55 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1594
1719
|
tasks,
|
|
1595
1720
|
duration_estimate: planResult.plan?.duration_estimate ?? 'unknown',
|
|
1596
1721
|
};
|
|
1722
|
+
autoRecord(description, 'task-planner', Math.round(planResult.output.confidence * 100));
|
|
1597
1723
|
const hash = createHash('sha256').update(description).digest('hex').slice(0, 16);
|
|
1598
1724
|
const plan_id = saveTaskPlan(JSON.stringify(plan), hash, project_dir);
|
|
1599
1725
|
return { content: [{ type: 'text', text: JSON.stringify({ success: true, plan_id, ...plan }, null, 2) }] };
|
|
1600
1726
|
}
|
|
1601
1727
|
// ── Phase 14: Observability & Safety ──────────────────────────────────────
|
|
1602
1728
|
case 'veto_usage_status': {
|
|
1729
|
+
if (args?.set_budget && typeof args.set_budget === 'object') {
|
|
1730
|
+
const b = args.set_budget;
|
|
1731
|
+
const current = getConfig().dailyTokenBudget;
|
|
1732
|
+
setConfig({
|
|
1733
|
+
dailyTokenBudget: {
|
|
1734
|
+
claude: typeof b.claude === 'number' ? b.claude : current.claude,
|
|
1735
|
+
gemini: typeof b.gemini === 'number' ? b.gemini : current.gemini,
|
|
1736
|
+
codex: typeof b.codex === 'number' ? b.codex : current.codex,
|
|
1737
|
+
},
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1603
1740
|
const status = getUsageStatus();
|
|
1604
|
-
|
|
1741
|
+
const { dailyTokenBudget } = getConfig();
|
|
1742
|
+
const rateStatus = getRateStatus();
|
|
1743
|
+
const recentBudgetLog = getUsageLogs({ limit: 10 });
|
|
1744
|
+
return {
|
|
1745
|
+
content: [{
|
|
1746
|
+
type: 'text',
|
|
1747
|
+
text: JSON.stringify({
|
|
1748
|
+
success: true,
|
|
1749
|
+
...status,
|
|
1750
|
+
daily_token_budget: dailyTokenBudget,
|
|
1751
|
+
tokens_today: {
|
|
1752
|
+
claude: rateStatus.claude.tokens_today,
|
|
1753
|
+
gemini: rateStatus.gemini.tokens_today,
|
|
1754
|
+
codex: rateStatus.codex.tokens_today,
|
|
1755
|
+
},
|
|
1756
|
+
budget_used_pct: {
|
|
1757
|
+
claude: rateStatus.claude.used_percent,
|
|
1758
|
+
gemini: rateStatus.gemini.used_percent,
|
|
1759
|
+
codex: rateStatus.codex.used_percent,
|
|
1760
|
+
},
|
|
1761
|
+
operation_budget_log: recentBudgetLog.map(e => ({
|
|
1762
|
+
tool: e.tool_name,
|
|
1763
|
+
max_tokens: e.max_tokens,
|
|
1764
|
+
estimated_tokens: e.estimated_tokens,
|
|
1765
|
+
exceeded: e.exceeded === 1,
|
|
1766
|
+
at: e.created_at,
|
|
1767
|
+
})),
|
|
1768
|
+
}, null, 2),
|
|
1769
|
+
}],
|
|
1770
|
+
};
|
|
1605
1771
|
}
|
|
1606
1772
|
case 'veto_audit_log': {
|
|
1607
1773
|
const events = getAuditLog({
|
|
@@ -1846,6 +2012,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1846
2012
|
const depthNote = sumFormat === 'detailed' ? 'Write paragraph-level prose.' : 'Return 4–6 bullet points only.';
|
|
1847
2013
|
const task = `Summarize this file concisely for a developer who has never seen it.${focusNote} ${depthNote} File: ${basename(sumFilePath)}`;
|
|
1848
2014
|
const r = await executeOne({ id: 'sum-file', agent, task, code: fileContent.slice(0, 8000) });
|
|
2015
|
+
autoRecord(`summarize ${basename(sumFilePath)}`, agent, Math.round(r.output.confidence * 100));
|
|
1849
2016
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
1850
2017
|
success: true, subject: 'file', path: sumFilePath, format: sumFormat,
|
|
1851
2018
|
summary: r.plan ?? r.analysis ?? r.output,
|
|
@@ -1867,6 +2034,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1867
2034
|
const depthNote = sumFormat === 'detailed' ? 'Write paragraph-level prose with sections.' : 'Return 5–7 bullet points that capture the essence.';
|
|
1868
2035
|
const task = `You are a senior engineer briefing a colleague on this codebase.${focusNote} ${depthNote} Be concise and precise — no filler.`;
|
|
1869
2036
|
const r = await executeOne({ id: 'sum-proj', agent: 'project-mapper', task, context: ctx });
|
|
2037
|
+
autoRecord('summarize project', 'project-mapper', Math.round(r.output.confidence * 100));
|
|
1870
2038
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
1871
2039
|
success: true, subject: 'project', path: sumProjectDir, format: sumFormat,
|
|
1872
2040
|
tech_stack: discResult.tech_stack,
|