@tyvm/knowhow 0.0.63 → 0.0.65
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/package.json +2 -1
- package/src/chat/modules/AgentModule.ts +18 -2
- package/src/cli.ts +5 -4
- package/src/clients/anthropic.ts +68 -5
- package/src/config.ts +7 -0
- package/src/processors/Base64ImageDetector.ts +193 -40
- package/src/processors/index.ts +1 -1
- package/src/services/AgentSynchronization.ts +27 -10
- package/src/types.ts +11 -0
- package/src/worker.ts +145 -8
- package/src/workers/tools/index.ts +10 -0
- package/src/workers/tools/listAllowedPorts.ts +30 -0
- package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -1
- package/tests/plugins/language/languagePlugin.test.ts +5 -1
- package/tests/processors/Base64ImageDetector.test.ts +263 -70
- package/tests/services/Tools.test.ts +6 -4
- package/ts_build/package.json +2 -1
- package/ts_build/src/chat/modules/AgentModule.js +12 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/cli.js +5 -4
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +9 -0
- package/ts_build/src/clients/anthropic.js +61 -5
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/config.js +6 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.d.ts +7 -3
- package/ts_build/src/processors/Base64ImageDetector.js +147 -27
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +1 -1
- package/ts_build/src/processors/index.js +2 -2
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSynchronization.d.ts +2 -0
- package/ts_build/src/services/AgentSynchronization.js +20 -10
- package/ts_build/src/services/AgentSynchronization.js.map +1 -1
- package/ts_build/src/types.d.ts +11 -0
- package/ts_build/src/types.js +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker/handlers/proxyHandler.d.ts +2 -0
- package/ts_build/src/worker/handlers/proxyHandler.js +41 -0
- package/ts_build/src/worker/handlers/proxyHandler.js.map +1 -0
- package/ts_build/src/worker/tools/index.d.ts +1 -0
- package/ts_build/src/worker/tools/index.js +18 -0
- package/ts_build/src/worker/tools/index.js.map +1 -0
- package/ts_build/src/worker/tools/portForwarding.d.ts +49 -0
- package/ts_build/src/worker/tools/portForwarding.js +173 -0
- package/ts_build/src/worker/tools/portForwarding.js.map +1 -0
- package/ts_build/src/worker/types/proxy.d.ts +18 -0
- package/ts_build/src/worker/types/proxy.js +3 -0
- package/ts_build/src/worker/types/proxy.js.map +1 -0
- package/ts_build/src/worker.js +119 -3
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/tools/index.d.ts +9 -0
- package/ts_build/src/workers/tools/index.js +23 -0
- package/ts_build/src/workers/tools/index.js.map +1 -0
- package/ts_build/src/workers/tools/listAllowedPorts.d.ts +3 -0
- package/ts_build/src/workers/tools/listAllowedPorts.js +25 -0
- package/ts_build/src/workers/tools/listAllowedPorts.js.map +1 -0
- package/ts_build/src/workers/tools/listForwardedPorts.d.ts +10 -0
- package/ts_build/src/workers/tools/listForwardedPorts.js +22 -0
- package/ts_build/src/workers/tools/listForwardedPorts.js.map +1 -0
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -1
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js +5 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
- package/ts_build/tests/processors/Base64ImageDetector.test.js +221 -59
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/services/Tools.test.js +3 -3
- package/ts_build/tests/services/Tools.test.js.map +1 -1
- package/ts_build/tests/worker/handlers/proxyHandler.test.d.ts +1 -0
- package/ts_build/tests/worker/handlers/proxyHandler.test.js +170 -0
- package/ts_build/tests/worker/handlers/proxyHandler.test.js.map +1 -0
- package/tsconfig.json +1 -1
package/src/worker.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import os from "os";
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
|
+
import { createTunnelHandler, TunnelHandler } from "@tyvm/knowhow-tunnel";
|
|
3
4
|
import { includedTools } from "./agents/tools/list";
|
|
4
5
|
import { loadJwt } from "./login";
|
|
5
6
|
import { services } from "./services";
|
|
6
7
|
import { McpServerService } from "./services/Mcp";
|
|
7
8
|
import * as allTools from "./agents/tools";
|
|
9
|
+
import workerTools from "./workers/tools";
|
|
8
10
|
import { wait } from "./utils";
|
|
9
11
|
import { getConfig, updateConfig } from "./config";
|
|
10
12
|
import { KNOWHOW_API_URL } from "./services/KnowhowClient";
|
|
@@ -88,9 +90,11 @@ export async function worker(options?: {
|
|
|
88
90
|
|
|
89
91
|
// Check if we're already running inside a Docker container
|
|
90
92
|
const isInsideDocker = process.env.KNOWHOW_DOCKER === "true";
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
if (isInsideDocker) {
|
|
93
|
-
console.log(
|
|
95
|
+
console.log(
|
|
96
|
+
"🐳 Already running inside Docker container, skipping sandbox mode"
|
|
97
|
+
);
|
|
94
98
|
// Force sandbox mode off when inside Docker to prevent nested containers
|
|
95
99
|
if (options) {
|
|
96
100
|
options.sandbox = false;
|
|
@@ -145,6 +149,11 @@ export async function worker(options?: {
|
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
const { Tools } = services();
|
|
152
|
+
// Combine agent tools and worker-specific tools
|
|
153
|
+
const combinedTools = { ...allTools, ...workerTools.tools };
|
|
154
|
+
Tools.defineTools(includedTools, combinedTools);
|
|
155
|
+
Tools.defineTools(workerTools.definitions, workerTools.tools);
|
|
156
|
+
|
|
148
157
|
const mcpServer = new McpServerService(Tools);
|
|
149
158
|
const clientName = "knowhow-worker";
|
|
150
159
|
const clientVersion = "1.1.1";
|
|
@@ -178,6 +187,74 @@ export async function worker(options?: {
|
|
|
178
187
|
mcpServer.createServer(clientName, clientVersion).withTools(toolsToUse);
|
|
179
188
|
|
|
180
189
|
let connected = false;
|
|
190
|
+
let tunnelHandler: TunnelHandler | null = null;
|
|
191
|
+
let tunnelWs: WebSocket | null = null;
|
|
192
|
+
|
|
193
|
+
// Check if tunnel is enabled
|
|
194
|
+
const tunnelEnabled = config.worker?.tunnel?.enabled ?? false;
|
|
195
|
+
|
|
196
|
+
// Determine localHost based on environment
|
|
197
|
+
let tunnelLocalHost = config.worker?.tunnel?.localHost;
|
|
198
|
+
if (!tunnelLocalHost) {
|
|
199
|
+
// Auto-detect based on Docker environment
|
|
200
|
+
if (isInsideDocker) {
|
|
201
|
+
tunnelLocalHost = "host.docker.internal";
|
|
202
|
+
console.log(
|
|
203
|
+
"🐳 Docker detected: tunnel will use host.docker.internal to reach host services"
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
tunnelLocalHost = "127.0.0.1";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for port mapping configuration
|
|
211
|
+
const portMapping = config.worker?.tunnel?.portMapping || {};
|
|
212
|
+
if (Object.keys(portMapping).length > 0) {
|
|
213
|
+
console.log("🔀 Port mapping configured:");
|
|
214
|
+
for (const [containerPort, hostPort] of Object.entries(portMapping)) {
|
|
215
|
+
console.log(` Container port ${containerPort} → Host port ${hostPort}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (tunnelEnabled) {
|
|
220
|
+
const tunnelPorts = config.worker?.tunnel?.allowedPorts || [];
|
|
221
|
+
if (tunnelPorts.length === 0) {
|
|
222
|
+
console.warn(
|
|
223
|
+
"⚠️ Tunnel enabled but no allowedPorts configured. Add tunnel.allowedPorts to knowhow.json"
|
|
224
|
+
);
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`🌐 Tunnel enabled for ports: ${tunnelPorts.join(", ")}`);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
console.log(
|
|
230
|
+
"🚫 Tunnel disabled (enable in knowhow.json: worker.tunnel.enabled = true)"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Extract tunnel domain from API_URL
|
|
235
|
+
// e.g., "https://api.knowhow.tyvm.ai" -> "knowhow.tyvm.ai"
|
|
236
|
+
// e.g., "http://localhost:4000" -> "localhost:4000"
|
|
237
|
+
function extractTunnelDomain(apiUrl: string): {
|
|
238
|
+
domain: string;
|
|
239
|
+
useHttps: boolean;
|
|
240
|
+
} {
|
|
241
|
+
try {
|
|
242
|
+
const url = new URL(apiUrl);
|
|
243
|
+
const useHttps = url.protocol === "https:";
|
|
244
|
+
|
|
245
|
+
// For localhost, include port; for production, just use hostname
|
|
246
|
+
if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
|
|
247
|
+
return {
|
|
248
|
+
domain: `worker.${url.hostname}:${url.port || "80"}`,
|
|
249
|
+
useHttps,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { domain: `worker.${url.hostname}`, useHttps };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error("Failed to parse API_URL for tunnel domain:", err);
|
|
255
|
+
return { domain: "worker.localhost:4000", useHttps: false }; // fallback
|
|
256
|
+
}
|
|
257
|
+
}
|
|
181
258
|
|
|
182
259
|
async function connectWebSocket() {
|
|
183
260
|
const jwt = await loadJwt();
|
|
@@ -185,10 +262,12 @@ export async function worker(options?: {
|
|
|
185
262
|
|
|
186
263
|
const dir = process.cwd();
|
|
187
264
|
const homedir = os.homedir();
|
|
188
|
-
|
|
265
|
+
|
|
189
266
|
// Use environment variables if available (set by Docker), otherwise compute defaults
|
|
190
267
|
const hostname = process.env.WORKER_HOSTNAME || os.hostname();
|
|
191
|
-
const root =
|
|
268
|
+
const root =
|
|
269
|
+
process.env.WORKER_ROOT ||
|
|
270
|
+
(dir === homedir ? "~" : dir.replace(homedir, "~"));
|
|
192
271
|
|
|
193
272
|
const headers: Record<string, string> = {
|
|
194
273
|
Authorization: `Bearer ${jwt}`,
|
|
@@ -207,12 +286,61 @@ export async function worker(options?: {
|
|
|
207
286
|
console.log("🔒 Worker is private (only you can use it)");
|
|
208
287
|
}
|
|
209
288
|
|
|
289
|
+
const { domain: tunnelDomain, useHttps: tunnelUseHttps } =
|
|
290
|
+
extractTunnelDomain(API_URL);
|
|
291
|
+
|
|
210
292
|
const ws = new WebSocket(`${API_URL}/ws/worker`, {
|
|
211
293
|
headers,
|
|
212
294
|
});
|
|
213
295
|
|
|
296
|
+
// Create separate WebSocket connection for tunnel if enabled
|
|
297
|
+
let tunnelConnection: WebSocket | null = null;
|
|
298
|
+
if (tunnelEnabled) {
|
|
299
|
+
tunnelConnection = new WebSocket(`${API_URL}/ws/tunnel`, {
|
|
300
|
+
headers,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
tunnelConnection.on("open", () => {
|
|
304
|
+
console.log("Tunnel WebSocket connected");
|
|
305
|
+
|
|
306
|
+
// Initialize tunnel handler with the tunnel-specific WebSocket
|
|
307
|
+
tunnelHandler = createTunnelHandler(tunnelConnection!, {
|
|
308
|
+
allowedPorts: config.worker?.tunnel?.allowedPorts || [],
|
|
309
|
+
maxConcurrentStreams:
|
|
310
|
+
config.worker?.tunnel?.maxConcurrentStreams || 50,
|
|
311
|
+
localHost: tunnelLocalHost,
|
|
312
|
+
tunnelDomain,
|
|
313
|
+
tunnelUseHttps,
|
|
314
|
+
enableUrlRewriting:
|
|
315
|
+
config.worker?.tunnel?.enableUrlRewriting !== false,
|
|
316
|
+
portMapping,
|
|
317
|
+
logLevel: "info",
|
|
318
|
+
});
|
|
319
|
+
console.log("🌐 Tunnel handler initialized");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
tunnelConnection.on("close", (code, reason) => {
|
|
323
|
+
console.log(
|
|
324
|
+
`Tunnel WebSocket closed. Code: ${code}, Reason: ${reason.toString()}`
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Cleanup tunnel handler
|
|
328
|
+
if (tunnelHandler) {
|
|
329
|
+
tunnelHandler.cleanup();
|
|
330
|
+
tunnelHandler = null;
|
|
331
|
+
}
|
|
332
|
+
tunnelWs = null;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
tunnelConnection.on("error", (error) => {
|
|
336
|
+
console.error("Tunnel WebSocket error:", error);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
tunnelWs = tunnelConnection;
|
|
340
|
+
}
|
|
341
|
+
|
|
214
342
|
ws.on("open", () => {
|
|
215
|
-
console.log("
|
|
343
|
+
console.log("Worker WebSocket connected");
|
|
216
344
|
connected = true;
|
|
217
345
|
});
|
|
218
346
|
|
|
@@ -221,6 +349,12 @@ export async function worker(options?: {
|
|
|
221
349
|
`WebSocket closed. Code: ${code}, Reason: ${reason.toString()}`
|
|
222
350
|
);
|
|
223
351
|
console.log("Attempting to reconnect...");
|
|
352
|
+
|
|
353
|
+
// Cleanup tunnel handler
|
|
354
|
+
if (tunnelHandler) {
|
|
355
|
+
tunnelHandler = null;
|
|
356
|
+
}
|
|
357
|
+
|
|
224
358
|
connected = false;
|
|
225
359
|
});
|
|
226
360
|
|
|
@@ -230,12 +364,15 @@ export async function worker(options?: {
|
|
|
230
364
|
|
|
231
365
|
mcpServer.runWsServer(ws);
|
|
232
366
|
|
|
233
|
-
return { ws, mcpServer };
|
|
367
|
+
return { ws, mcpServer, tunnelWs };
|
|
234
368
|
}
|
|
235
369
|
|
|
236
370
|
while (true) {
|
|
237
|
-
let connection: {
|
|
238
|
-
|
|
371
|
+
let connection: {
|
|
372
|
+
ws: WebSocket;
|
|
373
|
+
mcpServer: McpServerService;
|
|
374
|
+
tunnelWs: WebSocket | null;
|
|
375
|
+
} | null = null;
|
|
239
376
|
|
|
240
377
|
if (!connected) {
|
|
241
378
|
console.log("Attempting to connect...");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getConfig } from "../../config";
|
|
2
|
+
import { Tool } from "../../clients/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tool to list all forwarded ports configured for the worker tunnel
|
|
6
|
+
* This reads from the worker.tunnel.allowedPorts configuration
|
|
7
|
+
*/
|
|
8
|
+
export async function listAllowedPorts(): Promise<number[]> {
|
|
9
|
+
const config = await getConfig();
|
|
10
|
+
|
|
11
|
+
if (!config.worker?.tunnel?.enabled) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return config.worker.tunnel.allowedPorts || [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const listAllowedPortsDefinition: Tool = {
|
|
19
|
+
type: "function" as const,
|
|
20
|
+
function: {
|
|
21
|
+
name: "listAllowedPorts",
|
|
22
|
+
description:
|
|
23
|
+
"List all ports that are being forwarded through the worker tunnel. Returns an array of port numbers that can be accessed via the tunnel system.",
|
|
24
|
+
parameters: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {},
|
|
27
|
+
required: [],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -7,7 +7,11 @@ import { getConfig, getLanguageConfig } from "../../../src/config";
|
|
|
7
7
|
jest.mock("../../../src/utils", () => ({
|
|
8
8
|
readFile: jest.fn(),
|
|
9
9
|
fileExists: jest.fn().mockReturnValue(true),
|
|
10
|
-
fileStat: jest.fn()
|
|
10
|
+
fileStat: jest.fn().mockResolvedValue({
|
|
11
|
+
isDirectory: jest.fn().mockReturnValue(false),
|
|
12
|
+
isFile: jest.fn().mockReturnValue(true),
|
|
13
|
+
size: 1024,
|
|
14
|
+
}),
|
|
11
15
|
}));
|
|
12
16
|
|
|
13
17
|
jest.mock("../../../src/services/EventService", () => ({
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
jest.mock("../../../src/utils", () => ({
|
|
2
2
|
readFile: jest.fn().mockReturnValue(Buffer.from("test")),
|
|
3
3
|
fileExists: jest.fn().mockReturnValue(true),
|
|
4
|
-
fileStat: jest.fn()
|
|
4
|
+
fileStat: jest.fn().mockResolvedValue({
|
|
5
|
+
isDirectory: jest.fn().mockReturnValue(false),
|
|
6
|
+
isFile: jest.fn().mockReturnValue(true),
|
|
7
|
+
size: 1024,
|
|
8
|
+
}),
|
|
5
9
|
}));
|
|
6
10
|
|
|
7
11
|
jest.mock("../../../src/services/EventService", () => ({
|