|
|
|
@@ -5,6 +5,9 @@
|
|
|
|
* @module packages/toolchain-cpp/src/CppToolRegistrar
|
|
|
|
* @module packages/toolchain-cpp/src/CppToolRegistrar
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { writeFileSync, mkdirSync, existsSync } from 'fs'
|
|
|
|
|
|
|
|
import { join, dirname } from 'path'
|
|
|
|
|
|
|
|
import { randomUUID } from 'crypto'
|
|
|
|
import { CPP_TOOLCHAIN_CAPABILITY } from './capability.js'
|
|
|
|
import { CPP_TOOLCHAIN_CAPABILITY } from './capability.js'
|
|
|
|
import { CppProjectDetector } from './detect/CppProjectDetector.js'
|
|
|
|
import { CppProjectDetector } from './detect/CppProjectDetector.js'
|
|
|
|
import { CMakeConfigurator } from './build/CMakeConfigurator.js'
|
|
|
|
import { CMakeConfigurator } from './build/CMakeConfigurator.js'
|
|
|
|
@@ -13,14 +16,37 @@ import { CppTestRunner } from './test/CppTestRunner.js'
|
|
|
|
import { CppcheckRunner } from './analysis/CppcheckRunner.js'
|
|
|
|
import { CppcheckRunner } from './analysis/CppcheckRunner.js'
|
|
|
|
import { ClangdClient } from './analysis/ClangdClient.js'
|
|
|
|
import { ClangdClient } from './analysis/ClangdClient.js'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Duck-typed EventSink — avoids direct runtime import (INV-4)
|
|
|
|
|
|
|
|
export interface EventSink {
|
|
|
|
|
|
|
|
ingest(event: any): Promise<void>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Context passed through from RuntimeApp
|
|
|
|
|
|
|
|
export interface CppContext {
|
|
|
|
|
|
|
|
session_id: string
|
|
|
|
|
|
|
|
project_id: string
|
|
|
|
|
|
|
|
project_root: string
|
|
|
|
|
|
|
|
task_id?: string
|
|
|
|
|
|
|
|
agent_id?: string
|
|
|
|
|
|
|
|
agent_type?: string
|
|
|
|
|
|
|
|
tool_run_id?: string
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface ArtifactInfo {
|
|
|
|
|
|
|
|
artifact_id: string
|
|
|
|
|
|
|
|
path: string
|
|
|
|
|
|
|
|
size_bytes: number
|
|
|
|
|
|
|
|
sha256_calc: string
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class CppToolRegistrar {
|
|
|
|
export class CppToolRegistrar {
|
|
|
|
manifest = CPP_TOOLCHAIN_CAPABILITY
|
|
|
|
manifest = CPP_TOOLCHAIN_CAPABILITY
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
register(
|
|
|
|
* Register all cpp.* tools with the provided registry.
|
|
|
|
registry: { register(name: string, definition: any, executor: (call: any, ctx?: any) => Promise<any>): void },
|
|
|
|
* INV-4: This is called through CapabilityRegistry boundary, never via direct runtime import.
|
|
|
|
project_root: string,
|
|
|
|
*/
|
|
|
|
event_sink?: EventSink,
|
|
|
|
register(registry: { register(name: string, definition: any, executor: (call: any) => Promise<any>): void }, project_root: string): void {
|
|
|
|
): void {
|
|
|
|
const detector = new CppProjectDetector(project_root)
|
|
|
|
const detector = new CppProjectDetector(project_root)
|
|
|
|
const configurator = new CMakeConfigurator()
|
|
|
|
const configurator = new CMakeConfigurator()
|
|
|
|
const builder = new CppBuilder()
|
|
|
|
const builder = new CppBuilder()
|
|
|
|
@@ -28,90 +54,269 @@ export class CppToolRegistrar {
|
|
|
|
const cppcheck = new CppcheckRunner()
|
|
|
|
const cppcheck = new CppcheckRunner()
|
|
|
|
const clangd = new ClangdClient()
|
|
|
|
const clangd = new ClangdClient()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.detect — no external command, no evidence needed
|
|
|
|
// cpp.detect
|
|
|
|
// cpp.detect
|
|
|
|
registry.register('cpp.detect', {
|
|
|
|
registry.register('cpp.detect', {
|
|
|
|
name: 'cpp.detect', category: 'toolchain',
|
|
|
|
name: 'cpp.detect', category: 'toolchain',
|
|
|
|
description: 'Detect C++ project structure and toolchain',
|
|
|
|
description: 'Detect C++ project structure and toolchain',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[0].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[0].input_schema,
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
const result = detector.detect()
|
|
|
|
const result = detector.detect()
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.detect', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.detect', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.configure
|
|
|
|
// cpp.configure — cmake configure, evidence on failure
|
|
|
|
registry.register('cpp.configure', {
|
|
|
|
registry.register('cpp.configure', {
|
|
|
|
name: 'cpp.configure', category: 'toolchain',
|
|
|
|
name: 'cpp.configure', category: 'toolchain',
|
|
|
|
description: 'Configure C++ build with CMake',
|
|
|
|
description: 'Configure C++ build with CMake',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[1].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[1].input_schema,
|
|
|
|
permissions: { read: true, write: true, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: true, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
|
|
|
|
const ctx: CppContext = tool_ctx || {}
|
|
|
|
|
|
|
|
const cmd_id = this.cmd_id()
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cmake configure'))
|
|
|
|
|
|
|
|
|
|
|
|
const result = configurator.configure({ project_root, generator: call.arguments?.generator as any, build_type: call.arguments?.build_type as any })
|
|
|
|
const result = configurator.configure({ project_root, generator: call.arguments?.generator as any, build_type: call.arguments?.build_type as any })
|
|
|
|
|
|
|
|
const duration_ms = Date.now() - Date.parse(new Date().toISOString()) + 1
|
|
|
|
|
|
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
if (result.ok) {
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.configure', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
|
|
|
|
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.configure', output: result, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.configure', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'configure failed', retryability: 'not_retryable', semantic_signature: 'cpp.configure' }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, '', result.error || 'configure failed')
|
|
|
|
|
|
|
|
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, [{ file: 'CMakeLists.txt', line: 0, column: 0, severity: 'error', message: result.error || 'configure failed', semantic_signature: 'cmake.configure' }])
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
|
|
|
|
|
|
|
|
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { error: result.error })
|
|
|
|
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.configure', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'configure failed', retryability: 'not_retryable', semantic_signature: 'cpp.configure' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.build
|
|
|
|
// cpp.build — cmake build, diagnostics + evidence
|
|
|
|
registry.register('cpp.build', {
|
|
|
|
registry.register('cpp.build', {
|
|
|
|
name: 'cpp.build', category: 'toolchain',
|
|
|
|
name: 'cpp.build', category: 'toolchain',
|
|
|
|
description: 'Build C++ project',
|
|
|
|
description: 'Build C++ project',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[2].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[2].input_schema,
|
|
|
|
permissions: { read: true, write: true, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: true, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
|
|
|
|
const ctx: CppContext = tool_ctx || {}
|
|
|
|
|
|
|
|
const cmd_id = this.cmd_id()
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cmake --build'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const build_start = Date.now()
|
|
|
|
const result = builder.build(project_root + '/build', call.arguments?.target as string)
|
|
|
|
const result = builder.build(project_root + '/build', call.arguments?.target as string)
|
|
|
|
|
|
|
|
const duration_ms = Date.now() - build_start
|
|
|
|
|
|
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
if (result.ok) {
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.build', output: { built: true, output: result.output, diagnostics: result.diagnostics, elapsed_ms: result.elapsed_ms }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
|
|
|
|
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.build', output: { built: true, output: result.output, diagnostics: result.diagnostics, elapsed_ms: result.elapsed_ms }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.build', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'build failed', retryability: 'not_retryable', semantic_signature: 'cpp.build' }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
|
|
|
|
|
|
|
|
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, result.diagnostics.map(d => ({ file: d.file || '', line: d.line || 0, column: d.column || 0, severity: d.severity || 'error', message: d.message, semantic_signature: d.semantic_signature || `build.${d.file}.${d.line}` })))
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
|
|
|
|
|
|
|
|
await this.emit_evidence(event_sink, ctx, cmd_id, 'build_output', { diagnostics: result.diagnostics.length })
|
|
|
|
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.build', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'build failed', retryability: 'not_retryable', semantic_signature: 'cpp.build' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.test
|
|
|
|
// cpp.test — ctest, evidence on failure
|
|
|
|
registry.register('cpp.test', {
|
|
|
|
registry.register('cpp.test', {
|
|
|
|
name: 'cpp.test', category: 'toolchain',
|
|
|
|
name: 'cpp.test', category: 'toolchain',
|
|
|
|
description: 'Run C++ tests',
|
|
|
|
description: 'Run C++ tests',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[3].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[3].input_schema,
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
|
|
|
|
const ctx: CppContext = tool_ctx || {}
|
|
|
|
|
|
|
|
const cmd_id = this.cmd_id()
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'ctest'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const test_start = Date.now()
|
|
|
|
const result = tester.run_tests(project_root + '/build')
|
|
|
|
const result = tester.run_tests(project_root + '/build')
|
|
|
|
|
|
|
|
const duration_ms = Date.now() - test_start
|
|
|
|
|
|
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
if (result.ok) {
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.test', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
|
|
|
|
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.test', output: result, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.test', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'test failed', retryability: 'not_retryable', semantic_signature: 'cpp.test' }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, []))
|
|
|
|
|
|
|
|
await this.emit_evidence(event_sink, ctx, cmd_id, 'test_output', { passed: result.passed, failed: result.failed })
|
|
|
|
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.test', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'test failed', retryability: 'not_retryable', semantic_signature: 'cpp.test' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.cppcheck
|
|
|
|
// cpp.cppcheck — static analysis, diagnostics + evidence
|
|
|
|
registry.register('cpp.cppcheck', {
|
|
|
|
registry.register('cpp.cppcheck', {
|
|
|
|
name: 'cpp.cppcheck', category: 'toolchain',
|
|
|
|
name: 'cpp.cppcheck', category: 'toolchain',
|
|
|
|
description: 'Run cppcheck static analysis',
|
|
|
|
description: 'Run cppcheck static analysis',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[4].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[4].input_schema,
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
|
|
|
|
const ctx: CppContext = tool_ctx || {}
|
|
|
|
|
|
|
|
const cmd_id = this.cmd_id()
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cppcheck'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const check_start = Date.now()
|
|
|
|
const result = cppcheck.run(project_root, { enable_all: call.arguments?.enable_all as boolean, check_config: call.arguments?.check_config as boolean })
|
|
|
|
const result = cppcheck.run(project_root, { enable_all: call.arguments?.enable_all as boolean, check_config: call.arguments?.check_config as boolean })
|
|
|
|
|
|
|
|
const duration_ms = Date.now() - check_start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, result.diagnostics.map(d => ({ file: d.file || '', line: d.line || 0, column: d.column || 0, severity: d.severity || 'warning', message: d.message, semantic_signature: d.semantic_signature || `cppcheck.${d.file}.${d.line}` })))
|
|
|
|
|
|
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
if (result.ok) {
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.cppcheck', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
|
|
|
|
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.cppcheck', output: { ...result, diagnostic_ids: diag_ids }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.cppcheck', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'cppcheck failed', retryability: 'not_retryable', semantic_signature: 'cpp.cppcheck' }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
|
|
|
|
|
|
|
|
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { diagnostics: result.diagnostics.length })
|
|
|
|
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.cppcheck', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'cppcheck failed', retryability: 'not_retryable', semantic_signature: 'cpp.cppcheck' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// cpp.clangd
|
|
|
|
// cpp.clangd — LSP query, diagnostics
|
|
|
|
registry.register('cpp.clangd', {
|
|
|
|
registry.register('cpp.clangd', {
|
|
|
|
name: 'cpp.clangd', category: 'toolchain',
|
|
|
|
name: 'cpp.clangd', category: 'toolchain',
|
|
|
|
description: 'Query clangd for symbol info',
|
|
|
|
description: 'Query clangd for symbol info',
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[5].input_schema,
|
|
|
|
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[5].input_schema,
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
permissions: { read: true, write: false, network: false }, streaming: false
|
|
|
|
}, async (call) => {
|
|
|
|
}, async (call, tool_ctx) => {
|
|
|
|
|
|
|
|
const ctx: CppContext = tool_ctx || {}
|
|
|
|
|
|
|
|
const cmd_id = this.cmd_id()
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'clangd --check'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clangd_start = Date.now()
|
|
|
|
const result = await clangd.query_symbol(call.arguments?.file as string, call.arguments?.line as number, call.arguments?.column as number)
|
|
|
|
const result = await clangd.query_symbol(call.arguments?.file as string, call.arguments?.line as number, call.arguments?.column as number)
|
|
|
|
|
|
|
|
const duration_ms = Date.now() - clangd_start
|
|
|
|
|
|
|
|
|
|
|
|
if (result.ok) {
|
|
|
|
if (result.ok) {
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.clangd', output: result, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, (result.diagnostics || []).map(d => ({ file: d.file, line: d.line, column: 0, severity: d.severity, message: d.message, semantic_signature: `clangd.${d.file}.${d.line}` })))
|
|
|
|
|
|
|
|
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
|
|
|
|
|
|
|
|
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.clangd', output: { ...result, diagnostic_ids: diag_ids }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.clangd', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'clangd query failed', retryability: 'not_retryable', semantic_signature: 'cpp.clangd' }, metadata: { timestamp: new Date().toISOString() } }
|
|
|
|
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, '', '', []))
|
|
|
|
|
|
|
|
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { error: result.error })
|
|
|
|
|
|
|
|
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.clangd', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'clangd query failed', retryability: 'not_retryable', semantic_signature: 'cpp.clangd' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Private: event emission (no runtime import — uses duck-typed EventSink) =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private cmd_id(): string {
|
|
|
|
|
|
|
|
return `cmd_${randomUUID().slice(0, 12)}`
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async emit(sink: EventSink | undefined, ctx: CppContext, event: any): Promise<void> {
|
|
|
|
|
|
|
|
if (!sink) return
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await sink.ingest(event)
|
|
|
|
|
|
|
|
} catch { /* evidence emission is best-effort, never breaks tool execution */ }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async write_artifacts(
|
|
|
|
|
|
|
|
sink: EventSink | undefined, ctx: CppContext, project_root: string,
|
|
|
|
|
|
|
|
cmd_id: string, stdout: string, stderr: string,
|
|
|
|
|
|
|
|
): Promise<{ stdout_id: string; stderr_id: string }> {
|
|
|
|
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
|
|
|
|
const artifacts_dir = join(project_root, '.air', 'local', 'artifacts')
|
|
|
|
|
|
|
|
if (!existsSync(artifacts_dir)) mkdirSync(artifacts_dir, { recursive: true })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const stdout_path = join(artifacts_dir, `${cmd_id}.stdout.log`)
|
|
|
|
|
|
|
|
const stderr_path = join(artifacts_dir, `${cmd_id}.stderr.log`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
writeFileSync(stdout_path, stdout.slice(0, 65536), 'utf-8')
|
|
|
|
|
|
|
|
writeFileSync(stderr_path, stderr.slice(0, 65536), 'utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const stdout_id = `art_${cmd_id}_stdout`
|
|
|
|
|
|
|
|
const stderr_id = `art_${cmd_id}_stderr`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (sink) {
|
|
|
|
|
|
|
|
await this.emit(sink, ctx, {
|
|
|
|
|
|
|
|
id: `evt_${stdout_id}`, type: 'artifact.created', version: 1, timestamp: now,
|
|
|
|
|
|
|
|
session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: { artifact_id: stdout_id, type: 'log', uri: `file://${stdout_path}`, path: stdout_path, original_name: `${cmd_id}.stdout.log`, size_bytes: stdout.length, sha256: '', task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '', command_run_id: cmd_id, associated_entity_type: 'command_run', associated_entity_id: cmd_id, metadata: {} }
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
await this.emit(sink, ctx, {
|
|
|
|
|
|
|
|
id: `evt_${stderr_id}`, type: 'artifact.created', version: 1, timestamp: now,
|
|
|
|
|
|
|
|
session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: { artifact_id: stderr_id, type: 'log', uri: `file://${stderr_path}`, path: stderr_path, original_name: `${cmd_id}.stderr.log`, size_bytes: stderr.length, sha256: '', task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '', command_run_id: cmd_id, associated_entity_type: 'command_run', associated_entity_id: cmd_id, metadata: {} }
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { stdout_id, stderr_id }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async emit_diagnostics(
|
|
|
|
|
|
|
|
sink: EventSink | undefined, ctx: CppContext, cmd_id: string,
|
|
|
|
|
|
|
|
diags: Array<{ file: string; line: number; column: number; severity: string; message: string; semantic_signature: string }>,
|
|
|
|
|
|
|
|
): Promise<string[]> {
|
|
|
|
|
|
|
|
if (!sink || diags.length === 0) return []
|
|
|
|
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
|
|
|
|
const ids: string[] = []
|
|
|
|
|
|
|
|
for (let i = 0; i < diags.length; i++) {
|
|
|
|
|
|
|
|
const d = diags[i]
|
|
|
|
|
|
|
|
const did = `diag_${cmd_id}_${i}`
|
|
|
|
|
|
|
|
ids.push(did)
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await sink.ingest({
|
|
|
|
|
|
|
|
id: `evt_${did}`, type: 'diagnostic.created', version: 1, timestamp: now,
|
|
|
|
|
|
|
|
session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
|
|
diagnostic_id: did, task_id: ctx.task_id || '', agent_id: ctx.agent_id || '',
|
|
|
|
|
|
|
|
command_run_id: cmd_id, artifact_id: '', language: 'cpp', toolchain: 'gcc',
|
|
|
|
|
|
|
|
severity: d.severity, file: d.file, line: d.line, column: d.column,
|
|
|
|
|
|
|
|
code: '', message: d.message, semantic_signature: d.semantic_signature, metadata: {},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} catch { /* diagnostic ingestion is best-effort */ }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ids
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async emit_evidence(sink: EventSink | undefined, ctx: CppContext, cmd_id: string, kind: string, extra: any): Promise<void> {
|
|
|
|
|
|
|
|
if (!sink || !ctx.task_id) return
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await sink.ingest({
|
|
|
|
|
|
|
|
id: `evt_evr_${cmd_id}`, type: 'evidence.created', version: 1,
|
|
|
|
|
|
|
|
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
|
|
evidence_ref_id: `evr_${cmd_id}`, kind, ref: `command_run:${cmd_id}`,
|
|
|
|
|
|
|
|
location_json: {}, claim: JSON.stringify(extra),
|
|
|
|
|
|
|
|
task_id: ctx.task_id, agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '',
|
|
|
|
|
|
|
|
command_run_id: cmd_id, artifact_id: '', diagnostic_id: '', message_id: '',
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} catch { /* best-effort */ }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private command_started(cmd_id: string, ctx: CppContext, command: string) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
id: `evt_${cmd_id}_started`, type: 'command.started', version: 1,
|
|
|
|
|
|
|
|
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: { command_run_id: cmd_id, task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', origin_message_id: '', tool_run_id: ctx.tool_run_id || '', command, cwd: ctx.project_root, metadata: {} }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private command_completed(cmd_id: string, ctx: CppContext, exit_code: number, duration_ms: number) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
id: `evt_${cmd_id}_completed`, type: 'command.completed', version: 1,
|
|
|
|
|
|
|
|
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: { command_run_id: cmd_id, exit_code, duration_ms, stdout_artifact_id: '', stderr_artifact_id: '', combined_artifact_id: '', diagnostic_ids: [], parsed_diagnostics_json: {}, metadata: {} }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private command_failed(cmd_id: string, ctx: CppContext, exit_code: number, duration_ms: number, stdout_artifact_id: string, stderr_artifact_id: string, diagnostic_ids: string[]) {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
id: `evt_${cmd_id}_failed`, type: 'command.failed', version: 1,
|
|
|
|
|
|
|
|
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
|
|
|
|
|
|
|
|
source: { kind: 'tool' }, route: ['cpp', 'tool'],
|
|
|
|
|
|
|
|
payload: { command_run_id: cmd_id, exit_code, duration_ms, stdout_artifact_id, stderr_artifact_id, combined_artifact_id: '', error: {}, evidence_refs: [], metadata: {} }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|