import fs from 'fs'; import path from 'path'; import { ARTIFACT_EVIDENCE_KINDS, DB_EVIDENCE_ACTIONS, DEPLOY_EVIDENCE_ACTIONS, GITHUB_EVIDENCE_ACTIONS, HOST_EVIDENCE_ACTIONS, type HostEvidenceAction, } from 'ejclaw-runners-shared'; export { ARTIFACT_EVIDENCE_KINDS, DB_EVIDENCE_ACTIONS, DEPLOY_EVIDENCE_ACTIONS, GITHUB_EVIDENCE_ACTIONS, HOST_EVIDENCE_ACTIONS, type HostEvidenceAction, }; export interface HostEvidenceResponse { requestId: string; ok: boolean; action: HostEvidenceAction; command: string; stdout: string; stderr: string; exitCode: number; error?: string; } const DEFAULT_LOG_TAIL_LINES = 20; const MAX_LOG_TAIL_LINES = 200; export function normalizeHostEvidenceTailLines(value?: number): number { if (!Number.isFinite(value)) { return DEFAULT_LOG_TAIL_LINES; } const normalized = Math.trunc(value as number); return Math.min(Math.max(normalized, 1), MAX_LOG_TAIL_LINES); } export function resolveHostEvidenceResponsesDir(hostIpcDir: string): string { return path.join(hostIpcDir, 'host-evidence-responses'); } export async function waitForHostEvidenceResponse( responseDir: string, requestId: string, options?: { timeoutMs?: number; pollMs?: number; }, ): Promise { const timeoutMs = options?.timeoutMs ?? 7_000; const pollMs = options?.pollMs ?? 100; const responsePath = path.join(responseDir, `${requestId}.json`); const deadline = Date.now() + timeoutMs; while (Date.now() <= deadline) { if (fs.existsSync(responsePath)) { const response = JSON.parse( fs.readFileSync(responsePath, 'utf-8'), ) as HostEvidenceResponse; fs.unlinkSync(responsePath); return response; } await new Promise((resolve) => setTimeout(resolve, pollMs)); } throw new Error(`Timed out waiting for host evidence response: ${requestId}`); } export function formatHostEvidenceResponse( response: HostEvidenceResponse, ): string { const parts = [ `Host evidence action: ${response.action}`, `Exit code: ${response.exitCode}`, response.command ? `$ ${response.command}` : null, response.stdout ? response.stdout.trimEnd() : null, response.stderr ? `[stderr]\n${response.stderr.trimEnd()}` : null, response.error ? `[error] ${response.error}` : null, ].filter((value): value is string => Boolean(value)); return parts.join('\n'); }