growqr-backend: route service sessions to workflows dashboard #4

Merged
puter merged 7 commits from feat/integration-new-ui-service into main 2026-06-01 16:19:13 +00:00
25 changed files with 6294 additions and 1225 deletions
Showing only changes of commit dd6ccf1141 - Show all commits

View File

@@ -47,13 +47,14 @@ services:
# Self-hosted Rivet engine. The backend's Rivet Kit client connects here.
# The unified user agent runs as a durable Rivet actor (changes.md §5).
rivet-engine:
image: rivetgg/engine:latest
image: rivetdev/engine:latest
container_name: growqr-rivet
ports:
- "6420:6420" # API
- "6421:6421" # Guard/edge
environment:
RIVET__AUTH__ADMIN_TOKEN: ${RIVET_ADMIN_TOKEN:-dev-admin-token}
RIVET__FILE_SYSTEM__PATH: /data
volumes:
- rivet-data:/data
restart: unless-stopped

View File

@@ -58,8 +58,10 @@ export const config = {
process.env.RESUME_SERVICE_URL ?? "http://localhost:8002",
matchmakingServiceUrl:
process.env.MATCHMAKING_SERVICE_URL ?? "http://localhost:8006",
growqrAppFrontendUrl:
process.env.GROWQR_APP_FRONTEND_URL ?? "http://localhost:3002",
workflowsDashboardUrl:
process.env.WORKFLOWS_DASHBOARD_URL ??
process.env.FRONTEND_ORIGIN ??
"http://localhost:3000",
// ── Central Gitea (one org-wide instance, changes.md §2A) ──
giteaUrl: process.env.GITEA_URL ?? "http://127.0.0.1:3001",

View File

@@ -245,7 +245,16 @@ export function chatRoutes() {
{ userId, goal: String(toolCall.arguments.goal ?? "general") },
);
if (toolResult.status === "ok") {
sessions.push({ moduleId: "resume", moduleName: "Resume Agent", status: "done", summary: toolResult.summary });
const detail = toolResult.detail as Record<string, unknown> | undefined;
sessions.push({
moduleId: "resume",
moduleName: "Resume Agent",
status: "done",
sessionUrl: typeof detail?.ui_session_url === "string"
? detail.ui_session_url
: buildServiceSessionUrl("resume-service", detail, String(toolCall.arguments.goal ?? "general")),
summary: toolResult.summary,
});
}
break;
}

View File

@@ -28,23 +28,29 @@ export function buildServiceSessionUrl(
detail: Record<string, unknown> | undefined,
goal?: string,
): string | undefined {
const base = config.workflowsDashboardUrl.replace(/\/$/, "");
const sessionId = detail?.session_id ?? detail?.sessionId;
if (!sessionId || typeof sessionId !== "string") return undefined;
const base = config.growqrAppFrontendUrl.replace(/\/$/, "");
const params = new URLSearchParams({ session_id: sessionId });
const params = new URLSearchParams();
if (sessionId && typeof sessionId === "string") params.set("session_id", sessionId);
if (goal) params.set("goal", goal);
if (service === "interview-service") {
if (!sessionId || typeof sessionId !== "string") return undefined;
params.set("role", String(detail?.target_role ?? goal ?? "Interview practice"));
params.set("type", String(detail?.interview_type ?? "behavioral"));
return `${base}/service-sessions/interview?${params.toString()}`;
return `${base}/v2/service-sessions/interview?${params.toString()}`;
}
if (service === "roleplay-service") {
if (!sessionId || typeof sessionId !== "string") return undefined;
params.set("role", String(detail?.target_role ?? goal ?? "Roleplay practice"));
params.set("type", String(detail?.roleplay_type ?? "custom"));
return `${base}/service-sessions/roleplay?${params.toString()}`;
return `${base}/v2/service-sessions/roleplay?${params.toString()}`;
}
if (service === "resume-service") {
if (goal) params.set("role", goal);
return `${base}/v2/service-sessions/resume${params.size ? `?${params.toString()}` : ""}`;
}
return undefined;
@@ -315,6 +321,7 @@ async function runResumeTailor(ctx: ServiceAgentContext): Promise<ServiceAgentRe
detail: {
...(stateResult.detail as Record<string, unknown> ?? {}),
goal: ctx.goal,
ui_session_url: buildServiceSessionUrl("resume-service", undefined, ctx.goal),
recommendation: "Use the AI analysis and copilot tools to tailor bullet points, add missing keywords, and optimize for ATS.",
},
};