feat: upgrade investigation and presentation runtime
Generic Container CI/CD / test-build-publish (push) Successful in 1m6s
Agent CI/CD / build-test-publish-and-deploy (push) Successful in 1m7s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent 3b52a0ddab
commit 6336157ff4
104 changed files with 6541 additions and 961 deletions
+45 -30
View File
@@ -7,7 +7,7 @@ import type { McpQueryClient, QueryOwner } from "../product/mcp-query-client.js"
import { localQueryDescriptions, localQueryInputs } from "./local-query-tools.js";
import { allowedQueryTools } from "../runtime/query-mcp-policy.js";
import { localEvidenceResult } from "./evidence-result.js";
import { nativeQueryTimeout, queryTimeouts } from "./query-timeouts.js";
import { boundedQuery, MAX_NATIVE_QUERY_TIMEOUT_MS, modelQueryTool, nativeQueryTimeout, queryTimeouts } from "./query-timeouts.js";
import { saveMcpArtifact } from "./artifact-files.js";
export type LocalQueryName = typeof LOCAL_QUERY_TOOLS[number];
@@ -24,39 +24,46 @@ export function createQueryMcpHandler(options: {
inputSchema: z.toJSONSchema(localQueryInputs[name], { io: "input" }) as Tool["inputSchema"], annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: name === "geocode" } }))];
const handler = createMcpHandler(() => {
const server = new McpServer({ name: "tjwater-agent-queries", version: "1.0.0" });
for (const definition of definitions) {
for (const originalDefinition of definitions) {
const definition = modelQueryTool(originalDefinition);
server.registerTool(definition.name, { ...definition, inputSchema: fromJsonSchema<Record<string, unknown>>(definition.inputSchema as Parameters<typeof fromJsonSchema>[0]), outputSchema: definition.outputSchema ? fromJsonSchema(definition.outputSchema) : undefined }, async (args, request) => {
const id = request.mcpReq._meta?.["ai.opencode/sessionID"];
if (typeof id !== "string" || !id || id.length > 128) return rejected("Authenticated native session metadata required");
const direct = getRuntimeSessionContext(id);
const owner = direct ? { context: direct, runtimeSessionId: id, taskId: direct.taskId } : await options.resolveReader(id);
if (!owner || !allowedQueryTools(owner.context, !direct).includes(queryMcpToolId(definition.name))) return rejected("Query unavailable for this session");
try {
const budget = queryTimeouts(definition, args);
const budget = queryTimeouts(originalDefinition);
const signal = AbortSignal.any([request.mcpReq.signal, AbortSignal.timeout(budget.requestMs)]);
if (productNames.has(definition.name)) {
if (!options.productClient) return rejected("Product MCP is disabled");
const query = definition.name === "get_task_context" && args.report_revision === undefined && owner.context.reportRevision
? { ...args, report_revision: owner.context.reportRevision } : args;
// Native JSON-RPC IDs restart on reconnect. Resolve the persisted
// native tool call from host history, never from model arguments.
const nativeCallId = await options.resolveNativeCall?.(id, definition.name, args);
const requiresIdentity = (PRODUCT_MCP_QUEUED_TOOLS as readonly string[]).includes(definition.name)
&& (!["save_simulation_scenario", "create_sensor_placement"].includes(definition.name) || args.dry_run === false);
if (requiresIdentity && !nativeCallId) return rejected("Native tool call identity unavailable; check calculation jobs before retrying");
const callId = createHash("sha256").update(JSON.stringify([
owner.context.actorKey, owner.context.projectKey, id, definition.name, nativeCallId ?? randomUUID(),
])).digest("hex");
const result = await options.productClient.query(id, owner, definition.name, query, callId, signal);
if (["export_project_model", "export_sensor_placement"].includes(definition.name) && !result.isError) {
if (!options.artifactWorkspace) return rejected("Export workspace unavailable");
return await saveMcpArtifact(result, owner.context, await options.artifactWorkspace(owner.context));
return await boundedQuery(async () => {
const direct = getRuntimeSessionContext(id);
const owner = direct ? { context: direct, runtimeSessionId: id, taskId: direct.taskId } : await options.resolveReader(id);
signal.throwIfAborted();
if (!owner || !allowedQueryTools(owner.context, !direct).includes(queryMcpToolId(definition.name))) return rejected("Query unavailable for this session");
if (productNames.has(definition.name)) {
if (!options.productClient) return rejected("Product MCP is disabled");
const query = definition.name === "get_task_context" && args.report_revision === undefined && owner.context.reportRevision
? { ...args, report_revision: owner.context.reportRevision } : args;
// Native JSON-RPC IDs restart on reconnect. Resolve the persisted
// native tool call from host history, never from model arguments.
const nativeCallId = await options.resolveNativeCall?.(id, definition.name, args);
signal.throwIfAborted();
const requiresIdentity = (PRODUCT_MCP_QUEUED_TOOLS as readonly string[]).includes(definition.name)
&& (!["save_simulation_scenario", "create_sensor_placement"].includes(definition.name) || args.dry_run === false);
if (requiresIdentity && !nativeCallId) return rejected("Native tool call identity unavailable; check calculation jobs before retrying");
const callId = createHash("sha256").update(JSON.stringify([
owner.context.actorKey, owner.context.projectKey, id, definition.name, nativeCallId ?? randomUUID(),
])).digest("hex");
const result = await boundedQuery((boundedSignal) => options.productClient!.query(id, owner, definition.name, query, callId, boundedSignal), signal);
if (["export_project_model", "export_sensor_placement"].includes(definition.name) && !result.isError) {
if (!options.artifactWorkspace) return rejected("Export workspace unavailable");
const workspace = await options.artifactWorkspace(owner.context);
signal.throwIfAborted();
return await saveMcpArtifact(result, owner.context, workspace);
}
return localEvidenceResult(result);
}
return localEvidenceResult(result);
}
const value = await options.executeLocal(definition.name as LocalQueryName, args, owner.context, signal);
signal.throwIfAborted();
return { content: [{ type: "text", text: JSON.stringify(value) }] };
const value = await boundedQuery((boundedSignal) => options.executeLocal(definition.name as LocalQueryName, args, owner.context, boundedSignal), signal);
signal.throwIfAborted();
return { content: [{ type: "text", text: JSON.stringify(value) }] };
}, signal);
} catch (cause) {
// Never return upstream bodies, headers or credentials across MCP.
const status = cause && typeof cause === "object" && "status" in cause ? cause.status : undefined;
@@ -88,8 +95,16 @@ const rejected = (message: string) => ({ content: [{ type: "text" as const, text
export function startQueryMcpServer(options: Parameters<typeof createQueryMcpHandler>[0]) {
const handler = createQueryMcpHandler(options);
const server = Bun.serve({ hostname: "127.0.0.1", port: 0, idleTimeout: 60,
fetch: (request) => handler.fetch(request),
const server = Bun.serve({ hostname: "127.0.0.1", port: 0, // Bun caps socket idleTimeout at 255 seconds. Enforce a finite five-minute
// HTTP response deadline explicitly so the advertised 300s budget can finish.
idleTimeout: 0,
fetch: async (request) => {
try {
return await boundedQuery((signal) => handler.fetch(new Request(request, { signal })), AbortSignal.any([request.signal, AbortSignal.timeout(MAX_NATIVE_QUERY_TIMEOUT_MS)]));
} catch {
return new Response("Query timed out", { status: 504 });
}
},
});
return { url: `http://127.0.0.1:${server.port}/mcp`, executionTimeoutMs: handler.executionTimeoutMs,
async close() { await handler.close(); await server.stop(true); } };