@tyvm/knowhow 0.0.108 → 0.0.109
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/README.md +45 -0
- package/package.json +9 -4
- package/scripts/publish.sh +86 -0
- package/src/agents/base/base.ts +10 -0
- package/src/agents/tools/execCommand.ts +49 -6
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +0 -2
- package/src/chat/CliChatService.ts +10 -1
- package/src/chat/modules/AgentModule.ts +55 -30
- package/src/chat/modules/SessionsModule.ts +7 -2
- package/src/chat/renderer/CompactRenderer.ts +20 -0
- package/src/chat/renderer/ConsoleRenderer.ts +19 -0
- package/src/chat/renderer/FancyRenderer.ts +19 -0
- package/src/chat/renderer/types.ts +11 -0
- package/src/cli.ts +91 -659
- package/src/clients/anthropic.ts +17 -16
- package/src/clients/index.ts +6 -5
- package/src/clients/types.ts +19 -4
- package/src/cloudWorker.ts +175 -113
- package/src/commands/agent.ts +246 -0
- package/src/commands/misc.ts +174 -0
- package/src/commands/modules.ts +182 -0
- package/src/commands/services.ts +77 -0
- package/src/commands/workers.ts +168 -0
- package/src/config.ts +37 -0
- package/src/fileSync.ts +50 -17
- package/src/index.ts +18 -0
- package/src/logger.ts +197 -0
- package/src/plugins/embedding.ts +11 -6
- package/src/plugins/plugins.ts +0 -21
- package/src/plugins/vim.ts +5 -16
- package/src/processors/JsonCompressor.ts +6 -6
- package/src/services/EventService.ts +61 -1
- package/src/services/KnowhowClient.ts +34 -4
- package/src/services/modules/index.ts +70 -50
- package/src/services/modules/types.ts +6 -0
- package/src/tunnel.ts +216 -0
- package/src/types.ts +0 -1
- package/src/worker.ts +105 -312
- 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/src/workers/tools/index.ts +2 -0
- package/src/workers/tools/reloadConfig.ts +84 -0
- package/tests/services/WorkerReloadConfig.test.ts +141 -0
- package/tests/unit/commands/github-credentials.test.ts +211 -0
- package/tests/unit/modules/moduleLoading.test.ts +39 -37
- package/tests/unit/plugins/pluginLoading.test.ts +0 -85
- package/ts_build/package.json +9 -4
- package/ts_build/src/agents/base/base.js +11 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +39 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -2
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +13 -1
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +39 -19
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +7 -2
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/types.d.ts +2 -0
- package/ts_build/src/cli.js +47 -519
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +5 -5
- package/ts_build/src/clients/anthropic.js +17 -16
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/index.js +2 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +3 -2
- package/ts_build/src/cloudWorker.d.ts +14 -0
- package/ts_build/src/cloudWorker.js +105 -66
- package/ts_build/src/cloudWorker.js.map +1 -1
- package/ts_build/src/commands/agent.d.ts +6 -0
- package/ts_build/src/commands/agent.js +229 -0
- package/ts_build/src/commands/agent.js.map +1 -0
- package/ts_build/src/commands/misc.d.ts +10 -0
- package/ts_build/src/commands/misc.js +197 -0
- package/ts_build/src/commands/misc.js.map +1 -0
- package/ts_build/src/commands/modules.d.ts +3 -0
- package/ts_build/src/commands/modules.js +160 -0
- package/ts_build/src/commands/modules.js.map +1 -0
- package/ts_build/src/commands/services.d.ts +5 -0
- package/ts_build/src/commands/services.js +87 -0
- package/ts_build/src/commands/services.js.map +1 -0
- package/ts_build/src/commands/workers.d.ts +6 -0
- package/ts_build/src/commands/workers.js +168 -0
- package/ts_build/src/commands/workers.js.map +1 -0
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +32 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/fileSync.d.ts +6 -0
- package/ts_build/src/fileSync.js +37 -12
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +17 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/logger.d.ts +21 -0
- package/ts_build/src/logger.js +106 -0
- package/ts_build/src/logger.js.map +1 -0
- package/ts_build/src/plugins/embedding.js +4 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +0 -2
- package/ts_build/src/plugins/plugins.js +0 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/vim.js +3 -9
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/processors/JsonCompressor.js +4 -4
- package/ts_build/src/processors/JsonCompressor.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +6 -1
- package/ts_build/src/services/EventService.js +29 -0
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +13 -1
- package/ts_build/src/services/KnowhowClient.js +19 -2
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +33 -0
- package/ts_build/src/services/modules/index.js +46 -45
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +6 -0
- 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/types.d.ts +0 -1
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +1 -4
- package/ts_build/src/worker.js +59 -227
- 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/ts_build/src/workers/tools/index.d.ts +2 -0
- package/ts_build/src/workers/tools/index.js +4 -1
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
- package/ts_build/src/workers/tools/reloadConfig.js +48 -0
- package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js +146 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/src/agents/tools/executeScript/README.md +0 -94
- package/src/agents/tools/executeScript/definition.ts +0 -79
- package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
- package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
- package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
- package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
- package/src/agents/tools/executeScript/index.ts +0 -98
- package/src/services/script-execution/SandboxContext.ts +0 -282
- package/src/services/script-execution/ScriptExecutor.ts +0 -441
- package/src/services/script-execution/ScriptPolicy.ts +0 -194
- package/src/services/script-execution/ScriptTracer.ts +0 -249
- package/src/services/script-execution/types.ts +0 -134
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
- package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
- package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
- package/ts_build/src/agents/tools/executeScript/index.js +0 -72
- package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
- package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
- package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
- package/ts_build/src/services/script-execution/types.d.ts +0 -108
- package/ts_build/src/services/script-execution/types.js +0 -3
- package/ts_build/src/services/script-execution/types.js.map +0 -1
|
@@ -382,7 +382,7 @@ export class JsonCompressor {
|
|
|
382
382
|
i + currentChunk.length - 1
|
|
383
383
|
}]\nPreview: ${chunkString.substring(0, 100)}...\n[Use ${
|
|
384
384
|
this.toolName
|
|
385
|
-
} tool with key "${key}" to retrieve this chunk]`;
|
|
385
|
+
} tool with key "${key}" to retrieve this chunk]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
|
|
386
386
|
finalArray.unshift(stub); // Add stub to the start of our final result.
|
|
387
387
|
|
|
388
388
|
currentChunk = [];
|
|
@@ -415,11 +415,11 @@ export class JsonCompressor {
|
|
|
415
415
|
|
|
416
416
|
// Store objects on FIRST occurrence so second occurrence can reference it
|
|
417
417
|
// We increment seenCount above, so after increment:
|
|
418
|
-
// seenCount=
|
|
419
|
-
// seenCount>=
|
|
418
|
+
// seenCount=0: first occurrence (before increment), store it
|
|
419
|
+
// seenCount>=1: we already stored it on first occurrence, should be in dedup map
|
|
420
420
|
// Note: This means we store proactively - first occurrence gets stored AND returned in full
|
|
421
421
|
// Second+ occurrences will find it in the dedup map and return a reference
|
|
422
|
-
const isFirstOccurrence = seenCount ===
|
|
422
|
+
const isFirstOccurrence = seenCount === 0;
|
|
423
423
|
|
|
424
424
|
// Process the object - apply low-signal detection
|
|
425
425
|
const objWithLowSignalCompressed = this.compressObjectWithLowSignalDetection(obj, path);
|
|
@@ -453,7 +453,7 @@ export class JsonCompressor {
|
|
|
453
453
|
result
|
|
454
454
|
).join(", ")}\nPreview: ${objectAsString.substring(0, 200)}...\n[Use ${
|
|
455
455
|
this.toolName
|
|
456
|
-
} tool with key "${key}" to retrieve full content]`;
|
|
456
|
+
} tool with key "${key}" to retrieve full content]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
|
|
457
457
|
}
|
|
458
458
|
return result;
|
|
459
459
|
}
|
|
@@ -486,7 +486,7 @@ export class JsonCompressor {
|
|
|
486
486
|
200
|
|
487
487
|
)}...\n[Use ${
|
|
488
488
|
this.toolName
|
|
489
|
-
} tool with key "${key}" to retrieve full content]`;
|
|
489
|
+
} tool with key "${key}" to retrieve full content]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
|
|
490
490
|
}
|
|
491
491
|
return obj;
|
|
492
492
|
}
|
|
@@ -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:
|
|
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";
|
|
@@ -171,11 +172,9 @@ export class KnowhowSimpleClient {
|
|
|
171
172
|
try {
|
|
172
173
|
this.jwtValidated = true;
|
|
173
174
|
const response = await this.me();
|
|
174
|
-
|
|
175
175
|
const user = response.data.user;
|
|
176
176
|
const orgs = user.orgs;
|
|
177
177
|
const orgId = response.data.orgId;
|
|
178
|
-
|
|
179
178
|
const currentOrg = orgs.find((org) => {
|
|
180
179
|
return org.organizationId === orgId;
|
|
181
180
|
});
|
|
@@ -227,6 +226,17 @@ export class KnowhowSimpleClient {
|
|
|
227
226
|
return presignedUrl;
|
|
228
227
|
}
|
|
229
228
|
|
|
229
|
+
async getOrgEmbedding(id: string) {
|
|
230
|
+
await this.checkJwt();
|
|
231
|
+
const resp = await http.get(
|
|
232
|
+
`${this.baseUrl}/api/org-embeddings/${id}`,
|
|
233
|
+
{
|
|
234
|
+
headers: this.headers,
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
return resp.data as { id: string; modelName: string; name: string; [key: string]: unknown };
|
|
238
|
+
}
|
|
239
|
+
|
|
230
240
|
async updateEmbeddingMetadata(
|
|
231
241
|
id: string,
|
|
232
242
|
data: {
|
|
@@ -654,8 +664,10 @@ export class KnowhowSimpleClient {
|
|
|
654
664
|
/**
|
|
655
665
|
* Get presigned S3 URL for uploading a file to Knowhow FS.
|
|
656
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.
|
|
657
669
|
*/
|
|
658
|
-
async getOrgFilePresignedUploadUrl(filePath: string): Promise<string> {
|
|
670
|
+
async getOrgFilePresignedUploadUrl(filePath: string, localFilePath?: string): Promise<string> {
|
|
659
671
|
await this.checkJwt();
|
|
660
672
|
|
|
661
673
|
// Find or create the file by path
|
|
@@ -666,10 +678,17 @@ export class KnowhowSimpleClient {
|
|
|
666
678
|
const fileName =
|
|
667
679
|
lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
|
|
668
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
|
+
|
|
669
688
|
// Get upload URL using the file ID
|
|
670
689
|
const response = await http.post<{ uploadUrl: string }>(
|
|
671
690
|
`${this.baseUrl}/api/org-files/upload/${file.id}`,
|
|
672
|
-
{ fileName },
|
|
691
|
+
{ fileName, sha256Hash },
|
|
673
692
|
{ headers: this.headers }
|
|
674
693
|
);
|
|
675
694
|
return response.data.uploadUrl;
|
|
@@ -718,6 +737,17 @@ export class KnowhowSimpleClient {
|
|
|
718
737
|
);
|
|
719
738
|
}
|
|
720
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Get a single cloud worker by ID
|
|
742
|
+
*/
|
|
743
|
+
async getCloudWorker(id: string) {
|
|
744
|
+
await this.checkJwt();
|
|
745
|
+
return http.get<{ id: string; name: string; status: string; workerConfigJson?: Record<string, unknown> }>(
|
|
746
|
+
`${this.baseUrl}/api/cloud-workers/${id}`,
|
|
747
|
+
{ headers: this.headers }
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
721
751
|
/**
|
|
722
752
|
* Update an existing cloud worker
|
|
723
753
|
*/
|
|
@@ -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
|
|
6
|
+
import { toUniqueArray } from "../../utils";
|
|
6
7
|
|
|
7
8
|
export class ModulesService {
|
|
8
|
-
async
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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";
|
|
@@ -9,6 +10,8 @@ import { PluginService } from "../../plugins/plugins";
|
|
|
9
10
|
import { AIClient } from "../../clients";
|
|
10
11
|
import { ToolsService } from "../Tools";
|
|
11
12
|
import { MediaProcessorService } from "../MediaProcessorService";
|
|
13
|
+
import { TunnelHandler } from "@tyvm/knowhow-tunnel";
|
|
14
|
+
import { EventService } from "../EventService";
|
|
12
15
|
|
|
13
16
|
/*
|
|
14
17
|
*
|
|
@@ -52,7 +55,10 @@ export interface ModuleContext {
|
|
|
52
55
|
Plugins: PluginService;
|
|
53
56
|
Clients: AIClient;
|
|
54
57
|
Tools: ToolsService;
|
|
58
|
+
Events: EventService;
|
|
55
59
|
MediaProcessor?: MediaProcessorService;
|
|
60
|
+
Tunnel?: TunnelHandler;
|
|
61
|
+
Program?: Command;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
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
|
+
];
|