/** * MCP gno_job_status tool - job status lookup. * * @module src/mcp/tools/job-status */ import type { JobRecord, JobResult } from "../../core/job-manager"; import type { SyncResult } from "../../ingestion"; import type { ToolContext } from "../server"; import { runToolNoMutex, type ToolResult } from "./index"; interface JobStatusInput { jobId: string; } interface JobStatusResult { jobId: string; type: JobRecord["type"]; status: JobRecord["status"]; startedAt: string; completedAt?: string; /** @deprecated Use typedResult for new job types */ result?: SyncResult; typedResult?: JobResult; error?: string; serverInstanceId: string; } function formatJobStatus(result: JobStatusResult): string { const lines: string[] = []; lines.push(`Job: ${result.jobId}`); lines.push(`Type: ${result.type}`); lines.push(`Status: ${result.status}`); lines.push(`Started: ${result.startedAt}`); if (result.completedAt) { lines.push(`Completed: ${result.completedAt}`); } if (result.error) { lines.push(`Error: ${result.error}`); } // Handle typed results (embed, index jobs) if (result.typedResult) { switch (result.typedResult.kind) { case "embed": lines.push( `Embedded: ${result.typedResult.value.embedded} chunks, ${result.typedResult.value.errors} errors` ); break; case "index": { const { sync, embed } = result.typedResult.value; lines.push( `Sync: ${sync.totalFilesAdded} added, ${sync.totalFilesUpdated} updated, ${sync.totalFilesErrored} errors` ); lines.push(`Embed: ${embed.embedded} chunks, ${embed.errors} errors`); lines.push(`Duration: ${sync.totalDurationMs}ms`); break; } case "sync": lines.push( `Total: ${result.typedResult.value.totalFilesAdded} added, ${result.typedResult.value.totalFilesUpdated} updated, ` + `${result.typedResult.value.totalFilesErrored} errors` ); lines.push(`Duration: ${result.typedResult.value.totalDurationMs}ms`); break; } } else if (result.result) { // Legacy sync result lines.push( `Total: ${result.result.totalFilesAdded} added, ${result.result.totalFilesUpdated} updated, ` + `${result.result.totalFilesErrored} errors` ); lines.push(`Duration: ${result.result.totalDurationMs}ms`); } return lines.join("\n"); } function toJobStatusResult(job: JobRecord): JobStatusResult { return { jobId: job.id, type: job.type, status: job.status, startedAt: new Date(job.startedAt).toISOString(), completedAt: job.completedAt ? new Date(job.completedAt).toISOString() : undefined, result: job.result, typedResult: job.typedResult, error: job.error, serverInstanceId: job.serverInstanceId, }; } export function handleJobStatus( args: JobStatusInput, ctx: ToolContext ): Promise { // Use runToolNoMutex - job status is in-memory only, should not block return runToolNoMutex( ctx, "gno_job_status", async () => { const job = ctx.jobManager.getJob(args.jobId); if (!job) { throw new Error(`NOT_FOUND: Job not found: ${args.jobId}`); } return toJobStatusResult(job); }, formatJobStatus ); }