@tyvm/knowhow 0.0.108-dev.126b29e → 0.0.108-dev.80f1ac6

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 (172) hide show
  1. package/package.json +2 -3
  2. package/src/agents/base/base.ts +9 -0
  3. package/src/agents/tools/index.ts +0 -1
  4. package/src/agents/tools/list.ts +0 -2
  5. package/src/chat/CliChatService.ts +10 -1
  6. package/src/chat/renderer/CompactRenderer.ts +20 -0
  7. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  8. package/src/chat/renderer/FancyRenderer.ts +19 -0
  9. package/src/chat/renderer/types.ts +11 -0
  10. package/src/cli.ts +91 -664
  11. package/src/clients/index.ts +6 -5
  12. package/src/clients/types.ts +12 -4
  13. package/src/commands/agent.ts +246 -0
  14. package/src/commands/misc.ts +174 -0
  15. package/src/commands/modules.ts +182 -0
  16. package/src/commands/services.ts +77 -0
  17. package/src/commands/workers.ts +160 -0
  18. package/src/config.ts +37 -0
  19. package/src/fileSync.ts +14 -2
  20. package/src/index.ts +1 -0
  21. package/src/logger.ts +197 -0
  22. package/src/plugins/plugins.ts +0 -21
  23. package/src/processors/JsonCompressor.ts +6 -6
  24. package/src/services/EventService.ts +61 -1
  25. package/src/services/KnowhowClient.ts +12 -2
  26. package/src/services/S3.ts +0 -10
  27. package/src/services/modules/index.ts +70 -50
  28. package/src/services/modules/types.ts +4 -0
  29. package/src/tunnel.ts +216 -0
  30. package/src/types.ts +0 -1
  31. package/src/worker.ts +65 -336
  32. package/src/workers/auth/WsMiddleware.ts +99 -0
  33. package/src/workers/auth/authMiddleware.ts +104 -0
  34. package/src/workers/auth/types.ts +14 -2
  35. package/tests/unit/commands/github-credentials.test.ts +211 -0
  36. package/tests/unit/modules/moduleLoading.test.ts +39 -37
  37. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  38. package/ts_build/package.json +2 -3
  39. package/ts_build/src/agents/base/base.js +10 -0
  40. package/ts_build/src/agents/base/base.js.map +1 -1
  41. package/ts_build/src/agents/tools/index.d.ts +0 -1
  42. package/ts_build/src/agents/tools/index.js +0 -1
  43. package/ts_build/src/agents/tools/index.js.map +1 -1
  44. package/ts_build/src/agents/tools/list.js +0 -2
  45. package/ts_build/src/agents/tools/list.js.map +1 -1
  46. package/ts_build/src/chat/CliChatService.js +13 -1
  47. package/ts_build/src/chat/CliChatService.js.map +1 -1
  48. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  49. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  50. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  51. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  52. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  53. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  54. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  55. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  56. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  57. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  58. package/ts_build/src/cli.js +47 -525
  59. package/ts_build/src/cli.js.map +1 -1
  60. package/ts_build/src/clients/index.js +2 -4
  61. package/ts_build/src/clients/index.js.map +1 -1
  62. package/ts_build/src/clients/types.d.ts +2 -2
  63. package/ts_build/src/commands/agent.d.ts +6 -0
  64. package/ts_build/src/commands/agent.js +229 -0
  65. package/ts_build/src/commands/agent.js.map +1 -0
  66. package/ts_build/src/commands/misc.d.ts +10 -0
  67. package/ts_build/src/commands/misc.js +197 -0
  68. package/ts_build/src/commands/misc.js.map +1 -0
  69. package/ts_build/src/commands/modules.d.ts +3 -0
  70. package/ts_build/src/commands/modules.js +160 -0
  71. package/ts_build/src/commands/modules.js.map +1 -0
  72. package/ts_build/src/commands/services.d.ts +5 -0
  73. package/ts_build/src/commands/services.js +87 -0
  74. package/ts_build/src/commands/services.js.map +1 -0
  75. package/ts_build/src/commands/workers.d.ts +6 -0
  76. package/ts_build/src/commands/workers.js +163 -0
  77. package/ts_build/src/commands/workers.js.map +1 -0
  78. package/ts_build/src/config.d.ts +1 -0
  79. package/ts_build/src/config.js +32 -0
  80. package/ts_build/src/config.js.map +1 -1
  81. package/ts_build/src/fileSync.js +10 -2
  82. package/ts_build/src/fileSync.js.map +1 -1
  83. package/ts_build/src/index.d.ts +1 -0
  84. package/ts_build/src/index.js +3 -1
  85. package/ts_build/src/index.js.map +1 -1
  86. package/ts_build/src/logger.d.ts +21 -0
  87. package/ts_build/src/logger.js +106 -0
  88. package/ts_build/src/logger.js.map +1 -0
  89. package/ts_build/src/plugins/plugins.d.ts +0 -2
  90. package/ts_build/src/plugins/plugins.js +0 -11
  91. package/ts_build/src/plugins/plugins.js.map +1 -1
  92. package/ts_build/src/processors/JsonCompressor.js +4 -4
  93. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  94. package/ts_build/src/services/EventService.d.ts +6 -1
  95. package/ts_build/src/services/EventService.js +29 -0
  96. package/ts_build/src/services/EventService.js.map +1 -1
  97. package/ts_build/src/services/KnowhowClient.d.ts +1 -1
  98. package/ts_build/src/services/KnowhowClient.js +8 -2
  99. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  100. package/ts_build/src/services/S3.js +0 -7
  101. package/ts_build/src/services/S3.js.map +1 -1
  102. package/ts_build/src/services/modules/index.d.ts +33 -0
  103. package/ts_build/src/services/modules/index.js +46 -45
  104. package/ts_build/src/services/modules/index.js.map +1 -1
  105. package/ts_build/src/services/modules/types.d.ts +4 -0
  106. package/ts_build/src/tunnel.d.ts +27 -0
  107. package/ts_build/src/tunnel.js +112 -0
  108. package/ts_build/src/tunnel.js.map +1 -0
  109. package/ts_build/src/types.d.ts +0 -1
  110. package/ts_build/src/types.js.map +1 -1
  111. package/ts_build/src/worker.d.ts +1 -4
  112. package/ts_build/src/worker.js +38 -244
  113. package/ts_build/src/worker.js.map +1 -1
  114. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  115. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  116. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  117. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  118. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  119. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  120. package/ts_build/src/workers/auth/types.d.ts +8 -1
  121. package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
  122. package/ts_build/tests/unit/commands/github-credentials.test.js +146 -0
  123. package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
  124. package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
  125. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  126. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  127. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  128. package/src/agents/tools/executeScript/README.md +0 -94
  129. package/src/agents/tools/executeScript/definition.ts +0 -79
  130. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  131. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  132. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  133. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  134. package/src/agents/tools/executeScript/index.ts +0 -98
  135. package/src/services/script-execution/SandboxContext.ts +0 -282
  136. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  137. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  138. package/src/services/script-execution/ScriptTracer.ts +0 -249
  139. package/src/services/script-execution/types.ts +0 -134
  140. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  141. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  142. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  143. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  144. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  145. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  146. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  147. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  148. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  149. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  150. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  151. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  152. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  153. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  154. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  155. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  156. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  157. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  158. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  159. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  160. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  161. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  162. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  163. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  164. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  165. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  166. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  167. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  168. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  169. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  170. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  171. package/ts_build/src/services/script-execution/types.js +0 -3
  172. package/ts_build/src/services/script-execution/types.js.map +0 -1
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from "events";
2
2
  import { IAgent } from "../agents/interface";
3
3
 
4
+ export type LogLevel = "info" | "warn" | "error";
5
+
4
6
  export type EventHandlerFn = (...args: any[]) => any;
5
7
 
6
8
  export interface EventHandler {
@@ -31,9 +33,38 @@ type ManagedListenerRecord = {
31
33
  blocking: boolean;
32
34
  };
33
35
 
36
+ /**
37
+ * Default console handler for plugin:log events.
38
+ * Active when no renderer has taken over (e.g. worker mode, CLI before chat starts).
39
+ * Can be suppressed by calling suppressDefaultLogger() when a renderer is active.
40
+ *
41
+ * IMPORTANT: Uses process.stdout/stderr directly to avoid infinite recursion
42
+ * with logger.installConsoleOverload() which overrides console.log/warn.
43
+ */
44
+ function defaultConsoleLogHandler(event: {
45
+ source: string;
46
+ message: string;
47
+ level: LogLevel;
48
+ }): void {
49
+ const prefix = event.source ? `[${event.source}] ` : "";
50
+ const line = `${prefix}${event.message}\n`;
51
+ switch (event.level) {
52
+ case "warn":
53
+ process.stderr.write(line);
54
+ break;
55
+ case "error":
56
+ process.stderr.write(line);
57
+ break;
58
+ default:
59
+ process.stdout.write(line);
60
+ }
61
+ }
62
+
34
63
  export class EventService extends EventEmitter {
35
64
  private blockingHandlers: Map<string, EventHandler[]> = new Map();
36
65
  private managedListeners: Map<string, ManagedListenerRecord> = new Map();
66
+ private defaultLoggerActive = true;
67
+ private boundDefaultLogHandler = defaultConsoleLogHandler;
37
68
 
38
69
  eventTypes = {
39
70
  agentMsg: "agent:msg",
@@ -45,6 +76,35 @@ export class EventService extends EventEmitter {
45
76
  constructor() {
46
77
  super();
47
78
  this.setMaxListeners(100);
79
+ // Register the default console logger so Events.log() always produces output
80
+ // even before a renderer is attached (worker mode, module loading, etc.)
81
+ this.on(this.eventTypes.pluginLog, this.boundDefaultLogHandler);
82
+ }
83
+
84
+ /**
85
+ * Suppress the default console logger.
86
+ * Call this when a renderer has taken over and will handle plugin:log events.
87
+ * This prevents double-printing when both the renderer and the default handler fire.
88
+ */
89
+ suppressDefaultLogger(): void {
90
+ if (this.defaultLoggerActive) {
91
+ this.removeListener(
92
+ this.eventTypes.pluginLog,
93
+ this.boundDefaultLogHandler
94
+ );
95
+ this.defaultLoggerActive = false;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Restore the default console logger.
101
+ * Call this when the renderer is torn down.
102
+ */
103
+ restoreDefaultLogger(): void {
104
+ if (!this.defaultLoggerActive) {
105
+ this.on(this.eventTypes.pluginLog, this.boundDefaultLogHandler);
106
+ this.defaultLoggerActive = true;
107
+ }
48
108
  }
49
109
 
50
110
  /**
@@ -232,7 +292,7 @@ export class EventService extends EventEmitter {
232
292
  log(
233
293
  source: string,
234
294
  message: string,
235
- level: "info" | "warn" | "error" = "info"
295
+ level: LogLevel = "info"
236
296
  ): void {
237
297
  this.emit(this.eventTypes.pluginLog, {
238
298
  source,
@@ -1,3 +1,4 @@
1
+ import { createHash } from "crypto";
1
2
  import http from "../utils/http";
2
3
  import fs from "fs";
3
4
  import { Message } from "../clients/types";
@@ -663,8 +664,10 @@ export class KnowhowSimpleClient {
663
664
  /**
664
665
  * Get presigned S3 URL for uploading a file to Knowhow FS.
665
666
  * First finds or creates the file by path, then gets its upload URL.
667
+ * Computes SHA256 hash of the file content and stores it as S3 metadata
668
+ * so any client can determine if they already have this version without downloading.
666
669
  */
667
- async getOrgFilePresignedUploadUrl(filePath: string): Promise<string> {
670
+ async getOrgFilePresignedUploadUrl(filePath: string, localFilePath?: string): Promise<string> {
668
671
  await this.checkJwt();
669
672
 
670
673
  // Find or create the file by path
@@ -675,10 +678,17 @@ export class KnowhowSimpleClient {
675
678
  const fileName =
676
679
  lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
677
680
 
681
+ // Compute SHA256 hash if we have the local file path, so S3 stores it as metadata
682
+ let sha256Hash: string | undefined;
683
+ if (localFilePath) {
684
+ const fileContent = fs.readFileSync(localFilePath);
685
+ sha256Hash = createHash("sha256").update(fileContent).digest("base64");
686
+ }
687
+
678
688
  // Get upload URL using the file ID
679
689
  const response = await http.post<{ uploadUrl: string }>(
680
690
  `${this.baseUrl}/api/org-files/upload/${file.id}`,
681
- { fileName },
691
+ { fileName, sha256Hash },
682
692
  { headers: this.headers }
683
693
  );
684
694
  return response.data.uploadUrl;
@@ -1,6 +1,5 @@
1
1
  import * as fs from "fs";
2
2
  import { createWriteStream, createReadStream } from "fs";
3
- import * as crypto from "crypto";
4
3
  import { pipeline, Readable } from "stream";
5
4
  import * as util from "util";
6
5
 
@@ -15,19 +14,10 @@ export class S3Service {
15
14
  const fileContent = fs.readFileSync(filePath);
16
15
  const fileStats = await fs.promises.stat(filePath);
17
16
 
18
- // Compute SHA-256 checksum (base64) — required when presigned URL was
19
- // generated with ChecksumAlgorithm: SHA256
20
- const sha256Base64 = crypto
21
- .createHash("sha256")
22
- .update(fileContent)
23
- .digest("base64");
24
-
25
17
  const response = await fetch(presignedUrl, {
26
18
  method: "PUT",
27
19
  headers: {
28
20
  "Content-Length": String(fileStats.size),
29
- "x-amz-checksum-sha256": sha256Base64,
30
- "x-amz-sdk-checksum-algorithm": "SHA256",
31
21
  },
32
22
  body: fileContent,
33
23
  // @ts-ignore
@@ -1,38 +1,30 @@
1
+ import * as path from "path";
2
+
1
3
  import { getConfig, getGlobalConfig } from "../../config";
2
4
  import { KnowhowModule, ModuleContext } from "./types";
3
- import { ToolsService } from "../Tools";
4
5
  import { services } from "../";
5
- import * as path from "path";
6
+ import { toUniqueArray } from "../../utils";
6
7
 
7
8
  export class ModulesService {
8
- async loadModulesFromConfig(context?: ModuleContext) {
9
- const config = await getConfig();
9
+ async getDefaultContext() {
10
+ return { ...services() };
11
+ }
10
12
 
13
+ async overrideDefaultContext(overrides: Partial<ModuleContext>) {
14
+ const defaultContext = await this.getDefaultContext();
15
+ return { ...defaultContext, ...overrides };
16
+ }
17
+
18
+ async loadModulesFrom(
19
+ config: { modules: string[] } & any,
20
+ context?: Partial<ModuleContext>
21
+ ) {
11
22
  // If no context provided, fall back to global singletons
12
23
  if (!context) {
13
- const { Clients, Plugins, Agents, Tools, Embeddings, MediaProcessor } = services();
14
- context = {
15
- Agents,
16
- Embeddings,
17
- Plugins,
18
- Clients,
19
- Tools,
20
- MediaProcessor,
21
- };
24
+ context = { ...(await this.getDefaultContext()) };
22
25
  }
23
26
 
24
- // Use the toolsService from context
25
- const toolsService = context.Tools;
26
- const agentService = context.Agents;
27
- const pluginService = context.Plugins;
28
- const clients = context.Clients;
29
-
30
- // Load from global config (~/.knowhow/knowhow.json) first, then local config
31
- const globalConfig = await getGlobalConfig();
32
- const allModulePaths = [
33
- ...(globalConfig.modules || []),
34
- ...(config.modules || []),
35
- ];
27
+ const allModulePaths = config.modules;
36
28
 
37
29
  for (const modulePath of allModulePaths) {
38
30
  // Resolve relative paths relative to process.cwd() so that paths like
@@ -43,40 +35,68 @@ export class ModulesService {
43
35
  : modulePath;
44
36
  const rawModule = require(resolvedPath);
45
37
  const importedModule = (rawModule.default || rawModule) as KnowhowModule;
46
- console.log(`🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`);
47
- await importedModule.init({ config, cwd: process.cwd(), context });
48
- console.log(`✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`);
38
+ context.Events?.log(
39
+ "ModulesService",
40
+ `🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`
41
+ );
42
+ await importedModule.init({
43
+ config,
44
+ cwd: process.cwd(),
45
+ context: context as ModuleContext,
46
+ });
47
+ context.Events?.log(
48
+ "ModulesService",
49
+ `✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`
50
+ );
49
51
 
50
- for (const agent of importedModule.agents) {
51
- agentService.registerAgent(agent);
52
+ // Only register tools/agents/plugins/clients if the relevant services
53
+ // are available in context (they may not be during early CLI command registration)
54
+ if (context.Agents) {
55
+ for (const agent of importedModule.agents) {
56
+ context.Agents.registerAgent(agent);
57
+ }
52
58
  }
53
59
 
54
- for (const tool of importedModule.tools) {
55
- toolsService.addTool(tool.definition);
56
- toolsService.setFunction(tool.definition.function.name, tool.handler);
60
+ if (context.Tools) {
61
+ for (const tool of importedModule.tools) {
62
+ context.Tools.addTool(tool.definition);
63
+ context.Tools.setFunction(
64
+ tool.definition.function.name,
65
+ tool.handler
66
+ );
67
+ }
57
68
  }
58
69
 
59
- for (const plugin of importedModule.plugins) {
60
- const pluginContext = {
61
- Agents: agentService,
62
- Clients: clients,
63
- Tools: toolsService,
64
- Plugins: pluginService,
65
- ...(context.MediaProcessor ? { MediaProcessor: context.MediaProcessor } : {}),
66
- };
67
- pluginService.registerPlugin(plugin.name, new plugin.plugin(pluginContext as any));
70
+ if (context.Plugins) {
71
+ for (const plugin of importedModule.plugins) {
72
+ context.Plugins.registerPlugin(
73
+ plugin.name,
74
+ new plugin.plugin(context as any)
75
+ );
76
+ }
68
77
  }
69
78
 
70
- for (const client of importedModule.clients) {
71
- clients.registerClient(client.provider, client.client);
72
- clients.registerModels(client.provider, client.models);
79
+ if (context.Clients) {
80
+ for (const client of importedModule.clients) {
81
+ context.Clients.registerClient(client.provider, client.client);
82
+ context.Clients.registerModels(client.provider, client.models);
83
+ }
73
84
  }
74
85
  }
86
+ }
75
87
 
76
- // Also load plugins directly from config's pluginPackages map
77
- if (pluginService) {
78
- await pluginService.loadPluginsFromConfig(config);
79
- await pluginService.loadPluginsFromConfig(globalConfig);
80
- }
88
+ async loadModulesFromConfig(context?: ModuleContext) {
89
+ const config = await getConfig();
90
+
91
+ const globalConfig = await getGlobalConfig();
92
+ const allModulePaths = [
93
+ ...(globalConfig.modules || []),
94
+ ...(config.modules || []),
95
+ ];
96
+
97
+ return this.loadModulesFrom(
98
+ { ...config, modules: toUniqueArray(allModulePaths) },
99
+ context
100
+ );
81
101
  }
82
102
  }
@@ -1,4 +1,5 @@
1
1
  import { Plugin, PluginContext } from "../../plugins/types";
2
+ import { Command } from "commander";
2
3
  import { IAgent } from "../../agents/interface";
3
4
  import { Tool } from "../../clients/types";
4
5
  import { Config } from "../../types";
@@ -10,6 +11,7 @@ import { AIClient } from "../../clients";
10
11
  import { ToolsService } from "../Tools";
11
12
  import { MediaProcessorService } from "../MediaProcessorService";
12
13
  import { TunnelHandler } from "@tyvm/knowhow-tunnel";
14
+ import { EventService } from "../EventService";
13
15
 
14
16
  /*
15
17
  *
@@ -53,8 +55,10 @@ export interface ModuleContext {
53
55
  Plugins: PluginService;
54
56
  Clients: AIClient;
55
57
  Tools: ToolsService;
58
+ Events: EventService;
56
59
  MediaProcessor?: MediaProcessorService;
57
60
  Tunnel?: TunnelHandler;
61
+ Program?: Command;
58
62
  }
59
63
 
60
64
  export interface KnowhowModule {
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
+ ];
package/src/types.ts CHANGED
@@ -52,7 +52,6 @@ export type Config = {
52
52
  modules?: string[];
53
53
  };
54
54
  modules: string[];
55
- pluginPackages?: Record<string, string>;
56
55
  agents: Assistant[];
57
56
  mcps: McpConfig[];
58
57
  modelProviders: ModelProvider[];