@linkegringo/mcp 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +37 -10
- package/dist/index.js +349 -56
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -392,8 +392,7 @@ declare function handleGetPendingJob(input?: GetPendingJobInput): Promise<{
|
|
|
392
392
|
}[];
|
|
393
393
|
structuredData: {
|
|
394
394
|
jobFound: boolean;
|
|
395
|
-
|
|
396
|
-
job?: undefined;
|
|
395
|
+
job: BridgeJob<any, any>;
|
|
397
396
|
};
|
|
398
397
|
} | {
|
|
399
398
|
content: {
|
|
@@ -402,13 +401,7 @@ declare function handleGetPendingJob(input?: GetPendingJobInput): Promise<{
|
|
|
402
401
|
}[];
|
|
403
402
|
structuredData: {
|
|
404
403
|
jobFound: boolean;
|
|
405
|
-
|
|
406
|
-
id: string;
|
|
407
|
-
type: JobType;
|
|
408
|
-
payload: any;
|
|
409
|
-
createdAt: number;
|
|
410
|
-
};
|
|
411
|
-
pendingCount?: undefined;
|
|
404
|
+
pendingCount: number;
|
|
412
405
|
};
|
|
413
406
|
}>;
|
|
414
407
|
|
|
@@ -467,4 +460,38 @@ declare function handleSubmitJobResult(input: SubmitJobResultInput): Promise<{
|
|
|
467
460
|
};
|
|
468
461
|
}>;
|
|
469
462
|
|
|
470
|
-
|
|
463
|
+
declare const watchLinkeGringoInputSchema: z.ZodObject<{
|
|
464
|
+
timeoutSeconds: z.ZodOptional<z.ZodNumber>;
|
|
465
|
+
bridgeUrl: z.ZodOptional<z.ZodString>;
|
|
466
|
+
}, "strip", z.ZodTypeAny, {
|
|
467
|
+
timeoutSeconds?: number | undefined;
|
|
468
|
+
bridgeUrl?: string | undefined;
|
|
469
|
+
}, {
|
|
470
|
+
timeoutSeconds?: number | undefined;
|
|
471
|
+
bridgeUrl?: string | undefined;
|
|
472
|
+
}>;
|
|
473
|
+
type WatchLinkeGringoInput = z.infer<typeof watchLinkeGringoInputSchema>;
|
|
474
|
+
declare function waitForNextJob(bridgeUrl: string, timeoutMs: number): Promise<BridgeJob | null>;
|
|
475
|
+
declare function handleWatchLinkeGringo(input?: WatchLinkeGringoInput): Promise<{
|
|
476
|
+
content: {
|
|
477
|
+
type: "text";
|
|
478
|
+
text: string;
|
|
479
|
+
}[];
|
|
480
|
+
structuredData: {
|
|
481
|
+
jobFound: boolean;
|
|
482
|
+
job: BridgeJob<any, any>;
|
|
483
|
+
};
|
|
484
|
+
} | {
|
|
485
|
+
content: {
|
|
486
|
+
type: "text";
|
|
487
|
+
text: string;
|
|
488
|
+
}[];
|
|
489
|
+
structuredData: {
|
|
490
|
+
status: string;
|
|
491
|
+
timeoutSeconds: number;
|
|
492
|
+
pendingCount: number;
|
|
493
|
+
serverOnline: boolean;
|
|
494
|
+
};
|
|
495
|
+
}>;
|
|
496
|
+
|
|
497
|
+
export { type AuditIssue, type AuditProfileInput, type BridgeJob, type BridgeServerOptions, type ConvertToXyzBulletInput, type GenerateHeadlineInput, type GetPendingJobInput, type InstallResult, type InstallerOptions, type JobStatus, type JobType, type McpTarget, type SimulateRecruiterSearchInput, type SparseExperience, type SubmitJobResultInput, type WatchLinkeGringoInput, type WatcherOptions, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, handleWatchLinkeGringo, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, startBridgeWatcher, stopBridgeServer, submitJobResultInputSchema, waitForNextJob, watchLinkeGringoInputSchema };
|
package/dist/index.js
CHANGED
|
@@ -172,9 +172,9 @@ var DEFAULT_BRIDGE_URL = process.env.LINKEGRINGO_BRIDGE_URL || (process.env.NODE
|
|
|
172
172
|
async function getRemoteOrLocalPendingJob(jobId, bridgeUrl = DEFAULT_BRIDGE_URL) {
|
|
173
173
|
if (bridgeUrl) {
|
|
174
174
|
try {
|
|
175
|
-
const url = jobId ? `${bridgeUrl}/api/jobs
|
|
175
|
+
const url = jobId ? `${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}` : `${bridgeUrl}/api/jobs/pending`;
|
|
176
176
|
const controller = new AbortController();
|
|
177
|
-
const timer = setTimeout(() => controller.abort(),
|
|
177
|
+
const timer = setTimeout(() => controller.abort(), 15e3);
|
|
178
178
|
const res = await fetch(url, { signal: controller.signal });
|
|
179
179
|
clearTimeout(timer);
|
|
180
180
|
if (res.ok) {
|
|
@@ -192,7 +192,7 @@ async function completeRemoteOrLocalJob(jobId, result, bridgeUrl = DEFAULT_BRIDG
|
|
|
192
192
|
if (bridgeUrl) {
|
|
193
193
|
try {
|
|
194
194
|
const controller = new AbortController();
|
|
195
|
-
const timer = setTimeout(() => controller.abort(),
|
|
195
|
+
const timer = setTimeout(() => controller.abort(), 15e3);
|
|
196
196
|
const res = await fetch(`${bridgeUrl}/api/jobs/${encodeURIComponent(jobId)}/complete`, {
|
|
197
197
|
method: "POST",
|
|
198
198
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1256,26 +1256,10 @@ ${proposals.map(
|
|
|
1256
1256
|
|
|
1257
1257
|
// src/tools/get-pending-job.ts
|
|
1258
1258
|
import { z as z11 } from "zod";
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
const job = await getRemoteOrLocalPendingJob(input.jobId);
|
|
1264
|
-
if (!job) {
|
|
1265
|
-
return {
|
|
1266
|
-
content: [
|
|
1267
|
-
{
|
|
1268
|
-
type: "text",
|
|
1269
|
-
text: "Nenhum job pendente no momento na interface web do LinkeGringo."
|
|
1270
|
-
}
|
|
1271
|
-
],
|
|
1272
|
-
structuredData: {
|
|
1273
|
-
jobFound: false,
|
|
1274
|
-
pendingCount: bridgeJobStore.getPendingCount()
|
|
1275
|
-
}
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
bridgeJobStore.markProcessing(job.id);
|
|
1259
|
+
|
|
1260
|
+
// src/tools/format-job.ts
|
|
1261
|
+
function formatJobResponse(job) {
|
|
1262
|
+
let instructions = "";
|
|
1279
1263
|
let details = "";
|
|
1280
1264
|
if (job.type === "parse_and_diagnose") {
|
|
1281
1265
|
const p = job.payload || {};
|
|
@@ -1284,53 +1268,210 @@ async function handleGetPendingJob(input = {}) {
|
|
|
1284
1268
|
- **Cargo Almejado**: ${p.targetRole || "N\xE3o especificado"}
|
|
1285
1269
|
- **Possui PDF Base64**: ${Boolean(p.pdfBase64)} (${p.pdfBase64 ? Math.round(p.pdfBase64.length / 1024) + " KB base64" : "N/A"})
|
|
1286
1270
|
- **Possui Texto**: ${Boolean(p.pdfText)}
|
|
1271
|
+
`.trim();
|
|
1272
|
+
instructions = `
|
|
1273
|
+
### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
|
|
1274
|
+
1. **Zero Scripts**: N\xC3O execute scripts Python/Node no disco. N\xC3O salve arquivos no sistema de arquivos. N\xC3O inspecione arquivos locais de c\xF3digo.
|
|
1275
|
+
2. **Processamento Multimodal**: Analise o PDF/dados recebidos diretamente no seu racioc\xEDnio em 1 \xFAnico turno.
|
|
1276
|
+
3. **Submiss\xE3o Imediata**: Chame a ferramenta \`submit_job_result\` com o JSON estruturado no campo \`result\`.
|
|
1277
|
+
|
|
1278
|
+
### \u{1F4CB} Estrutura JSON Exata para \`submit_job_result\`:
|
|
1279
|
+
\`\`\`json
|
|
1280
|
+
{
|
|
1281
|
+
"jobId": "${job.id}",
|
|
1282
|
+
"result": {
|
|
1283
|
+
"profile": {
|
|
1284
|
+
"firstName": "Nome",
|
|
1285
|
+
"lastName": "Sobrenome",
|
|
1286
|
+
"headline": "Role Anchor | Core Techs | Scale/Impact | US Remote",
|
|
1287
|
+
"location": "Cidade, Pa\xEDs",
|
|
1288
|
+
"summary": "Resumo profissional com foco em impacto t\xE9cnico...",
|
|
1289
|
+
"experiences": [
|
|
1290
|
+
{
|
|
1291
|
+
"title": "Senior Software Engineer",
|
|
1292
|
+
"companyName": "Nome da Empresa",
|
|
1293
|
+
"workplaceType": "remote",
|
|
1294
|
+
"location": "Local",
|
|
1295
|
+
"current": true,
|
|
1296
|
+
"startDate": { "year": 2022, "month": 1 },
|
|
1297
|
+
"endDate": undefined,
|
|
1298
|
+
"description": "Responsabilidades e impacto..."
|
|
1299
|
+
}
|
|
1300
|
+
],
|
|
1301
|
+
"education": [
|
|
1302
|
+
{
|
|
1303
|
+
"schoolName": "Universidade",
|
|
1304
|
+
"degreeName": "Bacharelado",
|
|
1305
|
+
"fieldOfStudy": "Ci\xEAncia da Computa\xE7\xE3o"
|
|
1306
|
+
}
|
|
1307
|
+
],
|
|
1308
|
+
"skills": [
|
|
1309
|
+
{ "name": "Node.js" },
|
|
1310
|
+
{ "name": "TypeScript" }
|
|
1311
|
+
],
|
|
1312
|
+
"languages": [
|
|
1313
|
+
{ "name": "English", "proficiency": "professional" }
|
|
1314
|
+
]
|
|
1315
|
+
},
|
|
1316
|
+
"review": {
|
|
1317
|
+
"targetMarket": "United States Remote",
|
|
1318
|
+
"language": "en",
|
|
1319
|
+
"overallScore": 82,
|
|
1320
|
+
"scores": {
|
|
1321
|
+
"searchRelevance": 85,
|
|
1322
|
+
"humanVoice": 80,
|
|
1323
|
+
"credibility": 80,
|
|
1324
|
+
"positioningClarity": 85,
|
|
1325
|
+
"evidenceCoverage": 80
|
|
1326
|
+
},
|
|
1327
|
+
"scoreExplanations": {
|
|
1328
|
+
"searchRelevance": "Avalia\xE7\xE3o de indexa\xE7\xE3o ATS...",
|
|
1329
|
+
"humanVoice": "Tom profissional e aut\xEAntico...",
|
|
1330
|
+
"credibility": "Evid\xEAncias t\xE9cnicas e consist\xEAncia...",
|
|
1331
|
+
"positioningClarity": "Clareza do papel s\xEAnior...",
|
|
1332
|
+
"evidenceCoverage": "Densidade de m\xE9tricas quantific\xE1veis..."
|
|
1333
|
+
},
|
|
1334
|
+
"executiveSummary": "Resumo executivo do diagn\xF3stico e posicionamento para vagas nos EUA...",
|
|
1335
|
+
"profileDirection": {
|
|
1336
|
+
"positioning": "Senior Full-Stack Engineer",
|
|
1337
|
+
"primaryRole": "${p.targetRole || "Senior Full-Stack Engineer"}",
|
|
1338
|
+
"openToWorkTitles": [
|
|
1339
|
+
"Senior Full Stack Engineer",
|
|
1340
|
+
"Senior Backend Engineer",
|
|
1341
|
+
"Staff Software Engineer",
|
|
1342
|
+
"Lead Software Engineer",
|
|
1343
|
+
"Full Stack Developer"
|
|
1344
|
+
],
|
|
1345
|
+
"rationale": "Justificativa do posicionamento estrat\xE9gico para o mercado americano..."
|
|
1346
|
+
},
|
|
1347
|
+
"triageBottlenecks": [
|
|
1348
|
+
"Gargalo 1: Descri\xE7\xF5es de cargos com poucos bullets quantific\xE1veis",
|
|
1349
|
+
"Gargalo 2: Headline precisa de palavras-chave de indexa\xE7\xE3o booleana"
|
|
1350
|
+
],
|
|
1351
|
+
"critique": [
|
|
1352
|
+
{
|
|
1353
|
+
"section": "Headline",
|
|
1354
|
+
"assessment": "Avalia\xE7\xE3o da headline atual",
|
|
1355
|
+
"strengths": ["Clareza do cargo principal"],
|
|
1356
|
+
"issues": ["Falta men\xE7\xE3o a escala e especializa\xE7\xE3o"],
|
|
1357
|
+
"severity": "medium"
|
|
1358
|
+
}
|
|
1359
|
+
]
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
\`\`\`
|
|
1287
1364
|
`.trim();
|
|
1288
1365
|
} else if (job.type === "generate_interview") {
|
|
1366
|
+
const p = job.payload || {};
|
|
1289
1367
|
details = `
|
|
1290
1368
|
- **Tipo de A\xE7\xE3o**: Gera\xE7\xE3o de Perguntas de Entrevista T\xE9cnica
|
|
1291
|
-
- **Candidato**: ${
|
|
1292
|
-
- **Cargo Almejado**: ${
|
|
1369
|
+
- **Candidato**: ${p.profile?.firstName || ""} ${p.profile?.lastName || ""}
|
|
1370
|
+
- **Cargo Almejado**: ${p.objective?.primaryRole || "N\xE3o informado"}
|
|
1371
|
+
`.trim();
|
|
1372
|
+
instructions = `
|
|
1373
|
+
### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
|
|
1374
|
+
1. Gere perguntas t\xE9cnicas focando nos gargalos e lacunas identificados no perfil.
|
|
1375
|
+
2. N\xC3O execute scripts nem inspecione arquivos.
|
|
1376
|
+
3. Chame a ferramenta \`submit_job_result\` com a estrutura:
|
|
1377
|
+
|
|
1378
|
+
\`\`\`json
|
|
1379
|
+
{
|
|
1380
|
+
"jobId": "${job.id}",
|
|
1381
|
+
"result": {
|
|
1382
|
+
"questions": [
|
|
1383
|
+
{
|
|
1384
|
+
"id": "q1",
|
|
1385
|
+
"category": "scale",
|
|
1386
|
+
"question": "Texto claro da pergunta para investigar o escopo e impacto...",
|
|
1387
|
+
"rationale": "Por que esta pergunta \xE9 importante para o recrutador gringo...",
|
|
1388
|
+
"targetSignal": "M\xE9tricas de escala, throughput ou lat\xEAncia que queremos capturar"
|
|
1389
|
+
}
|
|
1390
|
+
]
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
\`\`\`
|
|
1293
1394
|
`.trim();
|
|
1294
1395
|
} else if (job.type === "generate_rewritten_profile") {
|
|
1396
|
+
const p = job.payload || {};
|
|
1295
1397
|
details = `
|
|
1296
|
-
- **Tipo de A\xE7\xE3o**: Reescrita
|
|
1297
|
-
- **Candidato**: ${
|
|
1298
|
-
- **Fatos Confirmados**: ${
|
|
1398
|
+
- **Tipo de A\xE7\xE3o**: Reescrita Completa de Perfil com Bullets Google XYZ
|
|
1399
|
+
- **Candidato**: ${p.profile?.firstName || ""} ${p.profile?.lastName || ""}
|
|
1400
|
+
- **Fatos Confirmados**: ${p.confirmedFacts?.length || 0}
|
|
1401
|
+
`.trim();
|
|
1402
|
+
instructions = `
|
|
1403
|
+
### \u26A0\uFE0F DIRETRIZES OBRIGAT\xD3RIAS PARA O AGENTE DE IA:
|
|
1404
|
+
1. Reescreva o perfil aplicando a f\xF3rmula Google XYZ (*Accomplished [X], measured by [Y], by doing [Z]*) e Headline $\\le$ 160 caracteres.
|
|
1405
|
+
2. N\xC3O execute scripts nem inspecione arquivos.
|
|
1406
|
+
3. Chame a ferramenta \`submit_job_result\` com a estrutura:
|
|
1407
|
+
|
|
1408
|
+
\`\`\`json
|
|
1409
|
+
{
|
|
1410
|
+
"jobId": "${job.id}",
|
|
1411
|
+
"result": {
|
|
1412
|
+
"rewritten": {
|
|
1413
|
+
"headline": "Role Anchor | 3-4 Core Techs | Scale/Impact | US Remote",
|
|
1414
|
+
"summary": "Resumo About reformulado com gancho forte nos primeiros 250 caracteres...",
|
|
1415
|
+
"experiences": [
|
|
1416
|
+
{
|
|
1417
|
+
"title": "Senior Software Engineer",
|
|
1418
|
+
"companyName": "Nome da Empresa",
|
|
1419
|
+
"workplaceType": "remote",
|
|
1420
|
+
"location": "Local",
|
|
1421
|
+
"current": true,
|
|
1422
|
+
"startDate": { "year": 2022, "month": 1 },
|
|
1423
|
+
"endDate": undefined,
|
|
1424
|
+
"description": "Accomplished [X], measured by [Y], by doing [Z]...\\nAccomplished [X], measured by [Y], by doing [Z]..."
|
|
1425
|
+
}
|
|
1426
|
+
]
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
\`\`\`
|
|
1299
1431
|
`.trim();
|
|
1300
1432
|
}
|
|
1301
1433
|
return {
|
|
1302
1434
|
content: [
|
|
1303
1435
|
{
|
|
1304
1436
|
type: "text",
|
|
1305
|
-
text:
|
|
1306
|
-
# \u{1F4E5} Job da Interface Web Encontrado!
|
|
1307
|
-
|
|
1308
|
-
- **Job ID**: \`${job.id}\`
|
|
1309
|
-
- **Tipo**: \`${job.type}\`
|
|
1310
|
-
- **Criado em**: ${new Date(job.createdAt).toLocaleTimeString()}
|
|
1437
|
+
text: `\u{1F680} **NOVO JOB RECEBIDO DO LINKEGRINGO**: \`${job.id}\`
|
|
1311
1438
|
|
|
1312
1439
|
${details}
|
|
1313
1440
|
|
|
1314
|
-
|
|
1315
|
-
\u{1F4A1} **Instru\xE7\xF5es para o Agente de IA**:
|
|
1316
|
-
1. Processe a intelig\xEAncia requerida para este job com base nas diretrizes do LinkeGringo.
|
|
1317
|
-
2. Ao concluir, chame a ferramenta \`submit_job_result\` passando \`jobId: "${job.id}"\` e o JSON estruturado no campo \`result\`.
|
|
1318
|
-
3. A interface web em \`localhost:5173\` atualizar\xE1 a tela no mesmo instante!
|
|
1319
|
-
`.trim()
|
|
1441
|
+
${instructions}`
|
|
1320
1442
|
}
|
|
1321
1443
|
],
|
|
1322
1444
|
structuredData: {
|
|
1323
1445
|
jobFound: true,
|
|
1324
|
-
job
|
|
1325
|
-
id: job.id,
|
|
1326
|
-
type: job.type,
|
|
1327
|
-
payload: job.payload,
|
|
1328
|
-
createdAt: job.createdAt
|
|
1329
|
-
}
|
|
1446
|
+
job
|
|
1330
1447
|
}
|
|
1331
1448
|
};
|
|
1332
1449
|
}
|
|
1333
1450
|
|
|
1451
|
+
// src/tools/get-pending-job.ts
|
|
1452
|
+
var getPendingJobInputSchema = z11.object({
|
|
1453
|
+
jobId: z11.string().optional().describe("ID espec\xEDfico do job a ser buscado (opcional. Se omitido, pega o pr\xF3ximo da fila)")
|
|
1454
|
+
});
|
|
1455
|
+
async function handleGetPendingJob(input = {}) {
|
|
1456
|
+
const job = await getRemoteOrLocalPendingJob(input.jobId);
|
|
1457
|
+
if (!job) {
|
|
1458
|
+
return {
|
|
1459
|
+
content: [
|
|
1460
|
+
{
|
|
1461
|
+
type: "text",
|
|
1462
|
+
text: "Nenhum job pendente no momento na interface web do LinkeGringo."
|
|
1463
|
+
}
|
|
1464
|
+
],
|
|
1465
|
+
structuredData: {
|
|
1466
|
+
jobFound: false,
|
|
1467
|
+
pendingCount: bridgeJobStore.getPendingCount()
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
bridgeJobStore.markProcessing(job.id);
|
|
1472
|
+
return formatJobResponse(job);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1334
1475
|
// src/tools/submit-job-result.ts
|
|
1335
1476
|
import { z as z12 } from "zod";
|
|
1336
1477
|
var submitJobResultInputSchema = z12.object({
|
|
@@ -1396,6 +1537,144 @@ O front-end em \`localhost:5173\` acabou de receber a resposta formatada e atual
|
|
|
1396
1537
|
}
|
|
1397
1538
|
}
|
|
1398
1539
|
|
|
1540
|
+
// src/tools/watch-linkegringo.ts
|
|
1541
|
+
import { z as z13 } from "zod";
|
|
1542
|
+
import http from "http";
|
|
1543
|
+
var watchLinkeGringoInputSchema = z13.object({
|
|
1544
|
+
timeoutSeconds: z13.number().optional().describe(
|
|
1545
|
+
"Tempo m\xE1ximo em segundos para aguardar a chegada de um novo job da interface web do LinkeGringo (padr\xE3o: 60s, m\xE1x: 300s). Se j\xE1 houver um job na fila, retorna imediatamente."
|
|
1546
|
+
),
|
|
1547
|
+
bridgeUrl: z13.string().optional().describe("URL base do bridge HTTP local do LinkeGringo (padr\xE3o: http://127.0.0.1:5174)")
|
|
1548
|
+
});
|
|
1549
|
+
async function waitForNextJob(bridgeUrl, timeoutMs) {
|
|
1550
|
+
const localPending = bridgeJobStore.getPendingJob();
|
|
1551
|
+
if (localPending) {
|
|
1552
|
+
return localPending;
|
|
1553
|
+
}
|
|
1554
|
+
return new Promise((resolve) => {
|
|
1555
|
+
let resolved = false;
|
|
1556
|
+
let req = null;
|
|
1557
|
+
let timer = null;
|
|
1558
|
+
const cleanup = () => {
|
|
1559
|
+
if (timer) {
|
|
1560
|
+
clearTimeout(timer);
|
|
1561
|
+
timer = null;
|
|
1562
|
+
}
|
|
1563
|
+
if (req) {
|
|
1564
|
+
try {
|
|
1565
|
+
req.destroy();
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
req = null;
|
|
1569
|
+
}
|
|
1570
|
+
bridgeJobStore.off("job:created", onLocalJob);
|
|
1571
|
+
};
|
|
1572
|
+
const done = (job) => {
|
|
1573
|
+
if (resolved) return;
|
|
1574
|
+
resolved = true;
|
|
1575
|
+
cleanup();
|
|
1576
|
+
resolve(job);
|
|
1577
|
+
};
|
|
1578
|
+
const onLocalJob = (job) => {
|
|
1579
|
+
done(job);
|
|
1580
|
+
};
|
|
1581
|
+
bridgeJobStore.on("job:created", onLocalJob);
|
|
1582
|
+
timer = setTimeout(() => {
|
|
1583
|
+
done(null);
|
|
1584
|
+
}, timeoutMs);
|
|
1585
|
+
try {
|
|
1586
|
+
const parsed = new URL("/api/jobs/stream", bridgeUrl);
|
|
1587
|
+
req = http.get(
|
|
1588
|
+
{
|
|
1589
|
+
hostname: parsed.hostname,
|
|
1590
|
+
port: parsed.port || 5174,
|
|
1591
|
+
path: parsed.pathname,
|
|
1592
|
+
headers: {
|
|
1593
|
+
Accept: "text/event-stream",
|
|
1594
|
+
"Cache-Control": "no-cache",
|
|
1595
|
+
Connection: "keep-alive"
|
|
1596
|
+
}
|
|
1597
|
+
},
|
|
1598
|
+
(res) => {
|
|
1599
|
+
if (res.statusCode !== 200) {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
let buffer = "";
|
|
1603
|
+
res.setEncoding("utf8");
|
|
1604
|
+
res.on("data", (chunk) => {
|
|
1605
|
+
buffer += chunk;
|
|
1606
|
+
const lines = buffer.split("\n\n");
|
|
1607
|
+
buffer = lines.pop() || "";
|
|
1608
|
+
for (const block of lines) {
|
|
1609
|
+
const trimmed = block.trim();
|
|
1610
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1611
|
+
const jsonStr = trimmed.replace(/^data:\s*/, "");
|
|
1612
|
+
try {
|
|
1613
|
+
const event = JSON.parse(jsonStr);
|
|
1614
|
+
if (event.type === "connected") {
|
|
1615
|
+
if (event.pendingCount > 0) {
|
|
1616
|
+
getRemoteOrLocalPendingJob().then((pending) => {
|
|
1617
|
+
if (pending) done(pending);
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
} else if (event.type === "job_created" && event.job) {
|
|
1621
|
+
done(event.job);
|
|
1622
|
+
}
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
res.on("end", () => {
|
|
1628
|
+
if (!resolved) {
|
|
1629
|
+
const pending = bridgeJobStore.getPendingJob();
|
|
1630
|
+
if (pending) {
|
|
1631
|
+
done(pending);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
res.on("error", () => {
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
);
|
|
1639
|
+
req.on("error", () => {
|
|
1640
|
+
});
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
async function handleWatchLinkeGringo(input = {}) {
|
|
1646
|
+
const timeoutSeconds = Math.min(Math.max(input.timeoutSeconds ?? 60, 0.1), 300);
|
|
1647
|
+
const timeoutMs = timeoutSeconds * 1e3;
|
|
1648
|
+
const bridgeUrl = input.bridgeUrl || "http://127.0.0.1:5174";
|
|
1649
|
+
const immediateJob = await getRemoteOrLocalPendingJob();
|
|
1650
|
+
if (immediateJob) {
|
|
1651
|
+
bridgeJobStore.markProcessing(immediateJob.id);
|
|
1652
|
+
return formatJobResponse(immediateJob);
|
|
1653
|
+
}
|
|
1654
|
+
const job = await waitForNextJob(bridgeUrl, timeoutMs);
|
|
1655
|
+
if (!job) {
|
|
1656
|
+
return {
|
|
1657
|
+
content: [
|
|
1658
|
+
{
|
|
1659
|
+
type: "text",
|
|
1660
|
+
text: `\u{1F7E2} **Modo Watch do LinkeGringo Ativo** (Bridge porta 5174)
|
|
1661
|
+
|
|
1662
|
+
Nenhum job recebido nos \xFAltimos ${timeoutSeconds} segundos.
|
|
1663
|
+
A interface web est\xE1 conectada ao Bridge. Assim que o usu\xE1rio clicar em **Analisar com Agente MCP**, **Iniciar Entrevista** ou **Gerar Perfil Reescrito**, execute a tool \`watch_linkegringo\` novamente para receber e processar os dados!`
|
|
1664
|
+
}
|
|
1665
|
+
],
|
|
1666
|
+
structuredData: {
|
|
1667
|
+
status: "waiting",
|
|
1668
|
+
timeoutSeconds,
|
|
1669
|
+
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1670
|
+
serverOnline: true
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
bridgeJobStore.markProcessing(job.id);
|
|
1675
|
+
return formatJobResponse(job);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1399
1678
|
// src/server.ts
|
|
1400
1679
|
function createLinkeGringoMcpServer() {
|
|
1401
1680
|
const server = new McpServer({
|
|
@@ -1445,7 +1724,7 @@ function createLinkeGringoMcpServer() {
|
|
|
1445
1724
|
server.registerTool(
|
|
1446
1725
|
"get_pending_job",
|
|
1447
1726
|
{
|
|
1448
|
-
description: "
|
|
1727
|
+
description: "Obt\xE9m os dados completos de um job pelo ID (ou o pr\xF3ximo da fila). Use APENAS quando for notificado de um job espec\xEDfico (ex: por watcher em background). N\xC3O use esta ferramenta para iniciar escuta ou checar jobs antes de iniciar o watch.",
|
|
1449
1728
|
inputSchema: getPendingJobInputSchema.shape
|
|
1450
1729
|
},
|
|
1451
1730
|
async (args) => {
|
|
@@ -1455,13 +1734,23 @@ function createLinkeGringoMcpServer() {
|
|
|
1455
1734
|
server.registerTool(
|
|
1456
1735
|
"submit_job_result",
|
|
1457
1736
|
{
|
|
1458
|
-
description: "Envia o resultado
|
|
1737
|
+
description: "Envia o resultado estruturado do processamento de um job de volta para o navegador do LinkeGringo, desbloqueando a tela do usu\xE1rio instantaneamente. Sempre chame esta ferramenta imediatamente ap\xF3s processar um job recebido por watch_linkegringo ou get_pending_job.",
|
|
1459
1738
|
inputSchema: submitJobResultInputSchema.shape
|
|
1460
1739
|
},
|
|
1461
1740
|
async (args) => {
|
|
1462
1741
|
return await handleSubmitJobResult(args);
|
|
1463
1742
|
}
|
|
1464
1743
|
);
|
|
1744
|
+
server.registerTool(
|
|
1745
|
+
"watch_linkegringo",
|
|
1746
|
+
{
|
|
1747
|
+
description: 'Inicia a escuta ativa (watch) por jobs enviados pela interface web do LinkeGringo (http://127.0.0.1:5174). Use esta ferramenta SEMPRE que o usu\xE1rio disser "iniciar watch", "escutar", "conectar ao LinkeGringo" ou similar. N\xC3O chame get_pending_job antes desta ferramenta. Bloqueia aguardando e retorna o job assim que ele for submetido no navegador.',
|
|
1748
|
+
inputSchema: watchLinkeGringoInputSchema.shape
|
|
1749
|
+
},
|
|
1750
|
+
async (args) => {
|
|
1751
|
+
return await handleWatchLinkeGringo(args);
|
|
1752
|
+
}
|
|
1753
|
+
);
|
|
1465
1754
|
server.registerResource(
|
|
1466
1755
|
"guidelines",
|
|
1467
1756
|
"linkegringo://guidelines",
|
|
@@ -1679,7 +1968,7 @@ function runInstaller(args = process.argv) {
|
|
|
1679
1968
|
}
|
|
1680
1969
|
|
|
1681
1970
|
// src/bridge/server.ts
|
|
1682
|
-
import
|
|
1971
|
+
import http2 from "http";
|
|
1683
1972
|
function setCorsHeaders(res) {
|
|
1684
1973
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1685
1974
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -1713,7 +2002,7 @@ async function parseBody(req) {
|
|
|
1713
2002
|
}
|
|
1714
2003
|
function createBridgeHttpServer(options = {}) {
|
|
1715
2004
|
const activeSseClients = /* @__PURE__ */ new Map();
|
|
1716
|
-
const server =
|
|
2005
|
+
const server = http2.createServer(async (req, res) => {
|
|
1717
2006
|
setCorsHeaders(res);
|
|
1718
2007
|
if (req.method === "OPTIONS") {
|
|
1719
2008
|
res.writeHead(204);
|
|
@@ -1945,7 +2234,7 @@ async function stopBridgeServer() {
|
|
|
1945
2234
|
}
|
|
1946
2235
|
|
|
1947
2236
|
// src/bridge/watcher.ts
|
|
1948
|
-
import
|
|
2237
|
+
import http3 from "http";
|
|
1949
2238
|
function startBridgeWatcher(options = {}) {
|
|
1950
2239
|
const bridgeUrl = options.bridgeUrl || process.env.LINKEGRINGO_BRIDGE_URL || "http://127.0.0.1:5174";
|
|
1951
2240
|
let isClosed = false;
|
|
@@ -1956,7 +2245,7 @@ function startBridgeWatcher(options = {}) {
|
|
|
1956
2245
|
if (isClosed) return;
|
|
1957
2246
|
try {
|
|
1958
2247
|
const url = new URL("/api/jobs/stream", bridgeUrl);
|
|
1959
|
-
const req =
|
|
2248
|
+
const req = http3.get(
|
|
1960
2249
|
url.toString(),
|
|
1961
2250
|
{
|
|
1962
2251
|
headers: {
|
|
@@ -1986,16 +2275,17 @@ function startBridgeWatcher(options = {}) {
|
|
|
1986
2275
|
console.log(
|
|
1987
2276
|
`[LinkeGringo Watcher] \u{1F7E2} Conectado ao bridge (${bridgeUrl}). Watcher ativo: ${payload.watcherId} (Jobs pendentes: ${payload.pendingCount})`
|
|
1988
2277
|
);
|
|
1989
|
-
if (
|
|
2278
|
+
if (payload.pendingCount > 0 && options.once) {
|
|
1990
2279
|
console.log(
|
|
1991
|
-
`[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)).
|
|
2280
|
+
`[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Chame get_pending_job para processar e submit_job_result para responder.`
|
|
1992
2281
|
);
|
|
1993
2282
|
setTimeout(() => process.exit(0), 50);
|
|
1994
2283
|
}
|
|
1995
2284
|
} else if (payload.type === "job_created") {
|
|
1996
2285
|
console.log(
|
|
1997
2286
|
`
|
|
1998
|
-
[LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}
|
|
2287
|
+
[LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}
|
|
2288
|
+
[LinkeGringo Watcher] \u{1F449} Chame get_pending_job({ jobId: '${payload.job.id}' }) e envie o resultado direto com submit_job_result.`
|
|
1999
2289
|
);
|
|
2000
2290
|
options.onJob?.(payload.job);
|
|
2001
2291
|
if (options.once) {
|
|
@@ -2136,6 +2426,7 @@ export {
|
|
|
2136
2426
|
handleGetPendingJob,
|
|
2137
2427
|
handleSimulateRecruiterSearch,
|
|
2138
2428
|
handleSubmitJobResult,
|
|
2429
|
+
handleWatchLinkeGringo,
|
|
2139
2430
|
installMcpServerConfig,
|
|
2140
2431
|
parseArgs,
|
|
2141
2432
|
runInstaller,
|
|
@@ -2143,5 +2434,7 @@ export {
|
|
|
2143
2434
|
startBridgeServer,
|
|
2144
2435
|
startBridgeWatcher,
|
|
2145
2436
|
stopBridgeServer,
|
|
2146
|
-
submitJobResultInputSchema
|
|
2437
|
+
submitJobResultInputSchema,
|
|
2438
|
+
waitForNextJob,
|
|
2439
|
+
watchLinkeGringoInputSchema
|
|
2147
2440
|
};
|