feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { type Express } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
import { isTrustedInternalBridgeRequest } from "../product/internal-bridge-auth.js";
|
||||
import { operationalTaskClient, type OperationalTaskClient, taskArtifactBridgeSchema } from "../product/operational-task-client.js";
|
||||
import { taskPresentationBridgeSchema } from "../product/task-presentation.js";
|
||||
import { getRuntimeSessionContext } from "../runtime/sessionContext.js";
|
||||
|
||||
const artifactsInputSchema = taskArtifactBridgeSchema.and(z.object({ session_id: z.string().trim().min(1).max(128) }));
|
||||
|
||||
export function registerTaskPublicationBridge(app: Express, options: {
|
||||
internalToken: string;
|
||||
client?: Pick<OperationalTaskClient, "publishArtifacts" | "publishPresentation">;
|
||||
}) {
|
||||
const client = options.client ?? operationalTaskClient;
|
||||
for (const kind of ["artifacts", "presentation"] as const) {
|
||||
app.post(`/internal/operational-task/${kind}`, async (req, res) => {
|
||||
if (!isTrustedInternalBridgeRequest(req, options.internalToken)) {
|
||||
res.status(403).json({ message: "forbidden" });
|
||||
return;
|
||||
}
|
||||
const sessionId = typeof req.body?.session_id === "string" ? req.body.session_id.trim() : "";
|
||||
const context = sessionId ? getRuntimeSessionContext(sessionId) : null;
|
||||
if (!context?.taskId) {
|
||||
res.status(403).json({ message: "operational task context unavailable" });
|
||||
return;
|
||||
}
|
||||
if ((kind === "presentation" || req.body?.progress !== undefined) && context.purpose !== "task_execution") {
|
||||
res.status(403).json({ message: "task execution context required" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (kind === "presentation") {
|
||||
const parsed = taskPresentationBridgeSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: "invalid task presentation", detail: parsed.error.flatten() });
|
||||
return;
|
||||
}
|
||||
res.json(await client.publishPresentation(sessionId, context.taskId, {
|
||||
requestId: parsed.data.request_id,
|
||||
sourceSessionId: context.clientSessionId,
|
||||
command: parsed.data.command,
|
||||
}));
|
||||
} else {
|
||||
const parsed = artifactsInputSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: "invalid task artifacts", detail: parsed.error.flatten() });
|
||||
return;
|
||||
}
|
||||
res.json(await client.publishArtifacts(sessionId, context.taskId, {
|
||||
requestId: parsed.data.request_id,
|
||||
sourceSessionId: context.clientSessionId,
|
||||
evidence: parsed.data.evidence,
|
||||
executionPlan: parsed.data.execution_plan,
|
||||
report: parsed.data.report,
|
||||
progress: parsed.data.progress,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
const status = error && typeof error === "object" && "status" in error && typeof error.status === "number" ? error.status : 502;
|
||||
res.status(status).json({ message: error instanceof Error ? error.message : `task ${kind} publish failed` });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user