Centralize session scope, chat projections and typed task bridges. Keep historical interaction replies authoritative and publish terminal state to subscribers. Validate Server contracts before replacing generated artifacts.
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
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 { readTaskBridgeContext, replyTaskBridgeError } from "./task-bridge-context.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 bridge = readTaskBridgeContext(req, options.internalToken);
|
|
if (!bridge) {
|
|
res.status(403).json({ message: "operational task context unavailable" });
|
|
return;
|
|
}
|
|
const { context, runtimeSessionId: sessionId, taskId } = bridge;
|
|
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, 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, 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) {
|
|
replyTaskBridgeError(res, error, `task ${kind} publish failed`);
|
|
}
|
|
});
|
|
}
|
|
}
|