@tyvm/knowhow 0.0.108-dev.879609c → 0.0.108-dev.c47492f
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 -2
- package/src/cli.ts +45 -21
- package/src/services/modules/index.ts +21 -17
- package/src/tunnel.ts +216 -0
- package/src/worker.ts +65 -336
- package/src/workers/auth/WsMiddleware.ts +99 -0
- package/src/workers/auth/authMiddleware.ts +104 -0
- package/src/workers/auth/types.ts +14 -2
- package/ts_build/package.json +2 -2
- package/ts_build/src/cli.js +17 -9
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +30 -0
- package/ts_build/src/services/modules/index.js +9 -14
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/tunnel.d.ts +27 -0
- package/ts_build/src/tunnel.js +112 -0
- package/ts_build/src/tunnel.js.map +1 -0
- package/ts_build/src/worker.d.ts +1 -4
- package/ts_build/src/worker.js +38 -244
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
- package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
- package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
- package/ts_build/src/workers/auth/authMiddleware.js +60 -0
- package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.108-dev.
|
|
3
|
+
"version": "0.0.108-dev.c47492f",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@inquirer/editor": "^4.2.18",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.13.3",
|
|
57
57
|
"@simplewebauthn/server": "^13.3.0",
|
|
58
|
-
"@tyvm/knowhow-tunnel": "0.0.
|
|
58
|
+
"@tyvm/knowhow-tunnel": "0.0.5",
|
|
59
59
|
"commander": "^14.0.0",
|
|
60
60
|
"diff": "^5.2.0",
|
|
61
61
|
"express": "^4.19.2",
|
package/src/cli.ts
CHANGED
|
@@ -14,7 +14,8 @@ import { includedTools } from "./agents/tools/list";
|
|
|
14
14
|
import * as allTools from "./agents/tools";
|
|
15
15
|
import { LazyToolsService, services } from "./services";
|
|
16
16
|
import { login } from "./login";
|
|
17
|
-
import { worker
|
|
17
|
+
import { worker } from "./worker";
|
|
18
|
+
import { TUNNEL_MINIMAL_TOOLS } from "./tunnel";
|
|
18
19
|
import { fileSync } from "./fileSync";
|
|
19
20
|
import { KnowhowSimpleClient } from "./services/KnowhowClient";
|
|
20
21
|
import { ModulesService } from "./services/modules";
|
|
@@ -44,23 +45,28 @@ import { CliChatService } from "./chat/CliChatService";
|
|
|
44
45
|
// Without this, a single failing MCP server (e.g. expired Notion token) will
|
|
45
46
|
// crash the entire CLI with an unhandled rejection.
|
|
46
47
|
process.on("unhandledRejection", (reason: unknown) => {
|
|
47
|
-
const message =
|
|
48
|
-
reason instanceof Error ? reason.message : String(reason);
|
|
48
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
49
49
|
// Only warn — don't exit. The MCP connect errors are recoverable;
|
|
50
50
|
// the server will simply be unavailable but others continue working.
|
|
51
|
-
console.warn(
|
|
52
|
-
`⚠ Unhandled MCP/async error (non-fatal): ${message}`
|
|
53
|
-
);
|
|
51
|
+
console.warn(`⚠ Unhandled MCP/async error (non-fatal): ${message}`);
|
|
54
52
|
});
|
|
55
53
|
|
|
56
54
|
async function setupServices() {
|
|
57
|
-
const {
|
|
55
|
+
const {
|
|
56
|
+
Agents,
|
|
57
|
+
Mcp,
|
|
58
|
+
Clients,
|
|
59
|
+
Tools: AllTools,
|
|
60
|
+
Embeddings,
|
|
61
|
+
Plugins,
|
|
62
|
+
MediaProcessor,
|
|
63
|
+
} = services();
|
|
58
64
|
const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
|
|
59
65
|
|
|
60
66
|
// Load modules from config first so module-provided tools/agents/plugins are available
|
|
61
67
|
// We need to wireup the LazyTools to be connected to the same singletons that are in services()
|
|
62
68
|
Tools.setContext({
|
|
63
|
-
...
|
|
69
|
+
...AllTools.getContext(),
|
|
64
70
|
});
|
|
65
71
|
|
|
66
72
|
// Build the AgentContext with the fully-populated LazyToolsService so every
|
|
@@ -107,16 +113,17 @@ async function setupServices() {
|
|
|
107
113
|
const modulesService = new ModulesService();
|
|
108
114
|
await modulesService.loadModulesFromConfig({
|
|
109
115
|
Agents,
|
|
110
|
-
Embeddings
|
|
111
|
-
Plugins
|
|
116
|
+
Embeddings,
|
|
117
|
+
Plugins,
|
|
112
118
|
Clients,
|
|
119
|
+
|
|
113
120
|
// Use LazyToolsService so module-provided tools are visible to agents and scripts
|
|
114
|
-
Tools
|
|
115
|
-
MediaProcessor
|
|
121
|
+
Tools,
|
|
122
|
+
MediaProcessor,
|
|
116
123
|
});
|
|
117
124
|
|
|
118
|
-
// Return both LazyToolsService (for agents) and
|
|
119
|
-
return { Tools, Clients
|
|
125
|
+
// Return both LazyToolsService (for agents) and AllTools (plain ToolsService with all tools for scripts)
|
|
126
|
+
return { Tools, Clients };
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
// Utility function to read from stdin
|
|
@@ -528,14 +535,25 @@ async function main() {
|
|
|
528
535
|
program
|
|
529
536
|
.command("cloudworker")
|
|
530
537
|
.description("Create or sync a cloud worker with your local knowhow config")
|
|
531
|
-
.option(
|
|
532
|
-
|
|
533
|
-
|
|
538
|
+
.option(
|
|
539
|
+
"--create",
|
|
540
|
+
"Create a new cloud worker with synced config and files"
|
|
541
|
+
)
|
|
542
|
+
.option(
|
|
543
|
+
"--push <uid>",
|
|
544
|
+
"Push/sync local config and files to an existing cloud worker"
|
|
545
|
+
)
|
|
546
|
+
.option(
|
|
547
|
+
"--pull <id>",
|
|
548
|
+
"Pull the latest workerConfigJson from a cloud worker and update local config"
|
|
549
|
+
)
|
|
534
550
|
.option("--name <name>", "Name for the cloud worker (used with --create)")
|
|
535
551
|
.option("--dry-run", "Print what would be synced without doing it")
|
|
536
552
|
.action(async (options) => {
|
|
537
553
|
try {
|
|
538
|
-
const { cloudWorker, pullCloudWorkerConfig } = await import(
|
|
554
|
+
const { cloudWorker, pullCloudWorkerConfig } = await import(
|
|
555
|
+
"./cloudWorker"
|
|
556
|
+
);
|
|
539
557
|
if (options.pull) {
|
|
540
558
|
await pullCloudWorkerConfig({ id: options.pull });
|
|
541
559
|
} else {
|
|
@@ -550,7 +568,9 @@ async function main() {
|
|
|
550
568
|
program
|
|
551
569
|
.command("tunnel")
|
|
552
570
|
.description(
|
|
553
|
-
"Start tunnel
|
|
571
|
+
"Start a minimal worker with tunnel enabled: exposes local ports to the cloud. " +
|
|
572
|
+
"Registers essential tools (unlock, lock, listAllowedPorts) so the backend is aware of the worker and ports. " +
|
|
573
|
+
"If passkey auth is configured, the tunnel is locked until unlocked via tool call or WebSocket auth protocol."
|
|
554
574
|
)
|
|
555
575
|
.option(
|
|
556
576
|
"--share",
|
|
@@ -558,10 +578,14 @@ async function main() {
|
|
|
558
578
|
)
|
|
559
579
|
.option("--unshare", "Make this tunnel private (only you can use it)")
|
|
560
580
|
.action(async (options) => {
|
|
561
|
-
|
|
581
|
+
console.log("🌐 Starting tunnel (minimal worker) mode...");
|
|
582
|
+
console.log(` Tools: ${TUNNEL_MINIMAL_TOOLS.join(", ")}`);
|
|
583
|
+
await worker({
|
|
584
|
+
...options,
|
|
585
|
+
allowedTools: TUNNEL_MINIMAL_TOOLS,
|
|
586
|
+
});
|
|
562
587
|
});
|
|
563
588
|
|
|
564
|
-
|
|
565
589
|
program
|
|
566
590
|
.command("script")
|
|
567
591
|
.description("Run a local tool script file using the executeScript sandbox")
|
|
@@ -5,20 +5,21 @@ import { services } from "../";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
|
|
7
7
|
export class ModulesService {
|
|
8
|
+
async getDefaultContext() {
|
|
9
|
+
return { ...services() };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async overrideDefaultContext(overrides: Partial<ModuleContext>) {
|
|
13
|
+
const defaultContext = await this.getDefaultContext();
|
|
14
|
+
return { ...defaultContext, ...overrides };
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
async loadModulesFromConfig(context?: ModuleContext) {
|
|
9
18
|
const config = await getConfig();
|
|
10
19
|
|
|
11
20
|
// If no context provided, fall back to global singletons
|
|
12
21
|
if (!context) {
|
|
13
|
-
|
|
14
|
-
context = {
|
|
15
|
-
Agents,
|
|
16
|
-
Embeddings,
|
|
17
|
-
Plugins,
|
|
18
|
-
Clients,
|
|
19
|
-
Tools,
|
|
20
|
-
MediaProcessor,
|
|
21
|
-
};
|
|
22
|
+
context = { ...(await this.getDefaultContext()) };
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Use the toolsService from context
|
|
@@ -43,9 +44,13 @@ export class ModulesService {
|
|
|
43
44
|
: modulePath;
|
|
44
45
|
const rawModule = require(resolvedPath);
|
|
45
46
|
const importedModule = (rawModule.default || rawModule) as KnowhowModule;
|
|
46
|
-
console.log(
|
|
47
|
+
console.log(
|
|
48
|
+
`🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`
|
|
49
|
+
);
|
|
47
50
|
await importedModule.init({ config, cwd: process.cwd(), context });
|
|
48
|
-
console.log(
|
|
51
|
+
console.log(
|
|
52
|
+
`✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`
|
|
53
|
+
);
|
|
49
54
|
|
|
50
55
|
for (const agent of importedModule.agents) {
|
|
51
56
|
agentService.registerAgent(agent);
|
|
@@ -58,13 +63,12 @@ export class ModulesService {
|
|
|
58
63
|
|
|
59
64
|
for (const plugin of importedModule.plugins) {
|
|
60
65
|
const pluginContext = {
|
|
61
|
-
|
|
62
|
-
Clients: clients,
|
|
63
|
-
Tools: toolsService,
|
|
64
|
-
Plugins: pluginService,
|
|
65
|
-
...(context.MediaProcessor ? { MediaProcessor: context.MediaProcessor } : {}),
|
|
66
|
+
...context,
|
|
66
67
|
};
|
|
67
|
-
pluginService.registerPlugin(
|
|
68
|
+
pluginService.registerPlugin(
|
|
69
|
+
plugin.name,
|
|
70
|
+
new plugin.plugin(pluginContext as any)
|
|
71
|
+
);
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
for (const client of importedModule.clients) {
|
package/src/tunnel.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
import { createTunnelHandler, TunnelHandler } from "@tyvm/knowhow-tunnel";
|
|
4
|
+
import { loadJwt } from "./login";
|
|
5
|
+
import { wait } from "./utils";
|
|
6
|
+
import { getConfig } from "./config";
|
|
7
|
+
import { KNOWHOW_API_URL } from "./services/KnowhowClient";
|
|
8
|
+
import { ModulesService } from "./services/modules";
|
|
9
|
+
import { WorkerPasskeyAuthService } from "./workers/auth/WorkerPasskeyAuth";
|
|
10
|
+
import { WsMiddlewareStack } from "./workers/auth/WsMiddleware";
|
|
11
|
+
import { makeAuthMiddleware } from "./workers/auth/authMiddleware";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract the tunnel domain and protocol from the API URL.
|
|
15
|
+
* e.g., "https://api.knowhow.tyvm.ai" -> { domain: "worker.knowhow.tyvm.ai", useHttps: true }
|
|
16
|
+
* e.g., "http://localhost:4000" -> { domain: "worker.localhost:4000", useHttps: false }
|
|
17
|
+
*/
|
|
18
|
+
export function extractTunnelDomain(apiUrl: string): {
|
|
19
|
+
domain: string;
|
|
20
|
+
useHttps: boolean;
|
|
21
|
+
} {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(apiUrl);
|
|
24
|
+
const useHttps = url.protocol === "https:";
|
|
25
|
+
|
|
26
|
+
// For localhost, include port; for production, just use hostname
|
|
27
|
+
if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
|
|
28
|
+
return {
|
|
29
|
+
domain: `worker.${url.hostname}:${url.port || "80"}`,
|
|
30
|
+
useHttps,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { domain: `worker.${url.hostname}`, useHttps };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error("Failed to parse API_URL for tunnel domain:", err);
|
|
36
|
+
return { domain: "worker.localhost:4000", useHttps: false }; // fallback
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize a tunnel handler and load tunnel modules.
|
|
42
|
+
*/
|
|
43
|
+
export async function initTunnelHandler(
|
|
44
|
+
tunnelConnection: WebSocket,
|
|
45
|
+
tunnelConfig: Parameters<typeof createTunnelHandler>[1]
|
|
46
|
+
): Promise<TunnelHandler> {
|
|
47
|
+
const handler = createTunnelHandler(tunnelConnection, tunnelConfig);
|
|
48
|
+
console.log("🌐 Tunnel handler initialized");
|
|
49
|
+
console.log(tunnelConfig);
|
|
50
|
+
|
|
51
|
+
const tunnelModuleService = new ModulesService();
|
|
52
|
+
const tunnelContext = await tunnelModuleService.overrideDefaultContext({
|
|
53
|
+
Tunnel: handler,
|
|
54
|
+
});
|
|
55
|
+
tunnelModuleService.loadModulesFromConfig(tunnelContext).catch((err) => {
|
|
56
|
+
console.error("Failed to load tunnel modules:", err);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return handler;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve tunnel local host, log port mapping, and return shared tunnel setup values.
|
|
64
|
+
* Extracted to avoid duplication between worker() and tunnel().
|
|
65
|
+
*/
|
|
66
|
+
export function resolveTunnelConfig(
|
|
67
|
+
config: Awaited<ReturnType<typeof getConfig>>,
|
|
68
|
+
isInsideDocker: boolean
|
|
69
|
+
): { tunnelLocalHost: string; portMapping: Record<string, number> } {
|
|
70
|
+
// Determine localHost based on environment
|
|
71
|
+
let tunnelLocalHost = config.worker?.tunnel?.localHost;
|
|
72
|
+
if (!tunnelLocalHost) {
|
|
73
|
+
if (isInsideDocker) {
|
|
74
|
+
tunnelLocalHost = "host.docker.internal";
|
|
75
|
+
console.log(
|
|
76
|
+
"🐳 Docker detected: tunnel will use host.docker.internal to reach host services"
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
tunnelLocalHost = "127.0.0.1";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for port mapping configuration
|
|
84
|
+
const portMapping = (config.worker?.tunnel?.portMapping || {}) as Record<string, number>;
|
|
85
|
+
if (Object.keys(portMapping).length > 0) {
|
|
86
|
+
console.log("🔀 Port mapping configured:");
|
|
87
|
+
for (const [containerPort, hostPort] of Object.entries(portMapping)) {
|
|
88
|
+
console.log(` Container port ${containerPort} → Host port ${hostPort}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { tunnelLocalHost, portMapping };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Options for connectTunnelWebSocket helper.
|
|
97
|
+
*/
|
|
98
|
+
export interface TunnelWebSocketOptions {
|
|
99
|
+
/** Already-resolved tunnel domain (hostname only, no protocol) */
|
|
100
|
+
tunnelDomain: string;
|
|
101
|
+
/** Whether the tunnel should use HTTPS */
|
|
102
|
+
tunnelUseHttps: boolean;
|
|
103
|
+
/** Local host to forward tunnel traffic to */
|
|
104
|
+
tunnelLocalHost: string;
|
|
105
|
+
/** Port mapping configuration */
|
|
106
|
+
portMapping: Record<string, number>;
|
|
107
|
+
/** Worker config (for tunnel sub-config) */
|
|
108
|
+
config: Awaited<ReturnType<typeof getConfig>>;
|
|
109
|
+
/** HTTP headers to attach to the WebSocket upgrade request */
|
|
110
|
+
headers: Record<string, string>;
|
|
111
|
+
/** Callback invoked with the TunnelHandler once the connection opens */
|
|
112
|
+
onOpen?: (handler: TunnelHandler) => void;
|
|
113
|
+
/** Called when the connection closes; receives code + reason string */
|
|
114
|
+
onClose?: (code: number, reason: string) => void;
|
|
115
|
+
/** Called on error */
|
|
116
|
+
onError?: (error: Error) => void;
|
|
117
|
+
/** Optional passkey auth service — if provided, applies WS middleware to gate tunnel traffic */
|
|
118
|
+
authService?: WorkerPasskeyAuthService | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a tunnel WebSocket connection, build the tunnelConfig, and
|
|
123
|
+
* initialize the tunnel handler. Returns the WebSocket.
|
|
124
|
+
*
|
|
125
|
+
* The caller is responsible for storing a reference to the returned TunnelHandler
|
|
126
|
+
* (via onOpen) and performing any outer-state cleanup (via onClose / onError).
|
|
127
|
+
*/
|
|
128
|
+
export function connectTunnelWebSocket(
|
|
129
|
+
options: TunnelWebSocketOptions
|
|
130
|
+
): WebSocket {
|
|
131
|
+
const {
|
|
132
|
+
tunnelDomain,
|
|
133
|
+
tunnelUseHttps,
|
|
134
|
+
tunnelLocalHost,
|
|
135
|
+
portMapping,
|
|
136
|
+
config,
|
|
137
|
+
headers,
|
|
138
|
+
onOpen,
|
|
139
|
+
onClose,
|
|
140
|
+
onError,
|
|
141
|
+
authService,
|
|
142
|
+
} = options;
|
|
143
|
+
|
|
144
|
+
const tunnelConnection = new WebSocket(`${KNOWHOW_API_URL}/ws/tunnel`, { headers });
|
|
145
|
+
|
|
146
|
+
tunnelConnection.on("open", async () => {
|
|
147
|
+
console.log("Tunnel WebSocket connected");
|
|
148
|
+
|
|
149
|
+
// Apply passkey auth middleware FIRST, before tunnel handler registers its
|
|
150
|
+
// "message" listener. Node.js EventEmitter fires listeners in registration
|
|
151
|
+
// order, so our middleware runs first. wrapSocket() also redirects future
|
|
152
|
+
// ws.on("message", ...) calls to an inner emitter, ensuring the tunnel
|
|
153
|
+
// handler only receives messages that passed the middleware.
|
|
154
|
+
if (authService) {
|
|
155
|
+
const stack = new WsMiddlewareStack();
|
|
156
|
+
stack.use(makeAuthMiddleware(authService));
|
|
157
|
+
stack.wrapSocket(tunnelConnection);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const allowedPorts = config.worker?.tunnel?.allowedPorts || [];
|
|
161
|
+
|
|
162
|
+
// Create URL rewriter callback that returns the hostname (without protocol).
|
|
163
|
+
// The tunnel package will add the protocol based on the useHttps config.
|
|
164
|
+
const urlRewriter = (port: number, metadata?: any) => {
|
|
165
|
+
const workerId = metadata?.workerId;
|
|
166
|
+
const secret = metadata?.secret;
|
|
167
|
+
// Examples: secret-p3000.worker.example.com / workerId-p3000.worker.example.com
|
|
168
|
+
const subdomain = secret
|
|
169
|
+
? `${secret}-p${port}`
|
|
170
|
+
: `${workerId}-p${port}`;
|
|
171
|
+
return `${subdomain}.${tunnelDomain}`;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const tunnelConfig = {
|
|
175
|
+
allowedPorts,
|
|
176
|
+
maxConcurrentStreams: config.worker?.tunnel?.maxConcurrentStreams || 50,
|
|
177
|
+
tunnelUseHttps,
|
|
178
|
+
localHost: tunnelLocalHost,
|
|
179
|
+
urlRewriter,
|
|
180
|
+
enableUrlRewriting: config.worker?.tunnel?.enableUrlRewriting !== false,
|
|
181
|
+
portMapping,
|
|
182
|
+
logLevel: "debug" as const,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const handler = await initTunnelHandler(tunnelConnection, tunnelConfig);
|
|
186
|
+
onOpen?.(handler);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
tunnelConnection.on("close", (code, reason) => {
|
|
190
|
+
console.log(
|
|
191
|
+
`Tunnel WebSocket closed. Code: ${code}, Reason: ${reason.toString()}`
|
|
192
|
+
);
|
|
193
|
+
onClose?.(code, reason.toString());
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
tunnelConnection.on("error", (error) => {
|
|
197
|
+
console.error("Tunnel WebSocket error:", error);
|
|
198
|
+
onError?.(error);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return tunnelConnection;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The minimal set of tool names that are always registered when running in
|
|
206
|
+
* tunnel mode. These are the tools the backend and frontend need to interact
|
|
207
|
+
* with the tunnel worker (port discovery, passkey auth).
|
|
208
|
+
*
|
|
209
|
+
* Additional tools can be added here in the future without changing the CLI.
|
|
210
|
+
*/
|
|
211
|
+
export const TUNNEL_MINIMAL_TOOLS = [
|
|
212
|
+
"listAllowedPorts",
|
|
213
|
+
"unlock",
|
|
214
|
+
"lock",
|
|
215
|
+
"reloadConfig",
|
|
216
|
+
];
|