Includes AirPlan design documents, AircOding-alpha1-plan, AirPlanV2, AirPlan-ParaV2, AirPlan-Para V1 reference docs, and all working code changes across packages. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
132 lines
5.3 KiB
TypeScript
Executable File
132 lines
5.3 KiB
TypeScript
Executable File
/**
|
|
* Doctor Tools - Diagnostic and repair operations
|
|
*
|
|
* Implements T-213: doctor.*
|
|
* FR-018: Wired to DoctorService for real diagnostics.
|
|
*
|
|
* @module packages/runtime/src/tools/doctor
|
|
*/
|
|
|
|
import type { ToolDefinition, ToolCall, ToolResultEnvelope, ISOTimeString } from '@aircoding/contracts'
|
|
import { DoctorService } from '../../doctor/DoctorService.js'
|
|
|
|
export const doctor_check: ToolDefinition = {
|
|
name: 'doctor.check',
|
|
category: 'doctor',
|
|
description: 'Run diagnostic checks',
|
|
version: 1,
|
|
output_schema: { type: 'object', properties: {}, required: [] },
|
|
input_schema: {
|
|
type: 'object',
|
|
properties: {
|
|
scope: { type: 'string', enum: ['all', 'self_bootstrap', 'capability', 'project', 'runtime', 'toolchain', 'display', 'network', 'provider'], default: 'all' }
|
|
}
|
|
},
|
|
permissions: { read_paths: { allow: ["*"] } },
|
|
streaming: false
|
|
}
|
|
|
|
export const doctor_fix: ToolDefinition = {
|
|
name: 'doctor.fix',
|
|
category: 'doctor',
|
|
description: 'Attempt to fix issues (requires permission policy)',
|
|
version: 1,
|
|
output_schema: { type: 'object', properties: {}, required: [] },
|
|
input_schema: {
|
|
type: 'object',
|
|
properties: {
|
|
check_name: { type: 'string', description: 'Check name to fix (e.g., bun, display, toolchain.cmake)' },
|
|
fix: { type: 'boolean', default: true, description: 'Actually perform the fix vs dry-run' }
|
|
},
|
|
required: ['check_name']
|
|
},
|
|
permissions: { write_paths: { allow: ["*"] } },
|
|
streaming: false
|
|
}
|
|
|
|
/**
|
|
* FR-018: Real executor that wraps DoctorService
|
|
*/
|
|
export function createDoctorExecutor(project_root?: string, capability_registry?: any) {
|
|
const doctor = new DoctorService(project_root || process.cwd(), capability_registry)
|
|
|
|
return {
|
|
'doctor.check': async (call: ToolCall): Promise<ToolResultEnvelope> => {
|
|
const scope = (call.arguments?.scope as any) || 'all'
|
|
const valid_scopes = ['all', 'self_bootstrap', 'capability', 'project', 'runtime', 'toolchain', 'display', 'network', 'provider']
|
|
const actual_scope = valid_scopes.includes(scope) ? scope : 'all'
|
|
|
|
try {
|
|
const report = await doctor.run_diagnostics(actual_scope as any)
|
|
return create_result(call.call_id, 'doctor.check', 'text', {
|
|
scope: actual_scope,
|
|
checks: report.checks.map(c => ({
|
|
name: c.name,
|
|
category: c.category,
|
|
passed: c.passed,
|
|
message: c.message,
|
|
fixable: c.fixable,
|
|
fix: c.fix
|
|
})),
|
|
all_passed: report.all_passed,
|
|
bootstrap_passed: report.bootstrap_passed,
|
|
fixable_count: report.fixable_count,
|
|
message: report.all_passed ? 'All checks passed' : `${report.fixable_count} fixable issues found`
|
|
})
|
|
} catch (e: any) {
|
|
return create_result(call.call_id, 'doctor.check', 'error', { message: e.message || String(e) })
|
|
}
|
|
},
|
|
|
|
'doctor.fix': async (call: ToolCall): Promise<ToolResultEnvelope> => {
|
|
const { check_name, fix = true } = call.arguments as { check_name: string; fix?: boolean }
|
|
|
|
try {
|
|
// FR-018: Check permission before system modifications
|
|
const requires_permission = check_name === 'display' || check_name.startsWith('toolchain.') || check_name.startsWith('capability.')
|
|
const project_root_val = project_root || process.cwd()
|
|
let has_permission = true
|
|
|
|
if (requires_permission) {
|
|
const { existsSync } = await import('fs')
|
|
const permission_config_path = `${project_root_val}/.air/shared/permissions.yaml`
|
|
if (existsSync(permission_config_path)) {
|
|
// Check if auto-fix is allowed in permissions config
|
|
has_permission = false // Require explicit consent for system changes
|
|
}
|
|
}
|
|
|
|
if (requires_permission && !has_permission) {
|
|
return create_result(call.call_id, 'doctor.fix', 'error', {
|
|
message: `System modification "${check_name}" requires explicit permission. Use --ask-confirm flag or add to permissions.yaml.`
|
|
})
|
|
}
|
|
|
|
if (!fix) {
|
|
// Dry-run: just describe what would be done
|
|
const result = await doctor.fix(check_name)
|
|
return create_result(call.call_id, 'doctor.fix', 'text', {
|
|
check_name,
|
|
dry_run: true,
|
|
would_do: result.message,
|
|
status: 'dry_run'
|
|
})
|
|
}
|
|
|
|
const result = await doctor.fix(check_name)
|
|
return create_result(call.call_id, 'doctor.fix', 'text', {
|
|
check_name,
|
|
fixed: result.ok,
|
|
message: result.message,
|
|
status: result.ok ? 'fixed' : 'failed'
|
|
})
|
|
} catch (e: any) {
|
|
return create_result(call.call_id, 'doctor.fix', 'error', { message: e.message || String(e) })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function create_result(call_id: string, tool_name: string, type: 'text' | 'error', content: Record<string, unknown>): ToolResultEnvelope {
|
|
return { status: type === "error" ? "error" : "ok", output: type === "error" ? undefined : content, error: type === "error" ? { error_id: call_id, kind: "tool_error", severity: "error", message: typeof content?.message === "string" ? content.message : "tool error", retryability: "not_retryable", semantic_signature: tool_name } : undefined, metadata: { timestamp: new Date().toISOString() as ISOTimeString, call_id, tool_name, type: type === "error" ? "error" : "ok" } }
|
|
} |