Compare commits
1 Commits
feat/integ
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 488fc1aeca |
@@ -2,6 +2,7 @@
|
|||||||
id: job-search
|
id: job-search
|
||||||
name: Job Search Agent
|
name: Job Search Agent
|
||||||
role: Opportunity Scout
|
role: Opportunity Scout
|
||||||
|
service: matchmaking-service
|
||||||
tools:
|
tools:
|
||||||
- search_jobs
|
- search_jobs
|
||||||
- rank_opportunities
|
- rank_opportunities
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
getSubAgentModules,
|
getSubAgentModules,
|
||||||
} from "../lib/prompt-loader.js";
|
} from "../lib/prompt-loader.js";
|
||||||
import {
|
import {
|
||||||
|
buildServiceSessionUrl,
|
||||||
runServiceAgentProbe,
|
runServiceAgentProbe,
|
||||||
type ServiceAgentResult,
|
type ServiceAgentResult,
|
||||||
} from "../services/service-agents.js";
|
} from "../services/service-agents.js";
|
||||||
@@ -532,11 +533,9 @@ export const userActor = actor({
|
|||||||
moduleName: m.name,
|
moduleName: m.name,
|
||||||
status: m.status,
|
status: m.status,
|
||||||
sessionId: detail?.session_id as string | undefined,
|
sessionId: detail?.session_id as string | undefined,
|
||||||
sessionUrl: m.service === "interview-service"
|
sessionUrl: typeof detail?.ui_session_url === "string"
|
||||||
? `http://localhost:8007/api/v1/demo?session_id=${detail?.session_id ?? ""}`
|
? detail.ui_session_url
|
||||||
: m.service === "roleplay-service"
|
: buildServiceSessionUrl(m.service, detail, c.state.workflowGoal),
|
||||||
? `http://localhost:8008/api/v1/demo?session_id=${detail?.session_id ?? ""}`
|
|
||||||
: undefined,
|
|
||||||
summary: m.lastResult?.summary,
|
summary: m.lastResult?.summary,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ export const config = {
|
|||||||
process.env.QSCORE_SERVICE_URL ?? "http://localhost:8000",
|
process.env.QSCORE_SERVICE_URL ?? "http://localhost:8000",
|
||||||
resumeServiceUrl:
|
resumeServiceUrl:
|
||||||
process.env.RESUME_SERVICE_URL ?? "http://localhost:8002",
|
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) ──
|
// ── Central Gitea (one org-wide instance, changes.md §2A) ──
|
||||||
giteaUrl: process.env.GITEA_URL ?? "http://127.0.0.1:3001",
|
giteaUrl: process.env.GITEA_URL ?? "http://127.0.0.1:3001",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export type SubAgentModule = {
|
|||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
description: 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[];
|
toolNames: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +122,8 @@ export async function loadPromptsFromDisk(): Promise<void> {
|
|||||||
service !== "interview-service" &&
|
service !== "interview-service" &&
|
||||||
service !== "roleplay-service" &&
|
service !== "roleplay-service" &&
|
||||||
service !== "qscore-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");
|
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,
|
role: data.role ?? data.name,
|
||||||
description: body || `Agent module: ${data.name}`,
|
description: body || `Agent module: ${data.name}`,
|
||||||
service: service &&
|
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"])
|
? (service as SubAgentModule["service"])
|
||||||
: undefined,
|
: undefined,
|
||||||
toolNames: data.tools ?? [],
|
toolNames: data.tools ?? [],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { LlmMessage } from "../lib/llm.js";
|
|||||||
import { createChatCompletion } from "../lib/llm.js";
|
import { createChatCompletion } from "../lib/llm.js";
|
||||||
import { buildUnifiedSystemPrompt } from "../agents/catalog.js";
|
import { buildUnifiedSystemPrompt } from "../agents/catalog.js";
|
||||||
import {
|
import {
|
||||||
|
buildServiceSessionUrl,
|
||||||
runServiceAgentProbe,
|
runServiceAgentProbe,
|
||||||
type ServiceAgentResult,
|
type ServiceAgentResult,
|
||||||
} from "../services/service-agents.js";
|
} from "../services/service-agents.js";
|
||||||
@@ -210,7 +211,9 @@ export function chatRoutes() {
|
|||||||
moduleName: "Sara",
|
moduleName: "Sara",
|
||||||
status: "done",
|
status: "done",
|
||||||
sessionId: detail.session_id as string,
|
sessionId: detail.session_id as string,
|
||||||
sessionUrl: `http://localhost:8007/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,
|
summary: toolResult.summary,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -228,7 +231,9 @@ export function chatRoutes() {
|
|||||||
moduleName: "Emily",
|
moduleName: "Emily",
|
||||||
status: "done",
|
status: "done",
|
||||||
sessionId: detail.session_id as string,
|
sessionId: detail.session_id as string,
|
||||||
sessionUrl: `http://localhost:8008/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,
|
summary: toolResult.summary,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -240,7 +245,16 @@ export function chatRoutes() {
|
|||||||
{ userId, goal: String(toolCall.arguments.goal ?? "general") },
|
{ userId, goal: String(toolCall.arguments.goal ?? "general") },
|
||||||
);
|
);
|
||||||
if (toolResult.status === "ok") {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,39 @@ export type ServiceAgentContext = {
|
|||||||
goal: string;
|
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 {
|
function stableUuid(input: string): string {
|
||||||
const hex = createHash("sha256").update(input).digest("hex").slice(0, 32);
|
const hex = createHash("sha256").update(input).digest("hex").slice(0, 32);
|
||||||
return [
|
return [
|
||||||
@@ -97,7 +130,12 @@ async function runSaraInterview(ctx: ServiceAgentContext): Promise<ServiceAgentR
|
|||||||
return {
|
return {
|
||||||
status: "ok",
|
status: "ok",
|
||||||
summary: `Sara created interview session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
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 {
|
return {
|
||||||
status: "ok",
|
status: "ok",
|
||||||
summary: `Emily created roleplay session ${detail.session_id ?? "(pending id)"} for ${ctx.goal}.`,
|
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: {
|
detail: {
|
||||||
...(stateResult.detail as Record<string, unknown> ?? {}),
|
...(stateResult.detail as Record<string, unknown> ?? {}),
|
||||||
goal: ctx.goal,
|
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.",
|
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(
|
export async function runServiceAgentProbe(
|
||||||
agent: ServiceAgentRef,
|
agent: ServiceAgentRef,
|
||||||
ctx?: ServiceAgentContext,
|
ctx?: ServiceAgentContext,
|
||||||
@@ -311,6 +395,10 @@ export async function runServiceAgentProbe(
|
|||||||
return ctx
|
return ctx
|
||||||
? await runResumeTailor(ctx)
|
? await runResumeTailor(ctx)
|
||||||
: healthCheck(config.resumeServiceUrl, "Resume Agent / resume-service");
|
: healthCheck(config.resumeServiceUrl, "Resume Agent / resume-service");
|
||||||
|
case "matchmaking-service":
|
||||||
|
return ctx
|
||||||
|
? await runMatchmaking(ctx)
|
||||||
|
: healthCheck(config.matchmakingServiceUrl, "Scout / matchmaking-service");
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
status: "local",
|
status: "local",
|
||||||
|
|||||||
Reference in New Issue
Block a user