feat(J): Phase J Self-Improvement Plugin — controlled improvement loop
Check Cross-Plugin Imports / check (push) Has been cancelled

- New self_improvement plugin: models, services, routes, plugin
- 4 SQLAlchemy models: ImprovementSignal, ImprovementPattern, ImprovementProposal, ImpactMeasurement
- Migration 0132: 4 tables with RLS
- Services: collect_signals, detect_patterns, create_proposal, evaluate_proposal, request_approval, activate_proposal, rollback_proposal, measure_impact
- 11 API routes under /api/v1/improvement/
- Frontend: improvement.ts API client, ImprovementPanel.tsx in AISidebar proactive tab
- 24/24 integration tests pass
- Fix: __init__.py imports Plugin class for discover_builtins() (self_improvement + knowledge)
- Fix: automation plugin register_plugin_contributions skips when no tenant exists
This commit is contained in:
Agent Zero
2026-08-21 01:34:48 +02:00
parent 6264ed4752
commit fbcfbbced6
14 changed files with 3109 additions and 3 deletions
+195
View File
@@ -0,0 +1,195 @@
/**
* React Query hooks for Self-Improvement API.
* Follows the pattern from automation.ts.
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost } from './client';
// ── Types ──
export interface ImprovementSignal {
id: string;
source_type: string;
signal_kind: string;
severity: string;
summary: string;
confidence: number;
pattern_id: string | null;
created_at: string | null;
}
export interface ImprovementPattern {
id: string;
pattern_kind: string;
title: string;
description: string;
target_type: string;
target_name: string;
occurrence_count: number;
confidence: number;
status: string;
proposed_action: string;
created_at: string | null;
}
export interface ImprovementProposal {
id: string;
title: string;
description: string;
target_type: string;
target_name: string | null;
status: string;
version_number: number;
rationale: string;
expected_benefit: string;
risk_assessment: string;
evaluation_score: number;
pattern_id: string | null;
created_at: string | null;
activated_at: string | null;
}
export interface ProposalDetail extends ImprovementProposal {
target_ref_id: string | null;
proposed_config: Record<string, any>;
previous_config: Record<string, any>;
evidence_refs: any[];
evaluation_result: Record<string, any>;
evaluated_at: string | null;
approval_request_id: string | null;
approved_by: string | null;
approved_at: string | null;
rolled_back_at: string | null;
rollback_reason: string | null;
}
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
page_size: number;
}
// ── Signal Hooks ──
export function useSignals(page = 1, pageSize = 20, sourceType?: string) {
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
if (sourceType) params.set('source_type', sourceType);
return useQuery({
queryKey: ['improvement-signals', page, pageSize, sourceType],
queryFn: () => apiGet<PaginatedResponse<ImprovementSignal>>(`/improvement/signals?${params}`),
});
}
export function useCollectSignals() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { since?: string; limit?: number }) => apiPost('/improvement/signals/collect', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-signals'] });
qc.invalidateQueries({ queryKey: ['improvement-patterns'] });
},
});
}
// ── Pattern Hooks ──
export function usePatterns(page = 1, pageSize = 20) {
return useQuery({
queryKey: ['improvement-patterns', page, pageSize],
queryFn: () => apiGet<PaginatedResponse<ImprovementPattern>>(`/improvement/patterns?page=${page}&page_size=${pageSize}`),
});
}
export function useDetectPatterns() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { min_occurrences?: number }) => apiPost('/improvement/patterns/detect', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-patterns'] });
},
});
}
// ── Proposal Hooks ──
export function useProposals(page = 1, pageSize = 20, status?: string) {
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
if (status) params.set('status', status);
return useQuery({
queryKey: ['improvement-proposals', page, pageSize, status],
queryFn: () => apiGet<PaginatedResponse<ImprovementProposal>>(`/improvement/proposals?${params}`),
});
}
export function useProposal(id: string) {
return useQuery({
queryKey: ['improvement-proposal', id],
queryFn: () => apiGet<ProposalDetail>(`/improvement/proposals/${id}`),
enabled: !!id,
});
}
export function useCreateProposal() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: Record<string, any>) => apiPost('/improvement/proposals', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
},
});
}
export function useEvaluateProposal() {
const qc = useQueryClient();
return useMutation({
mutationFn: (proposalId: string) => apiPost(`/improvement/proposals/${proposalId}/evaluate`, {}),
onSuccess: (_data, proposalId) => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
qc.invalidateQueries({ queryKey: ['improvement-proposal', proposalId] });
},
});
}
export function useRequestApproval() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ proposalId, body }: { proposalId: string; body: { approver_id?: string } }) =>
apiPost(`/improvement/proposals/${proposalId}/request-approval`, body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
},
});
}
export function useActivateProposal() {
const qc = useQueryClient();
return useMutation({
mutationFn: (proposalId: string) => apiPost(`/improvement/proposals/${proposalId}/activate`, {}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
},
});
}
export function useRollbackProposal() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ proposalId, reason }: { proposalId: string; reason?: string }) =>
apiPost(`/improvement/proposals/${proposalId}/rollback`, { reason: reason || '' }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
},
});
}
export function useMeasureImpact() {
const qc = useQueryClient();
return useMutation({
mutationFn: (proposalId: string) => apiPost(`/improvement/proposals/${proposalId}/measure`, {}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['improvement-proposals'] });
},
});
}