5fb285cf46
---ci---
project: ci
phase: 1
milestone: v0.8
status: in_progress
decisions:
- id: D-022
decision: Validate BackendResult at boundary with Zod schema
rationale: External backend output is untrusted; runtime validation prevents corrupt commit streams
confidence: 0.92
- id: D-023
decision: opencode parseResult returns success:false on malformed JSON
rationale: Silent success:true on parse failure masks backend errors; fail loudly instead
confidence: 0.95
requirements:
covered: [FIX-02, FIX-03]
---/ci---
FIX-02: Add Zod BackendResultSchema and validateBackendResult() in
backends/types.ts. backendResultToAgentResult() in base.ts now validates
before passing through. Invalid results produce success:false with error
detail. Path traversal protection: artifact paths with '..' or leading '/'
are rejected.
FIX-03: opencode.ts parseResult() no longer defaults to success:true when
JSON parsing fails entirely. Both the inner parse error and the no-JSON
match case now return emptyBackendResult() with descriptive error messages.
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
import { IntelligenceBackend, BackendRequest, BackendResult, BackendUnavailableError, emptyBackendResult, validateBackendResult } from "../backends/types.js";
|
|
import { AgentName, AutonomyLevel } from "../types/config.js";
|
|
|
|
export interface AgentResult {
|
|
success: boolean;
|
|
output: string;
|
|
artifacts_created: string[] | number;
|
|
decisions: number;
|
|
escalations: number;
|
|
duration_ms: number;
|
|
error?: string;
|
|
}
|
|
|
|
export interface AgentContext {
|
|
project_path: string;
|
|
phase: number;
|
|
stage: string;
|
|
specification: string;
|
|
config_path: string;
|
|
backend?: IntelligenceBackend;
|
|
}
|
|
|
|
export function backendResultToAgentResult(result: BackendResult): AgentResult {
|
|
const validation = validateBackendResult(result);
|
|
if (!validation.result) {
|
|
return {
|
|
success: false,
|
|
output: "",
|
|
artifacts_created: [],
|
|
decisions: 0,
|
|
escalations: 0,
|
|
duration_ms: 0,
|
|
error: `BackendResult validation failed: ${validation.errors.join("; ")}`,
|
|
};
|
|
}
|
|
return {
|
|
success: result.success,
|
|
output: result.output,
|
|
artifacts_created: result.artifacts.map((a) => a.path),
|
|
decisions: result.decisions.length,
|
|
escalations: result.escalations.length,
|
|
duration_ms: 0,
|
|
error: result.error,
|
|
};
|
|
}
|
|
|
|
export abstract class BaseAgent {
|
|
abstract readonly name: AgentName;
|
|
abstract readonly description: string;
|
|
abstract readonly workflow: string;
|
|
|
|
abstract execute(context: AgentContext): Promise<AgentResult>;
|
|
|
|
protected async executeViaBackend(context: AgentContext, task: string): Promise<AgentResult> {
|
|
if (!context.backend) {
|
|
throw new BackendUnavailableError("none", this.name);
|
|
}
|
|
const request: BackendRequest = {
|
|
persona: this.name,
|
|
workflow: this.workflow,
|
|
task,
|
|
context,
|
|
autonomy: "full",
|
|
};
|
|
const result = await context.backend.execute(request);
|
|
return backendResultToAgentResult(result);
|
|
}
|
|
|
|
protected log(message: string): void {
|
|
console.log(`[${this.name}] ${message}`);
|
|
}
|
|
|
|
protected warn(message: string): void {
|
|
console.warn(`[${this.name}] ⚠ ${message}`);
|
|
}
|
|
|
|
protected error(message: string): void {
|
|
console.error(`[${this.name}] ✗ ${message}`);
|
|
}
|
|
} |