merge: pull origin/main into chore/release (resolve conflicts)
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
id: job-search
|
||||
name: Job Search Agent
|
||||
role: Opportunity Scout
|
||||
service: matchmaking-service
|
||||
tools:
|
||||
- search_jobs
|
||||
- rank_opportunities
|
||||
- prepare_shortlist
|
||||
---
|
||||
|
||||
## Domain
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getSubAgentModules,
|
||||
} from "../lib/prompt-loader.js";
|
||||
import {
|
||||
buildServiceSessionUrl,
|
||||
runServiceAgentProbe,
|
||||
type ServiceAgentResult,
|
||||
} from "../services/service-agents.js";
|
||||
@@ -538,11 +539,9 @@ export const userActor = actor({
|
||||
moduleName: m.name,
|
||||
status: m.status,
|
||||
sessionId: detail?.session_id as string | undefined,
|
||||
sessionUrl: m.service === "interview-service"
|
||||
? `${config.interviewPublicUrl.replace(/\/$/, "")}/api/v1/demo?session_id=${detail?.session_id ?? ""}`
|
||||
: m.service === "roleplay-service"
|
||||
? `${config.roleplayPublicUrl.replace(/\/$/, "")}/api/v1/demo?session_id=${detail?.session_id ?? ""}`
|
||||
: undefined,
|
||||
sessionUrl: typeof detail?.ui_session_url === "string"
|
||||
? detail.ui_session_url
|
||||
: buildServiceSessionUrl(m.service, detail, c.state.workflowGoal),
|
||||
summary: m.lastResult?.summary,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -62,6 +62,12 @@ export const config = {
|
||||
process.env.RESUME_SERVICE_URL ?? "http://localhost:8002",
|
||||
resumePublicUrl:
|
||||
process.env.RESUME_PUBLIC_URL ?? process.env.RESUME_SERVICE_URL ?? "http://localhost:8002",
|
||||
matchmakingServiceUrl:
|
||||
process.env.MATCHMAKING_SERVICE_URL ?? "http://localhost:8006",
|
||||
workflowsDashboardUrl:
|
||||
process.env.WORKFLOWS_DASHBOARD_URL ??
|
||||
process.env.FRONTEND_ORIGIN ??
|
||||
"http://localhost:3000",
|
||||
|
||||
// ── Central Gitea (one org-wide instance, changes.md §2A) ──
|
||||
// Public URL is what Git remotes should use and what OpenCode containers see.
|
||||
|
||||
@@ -9,7 +9,7 @@ export type SubAgentModule = {
|
||||
name: string;
|
||||
role: string;
|
||||
description: string;
|
||||
service?: "interview-service" | "roleplay-service" | "qscore-service" | "resume-service";
|
||||
service?: "interview-service" | "roleplay-service" | "qscore-service" | "resume-service" | "matchmaking-service";
|
||||
toolNames: string[];
|
||||
};
|
||||
|
||||
@@ -122,7 +122,8 @@ export async function loadPromptsFromDisk(): Promise<void> {
|
||||
service !== "interview-service" &&
|
||||
service !== "roleplay-service" &&
|
||||
service !== "qscore-service" &&
|
||||
service !== "resume-service"
|
||||
service !== "resume-service" &&
|
||||
service !== "matchmaking-service"
|
||||
) {
|
||||
log.warn({ file: filename, service }, "unknown service value — treating as no service");
|
||||
}
|
||||
@@ -133,7 +134,7 @@ export async function loadPromptsFromDisk(): Promise<void> {
|
||||
role: data.role ?? data.name,
|
||||
description: body || `Agent module: ${data.name}`,
|
||||
service: service &&
|
||||
["interview-service", "roleplay-service", "qscore-service", "resume-service"].includes(service)
|
||||
["interview-service", "roleplay-service", "qscore-service", "resume-service", "matchmaking-service"].includes(service)
|
||||
? (service as SubAgentModule["service"])
|
||||
: undefined,
|
||||
toolNames: data.tools ?? [],
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { LlmMessage } from "../lib/llm.js";
|
||||
import { createChatCompletion } from "../lib/llm.js";
|
||||
import { buildUnifiedSystemPrompt } from "../agents/catalog.js";
|
||||
import {
|
||||
buildServiceSessionUrl,
|
||||
runServiceAgentProbe,
|
||||
type ServiceAgentResult,
|
||||
} from "../services/service-agents.js";
|
||||
@@ -183,7 +184,9 @@ export function chatRoutes() {
|
||||
moduleName: "Sara",
|
||||
status: "done",
|
||||
sessionId: detail.session_id as string,
|
||||
sessionUrl: `${config.interviewPublicUrl.replace(/\/$/, "")}/api/v1/demo?session_id=${detail.session_id ?? ""}`,
|
||||
sessionUrl: typeof detail.ui_session_url === "string"
|
||||
? detail.ui_session_url
|
||||
: buildServiceSessionUrl("interview-service", detail, String(toolCall.arguments.target_role ?? "general preparation")),
|
||||
summary: toolResult.summary,
|
||||
});
|
||||
}
|
||||
@@ -201,7 +204,9 @@ export function chatRoutes() {
|
||||
moduleName: "Emily",
|
||||
status: "done",
|
||||
sessionId: detail.session_id as string,
|
||||
sessionUrl: `${config.roleplayPublicUrl.replace(/\/$/, "")}/api/v1/demo?session_id=${detail.session_id ?? ""}`,
|
||||
sessionUrl: typeof detail.ui_session_url === "string"
|
||||
? detail.ui_session_url
|
||||
: buildServiceSessionUrl("roleplay-service", detail, String(toolCall.arguments.goal ?? "general practice",
|
||||
summary: toolResult.summary,
|
||||
});
|
||||
}
|
||||
@@ -213,7 +218,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;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,39 @@ export type ServiceAgentContext = {
|
||||
goal: string;
|
||||
};
|
||||
|
||||
export function buildServiceSessionUrl(
|
||||
service: string | undefined,
|
||||
detail: Record<string, unknown> | undefined,
|
||||
goal?: string,
|
||||
): string | undefined {
|
||||
const base = config.workflowsDashboardUrl.replace(/\/$/, "");
|
||||
const sessionId = detail?.session_id ?? detail?.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}/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}/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;
|
||||
}
|
||||
|
||||
function stableUuid(input: string): string {
|
||||
const hex = createHash("sha256").update(input).digest("hex").slice(0, 32);
|
||||
return [
|
||||
@@ -97,7 +130,12 @@ async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentR
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Sara created interview session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
detail,
|
||||
detail: {
|
||||
...detail,
|
||||
target_role: payload.context.target_role,
|
||||
interview_type: payload.interview_type,
|
||||
ui_session_url: buildServiceSessionUrl("interview-service", detail, ctx.goal),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,7 +174,12 @@ async function runEmilyRoleplay(ctx: ServiceAgentContext): Promise<ServiceAgentR
|
||||
return {
|
||||
status: "ok",
|
||||
summary: `Emily created roleplay session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
||||
detail,
|
||||
detail: {
|
||||
...detail,
|
||||
target_role: payload.metadata.target_role,
|
||||
roleplay_type: payload.roleplay_type,
|
||||
ui_session_url: buildServiceSessionUrl("roleplay-service", detail, ctx.goal),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -278,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.",
|
||||
},
|
||||
};
|
||||
@@ -289,6 +333,46 @@ async function runResumeTailor(ctx: ServiceAgentContext): Promise<ServiceAgentRe
|
||||
}
|
||||
}
|
||||
|
||||
async function runMatchmaking(ctx: ServiceAgentContext): Promise<ServiceAgentResult> {
|
||||
const matchmakingUserId = stableUuid(ctx.userId);
|
||||
const query = new URLSearchParams({
|
||||
user_id: matchmakingUserId,
|
||||
top_n: "6",
|
||||
threshold: "60",
|
||||
recompute: "true",
|
||||
});
|
||||
|
||||
try {
|
||||
const detail = await serviceJson<Record<string, unknown>>(
|
||||
config.matchmakingServiceUrl,
|
||||
`/api/v1/feed?${query.toString()}`,
|
||||
{ method: "GET" },
|
||||
);
|
||||
const items = Array.isArray(detail.items)
|
||||
? detail.items
|
||||
: Array.isArray(detail.feed_items)
|
||||
? detail.feed_items
|
||||
: [];
|
||||
|
||||
return {
|
||||
status: "ok",
|
||||
summary: items.length > 0
|
||||
? `Scout pulled ${items.length} ranked opportunities from matchmaking for ${ctx.goal}.`
|
||||
: `Scout asked matchmaking to refresh opportunities for ${ctx.goal}. Add preferences if the feed is empty.`,
|
||||
detail: {
|
||||
...detail,
|
||||
matchmaking_user_id: matchmakingUserId,
|
||||
goal: ctx.goal,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
status: "unavailable",
|
||||
summary: `Matchmaking service unavailable: ${err instanceof Error ? err.message : String(err)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function runServiceAgentProbe(
|
||||
agent: ServiceAgentRef,
|
||||
ctx?: ServiceAgentContext,
|
||||
@@ -311,6 +395,10 @@ export async function runServiceAgentProbe(
|
||||
return ctx
|
||||
? await runResumeTailor(ctx)
|
||||
: healthCheck(config.resumeServiceUrl, "Resume Agent / resume-service");
|
||||
case "matchmaking-service":
|
||||
return ctx
|
||||
? await runMatchmaking(ctx)
|
||||
: healthCheck(config.matchmakingServiceUrl, "Scout / matchmaking-service");
|
||||
default:
|
||||
return {
|
||||
status: "manual_required",
|
||||
|
||||
Reference in New Issue
Block a user