@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.
Files changed (73) hide show
  1. package/package.json +2 -1
  2. package/src/chat/modules/AgentModule.ts +18 -2
  3. package/src/cli.ts +5 -4
  4. package/src/clients/anthropic.ts +68 -5
  5. package/src/config.ts +7 -0
  6. package/src/processors/Base64ImageDetector.ts +193 -40
  7. package/src/processors/index.ts +1 -1
  8. package/src/services/AgentSynchronization.ts +27 -10
  9. package/src/types.ts +11 -0
  10. package/src/worker.ts +145 -8
  11. package/src/workers/tools/index.ts +10 -0
  12. package/src/workers/tools/listAllowedPorts.ts +30 -0
  13. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -1
  14. package/tests/plugins/language/languagePlugin.test.ts +5 -1
  15. package/tests/processors/Base64ImageDetector.test.ts +263 -70
  16. package/tests/services/Tools.test.ts +6 -4
  17. package/ts_build/package.json +2 -1
  18. package/ts_build/src/chat/modules/AgentModule.js +12 -2
  19. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  20. package/ts_build/src/cli.js +5 -4
  21. package/ts_build/src/cli.js.map +1 -1
  22. package/ts_build/src/clients/anthropic.d.ts +9 -0
  23. package/ts_build/src/clients/anthropic.js +61 -5
  24. package/ts_build/src/clients/anthropic.js.map +1 -1
  25. package/ts_build/src/config.js +6 -0
  26. package/ts_build/src/config.js.map +1 -1
  27. package/ts_build/src/processors/Base64ImageDetector.d.ts +7 -3
  28. package/ts_build/src/processors/Base64ImageDetector.js +147 -27
  29. package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
  30. package/ts_build/src/processors/index.d.ts +1 -1
  31. package/ts_build/src/processors/index.js +2 -2
  32. package/ts_build/src/processors/index.js.map +1 -1
  33. package/ts_build/src/services/AgentSynchronization.d.ts +2 -0
  34. package/ts_build/src/services/AgentSynchronization.js +20 -10
  35. package/ts_build/src/services/AgentSynchronization.js.map +1 -1
  36. package/ts_build/src/types.d.ts +11 -0
  37. package/ts_build/src/types.js +1 -0
  38. package/ts_build/src/types.js.map +1 -1
  39. package/ts_build/src/worker/handlers/proxyHandler.d.ts +2 -0
  40. package/ts_build/src/worker/handlers/proxyHandler.js +41 -0
  41. package/ts_build/src/worker/handlers/proxyHandler.js.map +1 -0
  42. package/ts_build/src/worker/tools/index.d.ts +1 -0
  43. package/ts_build/src/worker/tools/index.js +18 -0
  44. package/ts_build/src/worker/tools/index.js.map +1 -0
  45. package/ts_build/src/worker/tools/portForwarding.d.ts +49 -0
  46. package/ts_build/src/worker/tools/portForwarding.js +173 -0
  47. package/ts_build/src/worker/tools/portForwarding.js.map +1 -0
  48. package/ts_build/src/worker/types/proxy.d.ts +18 -0
  49. package/ts_build/src/worker/types/proxy.js +3 -0
  50. package/ts_build/src/worker/types/proxy.js.map +1 -0
  51. package/ts_build/src/worker.js +119 -3
  52. package/ts_build/src/worker.js.map +1 -1
  53. package/ts_build/src/workers/tools/index.d.ts +9 -0
  54. package/ts_build/src/workers/tools/index.js +23 -0
  55. package/ts_build/src/workers/tools/index.js.map +1 -0
  56. package/ts_build/src/workers/tools/listAllowedPorts.d.ts +3 -0
  57. package/ts_build/src/workers/tools/listAllowedPorts.js +25 -0
  58. package/ts_build/src/workers/tools/listAllowedPorts.js.map +1 -0
  59. package/ts_build/src/workers/tools/listForwardedPorts.d.ts +10 -0
  60. package/ts_build/src/workers/tools/listForwardedPorts.js +22 -0
  61. package/ts_build/src/workers/tools/listForwardedPorts.js.map +1 -0
  62. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -1
  63. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  64. package/ts_build/tests/plugins/language/languagePlugin.test.js +5 -1
  65. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
  66. package/ts_build/tests/processors/Base64ImageDetector.test.js +221 -59
  67. package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
  68. package/ts_build/tests/services/Tools.test.js +3 -3
  69. package/ts_build/tests/services/Tools.test.js.map +1 -1
  70. package/ts_build/tests/worker/handlers/proxyHandler.test.d.ts +1 -0
  71. package/ts_build/tests/worker/handlers/proxyHandler.test.js +170 -0
  72. package/ts_build/tests/worker/handlers/proxyHandler.test.js.map +1 -0
  73. 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("🐳 Already running inside Docker container, skipping sandbox mode");
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 = process.env.WORKER_ROOT || (dir === homedir ? "~" : dir.replace(homedir, "~"));
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("Connected to the server");
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: { ws: WebSocket; mcpServer: McpServerService } | null =
238
- null;
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,10 @@
1
+ export * from "./listAllowedPorts";
2
+ import {
3
+ listAllowedPorts,
4
+ listAllowedPortsDefinition,
5
+ } from "./listAllowedPorts";
6
+
7
+ export default {
8
+ tools: { listAllowedPorts },
9
+ definitions: [listAllowedPortsDefinition],
10
+ };
@@ -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", () => ({