@linkegringo/mcp 1.0.6 → 1.0.8
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 +29 -1
- package/dist/index.js +272 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,24 @@ declare function completeRemoteOrLocalJob(jobId: string, result: any, bridgeUrl?
|
|
|
72
72
|
*/
|
|
73
73
|
declare function failRemoteOrLocalJob(jobId: string, error: string, bridgeUrl?: string): Promise<BridgeJob>;
|
|
74
74
|
|
|
75
|
+
interface WatcherOptions {
|
|
76
|
+
bridgeUrl?: string;
|
|
77
|
+
once?: boolean;
|
|
78
|
+
onJob?: (job: {
|
|
79
|
+
id: string;
|
|
80
|
+
type: string;
|
|
81
|
+
createdAt: number;
|
|
82
|
+
}) => void;
|
|
83
|
+
onConnect?: (info: {
|
|
84
|
+
watcherId: string;
|
|
85
|
+
pendingCount: number;
|
|
86
|
+
}) => void;
|
|
87
|
+
onError?: (err: Error) => void;
|
|
88
|
+
}
|
|
89
|
+
declare function startBridgeWatcher(options?: WatcherOptions): {
|
|
90
|
+
close: () => void;
|
|
91
|
+
};
|
|
92
|
+
|
|
75
93
|
declare class BridgeJobStore extends EventEmitter {
|
|
76
94
|
private jobs;
|
|
77
95
|
private pendingQueue;
|
|
@@ -84,6 +102,16 @@ declare class BridgeJobStore extends EventEmitter {
|
|
|
84
102
|
waitForJob(id: string, timeoutMs?: number): Promise<BridgeJob>;
|
|
85
103
|
getAllJobs(): BridgeJob[];
|
|
86
104
|
getPendingCount(): number;
|
|
105
|
+
private activeWatchers;
|
|
106
|
+
registerWatcher(id: string): void;
|
|
107
|
+
unregisterWatcher(id: string): void;
|
|
108
|
+
getWatcherCount(): number;
|
|
109
|
+
hasActiveWatcher(): boolean;
|
|
110
|
+
cancelAllPending(reason?: string): number;
|
|
111
|
+
resetQueue(reason?: string): {
|
|
112
|
+
canceledCount: number;
|
|
113
|
+
clearedCount: number;
|
|
114
|
+
};
|
|
87
115
|
clear(): void;
|
|
88
116
|
}
|
|
89
117
|
declare const bridgeJobStore: BridgeJobStore;
|
|
@@ -439,4 +467,4 @@ declare function handleSubmitJobResult(input: SubmitJobResultInput): Promise<{
|
|
|
439
467
|
};
|
|
440
468
|
}>;
|
|
441
469
|
|
|
442
|
-
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, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, stopBridgeServer, submitJobResultInputSchema };
|
|
470
|
+
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 WatcherOptions, auditProfileInputSchema, bridgeJobStore, completeRemoteOrLocalJob, convertToXyzBulletInputSchema, createBridgeHttpServer, createLinkeGringoMcpServer, detectSparseExperiences, failRemoteOrLocalJob, formatGoogleXyzBullet, generateHeadlineInputSchema, getExperienceBulletCount, getMcpConfigsForSystem, getPendingJobInputSchema, getRemoteOrLocalPendingJob, handleAuditProfile, handleConvertToXyzBullet, handleGenerateHeadline, handleGetPendingJob, handleSimulateRecruiterSearch, handleSubmitJobResult, installMcpServerConfig, parseArgs, runInstaller, simulateRecruiterSearchInputSchema, startBridgeServer, startBridgeWatcher, stopBridgeServer, submitJobResultInputSchema };
|
package/dist/index.js
CHANGED
|
@@ -117,9 +117,51 @@ var BridgeJobStore = class extends EventEmitter {
|
|
|
117
117
|
getPendingCount() {
|
|
118
118
|
return this.jobs.size > 0 ? Array.from(this.jobs.values()).filter((j) => j.status === "pending" || j.status === "processing").length : 0;
|
|
119
119
|
}
|
|
120
|
+
// Watcher tracking
|
|
121
|
+
activeWatchers = /* @__PURE__ */ new Set();
|
|
122
|
+
registerWatcher(id) {
|
|
123
|
+
this.activeWatchers.add(id);
|
|
124
|
+
this.emit("watcher:connected", id);
|
|
125
|
+
}
|
|
126
|
+
unregisterWatcher(id) {
|
|
127
|
+
this.activeWatchers.delete(id);
|
|
128
|
+
this.emit("watcher:disconnected", id);
|
|
129
|
+
}
|
|
130
|
+
getWatcherCount() {
|
|
131
|
+
return this.activeWatchers.size;
|
|
132
|
+
}
|
|
133
|
+
hasActiveWatcher() {
|
|
134
|
+
return this.activeWatchers.size > 0;
|
|
135
|
+
}
|
|
136
|
+
cancelAllPending(reason = "Cancelado pelo usu\xE1rio via interface web") {
|
|
137
|
+
let count = 0;
|
|
138
|
+
while (this.pendingQueue.length > 0) {
|
|
139
|
+
const id = this.pendingQueue.shift();
|
|
140
|
+
const job = this.jobs.get(id);
|
|
141
|
+
if (job && (job.status === "pending" || job.status === "processing")) {
|
|
142
|
+
job.status = "failed";
|
|
143
|
+
job.error = reason;
|
|
144
|
+
job.updatedAt = Date.now();
|
|
145
|
+
count++;
|
|
146
|
+
this.emit(`job:${id}`, job);
|
|
147
|
+
this.emit("job:failed", job);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.emit("queue:cleared", count);
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
153
|
+
resetQueue(reason = "Cancelado pelo usu\xE1rio via interface web") {
|
|
154
|
+
const canceledCount = this.cancelAllPending(reason);
|
|
155
|
+
const clearedCount = this.jobs.size;
|
|
156
|
+
this.jobs.clear();
|
|
157
|
+
this.pendingQueue = [];
|
|
158
|
+
this.emit("queue:reset", { canceledCount, clearedCount });
|
|
159
|
+
return { canceledCount, clearedCount };
|
|
160
|
+
}
|
|
120
161
|
clear() {
|
|
121
162
|
this.jobs.clear();
|
|
122
163
|
this.pendingQueue = [];
|
|
164
|
+
this.activeWatchers.clear();
|
|
123
165
|
this.removeAllListeners();
|
|
124
166
|
}
|
|
125
167
|
};
|
|
@@ -1670,6 +1712,7 @@ async function parseBody(req) {
|
|
|
1670
1712
|
});
|
|
1671
1713
|
}
|
|
1672
1714
|
function createBridgeHttpServer(options = {}) {
|
|
1715
|
+
const activeSseClients = /* @__PURE__ */ new Map();
|
|
1673
1716
|
const server = http.createServer(async (req, res) => {
|
|
1674
1717
|
setCorsHeaders(res);
|
|
1675
1718
|
if (req.method === "OPTIONS") {
|
|
@@ -1684,11 +1727,98 @@ function createBridgeHttpServer(options = {}) {
|
|
|
1684
1727
|
sendJson(res, 200, {
|
|
1685
1728
|
status: "ok",
|
|
1686
1729
|
server: "linkegringo-mcp-bridge",
|
|
1730
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1731
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1732
|
+
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1733
|
+
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1734
|
+
});
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
if (req.method === "GET" && pathname === "/api/bridge/status") {
|
|
1738
|
+
const pendingJob = bridgeJobStore.getPendingJob();
|
|
1739
|
+
sendJson(res, 200, {
|
|
1740
|
+
ok: true,
|
|
1741
|
+
status: "ready",
|
|
1742
|
+
server: "linkegringo-mcp-bridge",
|
|
1743
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1744
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1687
1745
|
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1746
|
+
activeJobId: pendingJob ? pendingJob.id : null,
|
|
1688
1747
|
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1689
1748
|
});
|
|
1690
1749
|
return;
|
|
1691
1750
|
}
|
|
1751
|
+
if (req.method === "POST" && pathname === "/api/bridge/disconnect") {
|
|
1752
|
+
for (const [id, client] of activeSseClients.entries()) {
|
|
1753
|
+
try {
|
|
1754
|
+
client.res.write(
|
|
1755
|
+
`data: ${JSON.stringify({ type: "disconnect", message: "Desconectado pelo usu\xE1rio na interface web." })}
|
|
1756
|
+
|
|
1757
|
+
`
|
|
1758
|
+
);
|
|
1759
|
+
client.res.end();
|
|
1760
|
+
} catch {
|
|
1761
|
+
}
|
|
1762
|
+
client.cleanup();
|
|
1763
|
+
}
|
|
1764
|
+
activeSseClients.clear();
|
|
1765
|
+
const { canceledCount, clearedCount } = bridgeJobStore.resetQueue();
|
|
1766
|
+
sendJson(res, 200, {
|
|
1767
|
+
ok: true,
|
|
1768
|
+
message: "Conex\xE3o cancelada e fila de jobs limpa com sucesso.",
|
|
1769
|
+
canceledCount,
|
|
1770
|
+
clearedCount
|
|
1771
|
+
});
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (req.method === "GET" && pathname === "/api/jobs/stream") {
|
|
1775
|
+
res.writeHead(200, {
|
|
1776
|
+
"Content-Type": "text/event-stream",
|
|
1777
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1778
|
+
"Connection": "keep-alive",
|
|
1779
|
+
"Access-Control-Allow-Origin": "*"
|
|
1780
|
+
});
|
|
1781
|
+
res.flushHeaders?.();
|
|
1782
|
+
const watcherId = `watcher_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1783
|
+
bridgeJobStore.registerWatcher(watcherId);
|
|
1784
|
+
res.write(`data: ${JSON.stringify({ type: "connected", watcherId, pendingCount: bridgeJobStore.getPendingCount() })}
|
|
1785
|
+
|
|
1786
|
+
`);
|
|
1787
|
+
const onJobCreated = (job) => {
|
|
1788
|
+
res.write(`data: ${JSON.stringify({ type: "job_created", job: { id: job.id, type: job.type, createdAt: job.createdAt } })}
|
|
1789
|
+
|
|
1790
|
+
`);
|
|
1791
|
+
};
|
|
1792
|
+
const onJobCompleted = (job) => {
|
|
1793
|
+
res.write(`data: ${JSON.stringify({ type: "job_completed", job: { id: job.id, type: job.type } })}
|
|
1794
|
+
|
|
1795
|
+
`);
|
|
1796
|
+
};
|
|
1797
|
+
const onJobFailed = (job) => {
|
|
1798
|
+
res.write(`data: ${JSON.stringify({ type: "job_failed", job: { id: job.id, type: job.type, error: job.error } })}
|
|
1799
|
+
|
|
1800
|
+
`);
|
|
1801
|
+
};
|
|
1802
|
+
bridgeJobStore.on("job:created", onJobCreated);
|
|
1803
|
+
bridgeJobStore.on("job:completed", onJobCompleted);
|
|
1804
|
+
bridgeJobStore.on("job:failed", onJobFailed);
|
|
1805
|
+
const heartbeat = setInterval(() => {
|
|
1806
|
+
res.write(`: heartbeat
|
|
1807
|
+
|
|
1808
|
+
`);
|
|
1809
|
+
}, 15e3);
|
|
1810
|
+
const cleanup = () => {
|
|
1811
|
+
clearInterval(heartbeat);
|
|
1812
|
+
bridgeJobStore.unregisterWatcher(watcherId);
|
|
1813
|
+
bridgeJobStore.off("job:created", onJobCreated);
|
|
1814
|
+
bridgeJobStore.off("job:completed", onJobCompleted);
|
|
1815
|
+
bridgeJobStore.off("job:failed", onJobFailed);
|
|
1816
|
+
activeSseClients.delete(watcherId);
|
|
1817
|
+
};
|
|
1818
|
+
activeSseClients.set(watcherId, { res, cleanup });
|
|
1819
|
+
req.on("close", cleanup);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1692
1822
|
if (req.method === "POST" && pathname === "/api/jobs") {
|
|
1693
1823
|
const body = await parseBody(req);
|
|
1694
1824
|
if (!body.type) {
|
|
@@ -1700,6 +1830,10 @@ function createBridgeHttpServer(options = {}) {
|
|
|
1700
1830
|
sendJson(res, 201, { ok: true, job });
|
|
1701
1831
|
return;
|
|
1702
1832
|
}
|
|
1833
|
+
if (req.method === "GET" && pathname === "/api/jobs") {
|
|
1834
|
+
sendJson(res, 200, { ok: true, jobs: bridgeJobStore.getAllJobs() });
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1703
1837
|
if (req.method === "GET" && pathname === "/api/jobs/pending") {
|
|
1704
1838
|
const id = parsedUrl.searchParams.get("id") || void 0;
|
|
1705
1839
|
const job = bridgeJobStore.getPendingJob(id);
|
|
@@ -1810,6 +1944,137 @@ async function stopBridgeServer() {
|
|
|
1810
1944
|
});
|
|
1811
1945
|
}
|
|
1812
1946
|
|
|
1947
|
+
// src/bridge/watcher.ts
|
|
1948
|
+
import http2 from "http";
|
|
1949
|
+
function startBridgeWatcher(options = {}) {
|
|
1950
|
+
const bridgeUrl = options.bridgeUrl || process.env.LINKEGRINGO_BRIDGE_URL || "http://127.0.0.1:5174";
|
|
1951
|
+
let isClosed = false;
|
|
1952
|
+
let currentReq = null;
|
|
1953
|
+
let retryTimer = null;
|
|
1954
|
+
let retryDelay = 1e3;
|
|
1955
|
+
function connect() {
|
|
1956
|
+
if (isClosed) return;
|
|
1957
|
+
try {
|
|
1958
|
+
const url = new URL("/api/jobs/stream", bridgeUrl);
|
|
1959
|
+
const req = http2.get(
|
|
1960
|
+
url.toString(),
|
|
1961
|
+
{
|
|
1962
|
+
headers: {
|
|
1963
|
+
Accept: "text/event-stream",
|
|
1964
|
+
"Cache-Control": "no-cache"
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
(res) => {
|
|
1968
|
+
if (res.statusCode !== 200) {
|
|
1969
|
+
options.onError?.(new Error(`Falha ao conectar no stream SSE: HTTP ${res.statusCode}`));
|
|
1970
|
+
scheduleRetry();
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
retryDelay = 1e3;
|
|
1974
|
+
let buffer = "";
|
|
1975
|
+
res.on("data", (chunk) => {
|
|
1976
|
+
buffer += chunk.toString("utf-8");
|
|
1977
|
+
const lines = buffer.split("\n\n");
|
|
1978
|
+
buffer = lines.pop() || "";
|
|
1979
|
+
for (const block of lines) {
|
|
1980
|
+
for (const line of block.split("\n")) {
|
|
1981
|
+
if (line.startsWith("data: ")) {
|
|
1982
|
+
try {
|
|
1983
|
+
const payload = JSON.parse(line.slice(6));
|
|
1984
|
+
if (payload.type === "connected") {
|
|
1985
|
+
options.onConnect?.(payload);
|
|
1986
|
+
console.log(
|
|
1987
|
+
`[LinkeGringo Watcher] \u{1F7E2} Conectado ao bridge (${bridgeUrl}). Watcher ativo: ${payload.watcherId} (Jobs pendentes: ${payload.pendingCount})`
|
|
1988
|
+
);
|
|
1989
|
+
if (options.once && payload.pendingCount > 0) {
|
|
1990
|
+
console.log(
|
|
1991
|
+
`[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Finalizando para ativar agente...`
|
|
1992
|
+
);
|
|
1993
|
+
setTimeout(() => process.exit(0), 50);
|
|
1994
|
+
}
|
|
1995
|
+
} else if (payload.type === "job_created") {
|
|
1996
|
+
console.log(
|
|
1997
|
+
`
|
|
1998
|
+
[LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}`
|
|
1999
|
+
);
|
|
2000
|
+
options.onJob?.(payload.job);
|
|
2001
|
+
if (options.once) {
|
|
2002
|
+
setTimeout(() => process.exit(0), 50);
|
|
2003
|
+
}
|
|
2004
|
+
} else if (payload.type === "job_completed") {
|
|
2005
|
+
console.log(`[LinkeGringo Watcher] \u2705 Job finalizado: ${payload.job.id}`);
|
|
2006
|
+
} else if (payload.type === "job_failed") {
|
|
2007
|
+
console.log(
|
|
2008
|
+
`[LinkeGringo Watcher] \u26A0\uFE0F Job cancelado ou com erro: ${payload.job.id} (${payload.job.error})`
|
|
2009
|
+
);
|
|
2010
|
+
} else if (payload.type === "disconnect") {
|
|
2011
|
+
console.log(
|
|
2012
|
+
`
|
|
2013
|
+
[LinkeGringo Watcher] \u{1F6D1} Conex\xE3o encerrada pelo servidor (desconectado via interface web).`
|
|
2014
|
+
);
|
|
2015
|
+
isClosed = true;
|
|
2016
|
+
if (currentReq) {
|
|
2017
|
+
currentReq.destroy();
|
|
2018
|
+
currentReq = null;
|
|
2019
|
+
}
|
|
2020
|
+
setTimeout(() => process.exit(0), 50);
|
|
2021
|
+
}
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
res.on("end", () => {
|
|
2029
|
+
if (!isClosed) {
|
|
2030
|
+
scheduleRetry();
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
res.on("error", (err) => {
|
|
2034
|
+
options.onError?.(err);
|
|
2035
|
+
if (!isClosed) {
|
|
2036
|
+
scheduleRetry();
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
);
|
|
2041
|
+
req.on("error", (err) => {
|
|
2042
|
+
options.onError?.(err);
|
|
2043
|
+
if (!isClosed) {
|
|
2044
|
+
scheduleRetry();
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
currentReq = req;
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
options.onError?.(err);
|
|
2050
|
+
scheduleRetry();
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function scheduleRetry() {
|
|
2054
|
+
if (isClosed || retryTimer) return;
|
|
2055
|
+
retryTimer = setTimeout(() => {
|
|
2056
|
+
retryTimer = null;
|
|
2057
|
+
retryDelay = Math.min(retryDelay * 1.5, 1e4);
|
|
2058
|
+
connect();
|
|
2059
|
+
}, retryDelay);
|
|
2060
|
+
}
|
|
2061
|
+
connect();
|
|
2062
|
+
return {
|
|
2063
|
+
close() {
|
|
2064
|
+
isClosed = true;
|
|
2065
|
+
if (retryTimer) {
|
|
2066
|
+
clearTimeout(retryTimer);
|
|
2067
|
+
retryTimer = null;
|
|
2068
|
+
}
|
|
2069
|
+
if (currentReq) {
|
|
2070
|
+
currentReq.destroy();
|
|
2071
|
+
currentReq = null;
|
|
2072
|
+
}
|
|
2073
|
+
console.log("[LinkeGringo Watcher] Modo watch desconectado.");
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
|
|
1813
2078
|
// src/index.ts
|
|
1814
2079
|
import fs2 from "fs";
|
|
1815
2080
|
import { fileURLToPath } from "url";
|
|
@@ -1818,6 +2083,12 @@ async function main() {
|
|
|
1818
2083
|
runInstaller(process.argv);
|
|
1819
2084
|
return;
|
|
1820
2085
|
}
|
|
2086
|
+
if (process.argv.includes("watch") || process.argv.includes("--watch") || process.argv.includes("listen")) {
|
|
2087
|
+
const once = process.argv.includes("--once") || process.argv.includes("-1");
|
|
2088
|
+
console.log(`[LinkeGringo Watcher] Iniciando modo escuta ultraleve${once ? " (once)" : ""}...`);
|
|
2089
|
+
startBridgeWatcher({ once });
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
1821
2092
|
try {
|
|
1822
2093
|
await startBridgeServer();
|
|
1823
2094
|
} catch (err) {
|
|
@@ -1870,6 +2141,7 @@ export {
|
|
|
1870
2141
|
runInstaller,
|
|
1871
2142
|
simulateRecruiterSearchInputSchema,
|
|
1872
2143
|
startBridgeServer,
|
|
2144
|
+
startBridgeWatcher,
|
|
1873
2145
|
stopBridgeServer,
|
|
1874
2146
|
submitJobResultInputSchema
|
|
1875
2147
|
};
|