@linkegringo/mcp 1.0.6 → 1.0.7
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 +25 -1
- package/dist/index.js +235 -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,12 @@ 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;
|
|
87
111
|
clear(): void;
|
|
88
112
|
}
|
|
89
113
|
declare const bridgeJobStore: BridgeJobStore;
|
|
@@ -439,4 +463,4 @@ declare function handleSubmitJobResult(input: SubmitJobResultInput): Promise<{
|
|
|
439
463
|
};
|
|
440
464
|
}>;
|
|
441
465
|
|
|
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 };
|
|
466
|
+
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,43 @@ 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
|
+
}
|
|
120
153
|
clear() {
|
|
121
154
|
this.jobs.clear();
|
|
122
155
|
this.pendingQueue = [];
|
|
156
|
+
this.activeWatchers.clear();
|
|
123
157
|
this.removeAllListeners();
|
|
124
158
|
}
|
|
125
159
|
};
|
|
@@ -1684,11 +1718,81 @@ function createBridgeHttpServer(options = {}) {
|
|
|
1684
1718
|
sendJson(res, 200, {
|
|
1685
1719
|
status: "ok",
|
|
1686
1720
|
server: "linkegringo-mcp-bridge",
|
|
1721
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1722
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1723
|
+
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1724
|
+
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1725
|
+
});
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
if (req.method === "GET" && pathname === "/api/bridge/status") {
|
|
1729
|
+
const pendingJob = bridgeJobStore.getPendingJob();
|
|
1730
|
+
sendJson(res, 200, {
|
|
1731
|
+
ok: true,
|
|
1732
|
+
status: "ready",
|
|
1733
|
+
server: "linkegringo-mcp-bridge",
|
|
1734
|
+
watcherConnected: bridgeJobStore.hasActiveWatcher(),
|
|
1735
|
+
watcherCount: bridgeJobStore.getWatcherCount(),
|
|
1687
1736
|
pendingCount: bridgeJobStore.getPendingCount(),
|
|
1737
|
+
activeJobId: pendingJob ? pendingJob.id : null,
|
|
1688
1738
|
totalJobs: bridgeJobStore.getAllJobs().length
|
|
1689
1739
|
});
|
|
1690
1740
|
return;
|
|
1691
1741
|
}
|
|
1742
|
+
if (req.method === "POST" && pathname === "/api/bridge/disconnect") {
|
|
1743
|
+
const canceledCount = bridgeJobStore.cancelAllPending("Cancelado pelo usu\xE1rio na interface web.");
|
|
1744
|
+
sendJson(res, 200, {
|
|
1745
|
+
ok: true,
|
|
1746
|
+
message: "Conex\xE3o cancelada e fila de jobs limpa com sucesso.",
|
|
1747
|
+
canceledCount
|
|
1748
|
+
});
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (req.method === "GET" && pathname === "/api/jobs/stream") {
|
|
1752
|
+
res.writeHead(200, {
|
|
1753
|
+
"Content-Type": "text/event-stream",
|
|
1754
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1755
|
+
"Connection": "keep-alive",
|
|
1756
|
+
"Access-Control-Allow-Origin": "*"
|
|
1757
|
+
});
|
|
1758
|
+
res.flushHeaders?.();
|
|
1759
|
+
const watcherId = `watcher_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1760
|
+
bridgeJobStore.registerWatcher(watcherId);
|
|
1761
|
+
res.write(`data: ${JSON.stringify({ type: "connected", watcherId, pendingCount: bridgeJobStore.getPendingCount() })}
|
|
1762
|
+
|
|
1763
|
+
`);
|
|
1764
|
+
const onJobCreated = (job) => {
|
|
1765
|
+
res.write(`data: ${JSON.stringify({ type: "job_created", job: { id: job.id, type: job.type, createdAt: job.createdAt } })}
|
|
1766
|
+
|
|
1767
|
+
`);
|
|
1768
|
+
};
|
|
1769
|
+
const onJobCompleted = (job) => {
|
|
1770
|
+
res.write(`data: ${JSON.stringify({ type: "job_completed", job: { id: job.id, type: job.type } })}
|
|
1771
|
+
|
|
1772
|
+
`);
|
|
1773
|
+
};
|
|
1774
|
+
const onJobFailed = (job) => {
|
|
1775
|
+
res.write(`data: ${JSON.stringify({ type: "job_failed", job: { id: job.id, type: job.type, error: job.error } })}
|
|
1776
|
+
|
|
1777
|
+
`);
|
|
1778
|
+
};
|
|
1779
|
+
bridgeJobStore.on("job:created", onJobCreated);
|
|
1780
|
+
bridgeJobStore.on("job:completed", onJobCompleted);
|
|
1781
|
+
bridgeJobStore.on("job:failed", onJobFailed);
|
|
1782
|
+
const heartbeat = setInterval(() => {
|
|
1783
|
+
res.write(`: heartbeat
|
|
1784
|
+
|
|
1785
|
+
`);
|
|
1786
|
+
}, 15e3);
|
|
1787
|
+
req.on("close", () => {
|
|
1788
|
+
clearInterval(heartbeat);
|
|
1789
|
+
bridgeJobStore.unregisterWatcher(watcherId);
|
|
1790
|
+
bridgeJobStore.off("job:created", onJobCreated);
|
|
1791
|
+
bridgeJobStore.off("job:completed", onJobCompleted);
|
|
1792
|
+
bridgeJobStore.off("job:failed", onJobFailed);
|
|
1793
|
+
});
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1692
1796
|
if (req.method === "POST" && pathname === "/api/jobs") {
|
|
1693
1797
|
const body = await parseBody(req);
|
|
1694
1798
|
if (!body.type) {
|
|
@@ -1700,6 +1804,10 @@ function createBridgeHttpServer(options = {}) {
|
|
|
1700
1804
|
sendJson(res, 201, { ok: true, job });
|
|
1701
1805
|
return;
|
|
1702
1806
|
}
|
|
1807
|
+
if (req.method === "GET" && pathname === "/api/jobs") {
|
|
1808
|
+
sendJson(res, 200, { ok: true, jobs: bridgeJobStore.getAllJobs() });
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1703
1811
|
if (req.method === "GET" && pathname === "/api/jobs/pending") {
|
|
1704
1812
|
const id = parsedUrl.searchParams.get("id") || void 0;
|
|
1705
1813
|
const job = bridgeJobStore.getPendingJob(id);
|
|
@@ -1810,6 +1918,126 @@ async function stopBridgeServer() {
|
|
|
1810
1918
|
});
|
|
1811
1919
|
}
|
|
1812
1920
|
|
|
1921
|
+
// src/bridge/watcher.ts
|
|
1922
|
+
import http2 from "http";
|
|
1923
|
+
function startBridgeWatcher(options = {}) {
|
|
1924
|
+
const bridgeUrl = options.bridgeUrl || process.env.LINKEGRINGO_BRIDGE_URL || "http://127.0.0.1:5174";
|
|
1925
|
+
let isClosed = false;
|
|
1926
|
+
let currentReq = null;
|
|
1927
|
+
let retryTimer = null;
|
|
1928
|
+
let retryDelay = 1e3;
|
|
1929
|
+
function connect() {
|
|
1930
|
+
if (isClosed) return;
|
|
1931
|
+
try {
|
|
1932
|
+
const url = new URL("/api/jobs/stream", bridgeUrl);
|
|
1933
|
+
const req = http2.get(
|
|
1934
|
+
url.toString(),
|
|
1935
|
+
{
|
|
1936
|
+
headers: {
|
|
1937
|
+
Accept: "text/event-stream",
|
|
1938
|
+
"Cache-Control": "no-cache"
|
|
1939
|
+
}
|
|
1940
|
+
},
|
|
1941
|
+
(res) => {
|
|
1942
|
+
if (res.statusCode !== 200) {
|
|
1943
|
+
options.onError?.(new Error(`Falha ao conectar no stream SSE: HTTP ${res.statusCode}`));
|
|
1944
|
+
scheduleRetry();
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
retryDelay = 1e3;
|
|
1948
|
+
let buffer = "";
|
|
1949
|
+
res.on("data", (chunk) => {
|
|
1950
|
+
buffer += chunk.toString("utf-8");
|
|
1951
|
+
const lines = buffer.split("\n\n");
|
|
1952
|
+
buffer = lines.pop() || "";
|
|
1953
|
+
for (const block of lines) {
|
|
1954
|
+
for (const line of block.split("\n")) {
|
|
1955
|
+
if (line.startsWith("data: ")) {
|
|
1956
|
+
try {
|
|
1957
|
+
const payload = JSON.parse(line.slice(6));
|
|
1958
|
+
if (payload.type === "connected") {
|
|
1959
|
+
options.onConnect?.(payload);
|
|
1960
|
+
console.log(
|
|
1961
|
+
`[LinkeGringo Watcher] \u{1F7E2} Conectado ao bridge (${bridgeUrl}). Watcher ativo: ${payload.watcherId} (Jobs pendentes: ${payload.pendingCount})`
|
|
1962
|
+
);
|
|
1963
|
+
if (options.once && payload.pendingCount > 0) {
|
|
1964
|
+
console.log(
|
|
1965
|
+
`[LinkeGringo Watcher] \u{1F680} JOB DETECTADO NA FILA (${payload.pendingCount} pendente(s)). Finalizando para ativar agente...`
|
|
1966
|
+
);
|
|
1967
|
+
setTimeout(() => process.exit(0), 50);
|
|
1968
|
+
}
|
|
1969
|
+
} else if (payload.type === "job_created") {
|
|
1970
|
+
console.log(
|
|
1971
|
+
`
|
|
1972
|
+
[LinkeGringo Watcher] \u{1F680} NOVO JOB DETECTADO: ${payload.job.id} | Tipo: ${payload.job.type}`
|
|
1973
|
+
);
|
|
1974
|
+
options.onJob?.(payload.job);
|
|
1975
|
+
if (options.once) {
|
|
1976
|
+
setTimeout(() => process.exit(0), 50);
|
|
1977
|
+
}
|
|
1978
|
+
} else if (payload.type === "job_completed") {
|
|
1979
|
+
console.log(`[LinkeGringo Watcher] \u2705 Job finalizado: ${payload.job.id}`);
|
|
1980
|
+
} else if (payload.type === "job_failed") {
|
|
1981
|
+
console.log(
|
|
1982
|
+
`[LinkeGringo Watcher] \u26A0\uFE0F Job cancelado ou com erro: ${payload.job.id} (${payload.job.error})`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
} catch {
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
res.on("end", () => {
|
|
1992
|
+
if (!isClosed) {
|
|
1993
|
+
scheduleRetry();
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
res.on("error", (err) => {
|
|
1997
|
+
options.onError?.(err);
|
|
1998
|
+
if (!isClosed) {
|
|
1999
|
+
scheduleRetry();
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
2004
|
+
req.on("error", (err) => {
|
|
2005
|
+
options.onError?.(err);
|
|
2006
|
+
if (!isClosed) {
|
|
2007
|
+
scheduleRetry();
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
currentReq = req;
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
options.onError?.(err);
|
|
2013
|
+
scheduleRetry();
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
function scheduleRetry() {
|
|
2017
|
+
if (isClosed || retryTimer) return;
|
|
2018
|
+
retryTimer = setTimeout(() => {
|
|
2019
|
+
retryTimer = null;
|
|
2020
|
+
retryDelay = Math.min(retryDelay * 1.5, 1e4);
|
|
2021
|
+
connect();
|
|
2022
|
+
}, retryDelay);
|
|
2023
|
+
}
|
|
2024
|
+
connect();
|
|
2025
|
+
return {
|
|
2026
|
+
close() {
|
|
2027
|
+
isClosed = true;
|
|
2028
|
+
if (retryTimer) {
|
|
2029
|
+
clearTimeout(retryTimer);
|
|
2030
|
+
retryTimer = null;
|
|
2031
|
+
}
|
|
2032
|
+
if (currentReq) {
|
|
2033
|
+
currentReq.destroy();
|
|
2034
|
+
currentReq = null;
|
|
2035
|
+
}
|
|
2036
|
+
console.log("[LinkeGringo Watcher] Modo watch desconectado.");
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1813
2041
|
// src/index.ts
|
|
1814
2042
|
import fs2 from "fs";
|
|
1815
2043
|
import { fileURLToPath } from "url";
|
|
@@ -1818,6 +2046,12 @@ async function main() {
|
|
|
1818
2046
|
runInstaller(process.argv);
|
|
1819
2047
|
return;
|
|
1820
2048
|
}
|
|
2049
|
+
if (process.argv.includes("watch") || process.argv.includes("--watch") || process.argv.includes("listen")) {
|
|
2050
|
+
const once = process.argv.includes("--once") || process.argv.includes("-1");
|
|
2051
|
+
console.log(`[LinkeGringo Watcher] Iniciando modo escuta ultraleve${once ? " (once)" : ""}...`);
|
|
2052
|
+
startBridgeWatcher({ once });
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
1821
2055
|
try {
|
|
1822
2056
|
await startBridgeServer();
|
|
1823
2057
|
} catch (err) {
|
|
@@ -1870,6 +2104,7 @@ export {
|
|
|
1870
2104
|
runInstaller,
|
|
1871
2105
|
simulateRecruiterSearchInputSchema,
|
|
1872
2106
|
startBridgeServer,
|
|
2107
|
+
startBridgeWatcher,
|
|
1873
2108
|
stopBridgeServer,
|
|
1874
2109
|
submitJobResultInputSchema
|
|
1875
2110
|
};
|