desktop: Fix with AI on scan findings that need a code change (aiFix: true, AI_FIX_RULES). The host (src-tauri/src/ai_fix.rs) runs the owner's local Claude Code headless in the app folder with Read / Edit / Write / Grep / Glob only (Bash, web tools, sub-agents disallowed) and a prompt it builds from the finding's fields; it refuses while the folder has uncommitted files, streams progress, times out after 10 min with a heartbeat, kills the process group on Cancel or exit, and records the run's changed files so Undo restores exactly those; the window shows the diff with Keep / Undo and rescans. Without claude the button copies the prompt (Copy for AI). Only the claude binary, git and kill / taskkill are started there (tests/security.rs); tests/ai_fix.rs covers the prompt, dirty-tree refusal, undo scope, timeout and cancel
This commit is contained in:
parent
992f5bcc0e
commit
c95f2af8ff
24 changed files with 1023 additions and 10 deletions
|
|
@ -69,6 +69,13 @@ async function listen<T>(event: string, cb: (payload: T) => void): Promise<Unlis
|
|||
return tauriListen<T>(event, e => cb(e.payload));
|
||||
}
|
||||
|
||||
/** The finding fields the host builds the Fix with AI prompt from. */
|
||||
export interface AiFinding { rule: string; guideline: string; store: 'apple' | 'play'; title: string; evidence: { file: string; line: number; text?: string }[]; fix: string; skill: string }
|
||||
export interface AiChange { path: string; status: string }
|
||||
export interface AiFixInfo { claude: boolean; prompt: string; git: boolean; gitError: string | null; dirty: AiChange[] }
|
||||
export type AiFixRun = { refused: 'dirty'; dirty: AiChange[] }
|
||||
| { refused?: undefined; runId: string; outcome: { code: number | null; timed_out: boolean; cancelled: boolean; summary: string }; changes: AiChange[]; diff: string };
|
||||
|
||||
let jobSeq = 0;
|
||||
/** A fresh job id for one background run (progress events and cancel refer to it). */
|
||||
export const newJob = (kind: string): string => `${kind.replace(/[^A-Za-z0-9_.-]/g, '-').slice(0, 40)}-${Date.now().toString(36)}-${++jobSeq}`;
|
||||
|
|
@ -84,6 +91,10 @@ export const api = {
|
|||
invoke<CacheEntry<T> | null>('cache_get', { method, projectId, args }),
|
||||
clearCache: () => invoke<void>('cache_clear'),
|
||||
cancel: (job: string) => invoke<boolean>('job_cancel', { job }),
|
||||
/** Fix with AI (src-tauri/src/ai_fix.rs): claude installed?, the prompt (also for Copy for AI), the dirty files. */
|
||||
aiFixInfo: (projectId: string, finding: AiFinding) => invoke<AiFixInfo>('ai_fix_info', { projectId, finding }),
|
||||
aiFixRun: (projectId: string, finding: AiFinding, job: string) => invoke<AiFixRun>('ai_fix_run', { projectId, finding, job }),
|
||||
aiFixUndo: (runId: string) => invoke<string[]>('ai_fix_undo', { runId }),
|
||||
/** Dry run, native confirm dialog with that dry run, then the real run with a host-minted token. */
|
||||
writeWithConfirm: (method: string, projectId: string, args: Record<string, unknown> = {}, job?: string) =>
|
||||
invoke<WriteOutcome>('write_with_confirm', { method, projectId, args, job: job ?? null }),
|
||||
|
|
@ -155,6 +166,8 @@ export const reads = {
|
|||
export interface ProgressEvent extends ProgressParams { job?: string }
|
||||
export const onProgress = (cb: (p: ProgressEvent) => void): Promise<UnlistenFn> =>
|
||||
listen<{ params: ProgressParams; job?: string }>('sidecar-progress', e => cb({ ...e.params, job: e.job }));
|
||||
/** One progress line of a Fix with AI run. */
|
||||
export const onAiFix = (cb: (e: { job: string; line: string }) => void): Promise<UnlistenFn> => listen<{ job: string; line: string }>('ai-fix', cb);
|
||||
export const onDownload = (cb: (e: DownloadEvent) => void): Promise<UnlistenFn> => listen<DownloadEvent>('tool-download', cb);
|
||||
export const onDeepLink = (cb: (screen: string) => void): Promise<UnlistenFn> => listen<string>('deep-link', cb);
|
||||
/** storebound://open?path=... selected or added an app folder (its project id). */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue