@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.
- package/package.json +2 -3
- package/src/agents/base/base.ts +9 -0
- 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/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 -664
- package/src/clients/index.ts +6 -5
- package/src/clients/types.ts +12 -4
- 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 +160 -0
- package/src/config.ts +37 -0
- package/src/fileSync.ts +14 -2
- package/src/index.ts +1 -0
- package/src/logger.ts +197 -0
- package/src/plugins/plugins.ts +0 -21
- package/src/processors/JsonCompressor.ts +6 -6
- package/src/services/EventService.ts +61 -1
- package/src/services/KnowhowClient.ts +12 -2
- package/src/services/S3.ts +0 -10
- package/src/services/modules/index.ts +70 -50
- package/src/services/modules/types.ts +4 -0
- package/src/tunnel.ts +216 -0
- package/src/types.ts +0 -1
- 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/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 +2 -3
- package/ts_build/src/agents/base/base.js +10 -0
- package/ts_build/src/agents/base/base.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/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 -525
- package/ts_build/src/cli.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 +2 -2
- 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 +163 -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.js +10 -2
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +3 -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/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/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 +1 -1
- package/ts_build/src/services/KnowhowClient.js +8 -2
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/S3.js +0 -7
- package/ts_build/src/services/S3.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 +4 -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 +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/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
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
* Auth message types for the worker WebSocket authentication protocol.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
// Client → Worker: request a challenge
|
|
6
|
+
export interface AuthGetChallengeMessage {
|
|
7
|
+
type: "auth:getChallenge";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Worker → Client: sent when a non-auth message is received while locked
|
|
11
|
+
export interface AuthLockedMessage {
|
|
12
|
+
type: "auth:locked";
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
// Worker → Client: challenge
|
|
6
17
|
export interface AuthChallengeMessage {
|
|
7
18
|
type: "auth:challenge";
|
|
@@ -31,12 +42,13 @@ export interface AuthFailureMessage {
|
|
|
31
42
|
type: "auth:failure";
|
|
32
43
|
reason: "invalid_signature" | "expired" | "unknown_credential";
|
|
33
44
|
}
|
|
34
|
-
|
|
35
45
|
export type AuthMessage =
|
|
46
|
+
| AuthGetChallengeMessage
|
|
36
47
|
| AuthChallengeMessage
|
|
37
48
|
| AuthResponseMessage
|
|
38
49
|
| AuthSuccessMessage
|
|
39
|
-
| AuthFailureMessage
|
|
50
|
+
| AuthFailureMessage
|
|
51
|
+
| AuthLockedMessage;
|
|
40
52
|
|
|
41
53
|
// Passkey credential stored in config
|
|
42
54
|
export interface PasskeyCredential {
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the github-credentials command.
|
|
3
|
+
*
|
|
4
|
+
* Key invariant: running `github-credentials` must NEVER write anything other than
|
|
5
|
+
* the credential lines to stdout. Module loading logs, warnings, etc. must be
|
|
6
|
+
* silenced so the git credential helper protocol is not corrupted.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Mock config before any imports that depend on it
|
|
10
|
+
jest.mock("../../../src/config", () => ({
|
|
11
|
+
getConfig: jest.fn().mockResolvedValue({ modules: [] }),
|
|
12
|
+
getGlobalConfig: jest.fn().mockResolvedValue({ modules: [] }),
|
|
13
|
+
getConfigSync: jest.fn().mockReturnValue({}),
|
|
14
|
+
migrateConfig: jest.fn().mockResolvedValue(undefined),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock clients to avoid openai.ts side-effects
|
|
18
|
+
jest.mock("../../../src/clients", () => ({
|
|
19
|
+
AIClient: jest.fn(),
|
|
20
|
+
Clients: { registerClient: jest.fn(), registerModels: jest.fn() },
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Mock KnowhowSimpleClient so we control what getGitCredential returns
|
|
24
|
+
// without needing a real JWT or network connection
|
|
25
|
+
jest.mock("../../../src/services/KnowhowClient", () => ({
|
|
26
|
+
KnowhowSimpleClient: jest.fn().mockImplementation(() => ({
|
|
27
|
+
getGitCredential: jest.fn().mockResolvedValue({
|
|
28
|
+
protocol: "https",
|
|
29
|
+
host: "github.com",
|
|
30
|
+
username: "x-access-token",
|
|
31
|
+
password: "ghu_TESTTOKEN123",
|
|
32
|
+
}),
|
|
33
|
+
})),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock readline so the 'get' action doesn't hang waiting for stdin
|
|
37
|
+
jest.mock("readline", () => ({
|
|
38
|
+
createInterface: jest.fn().mockReturnValue({
|
|
39
|
+
on: jest.fn().mockImplementation(function (event: string, cb: Function) {
|
|
40
|
+
// Immediately fire 'close' so the readline promise resolves
|
|
41
|
+
if (event === "close") {
|
|
42
|
+
setImmediate(() => cb());
|
|
43
|
+
}
|
|
44
|
+
return this;
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
import { Command } from "commander";
|
|
50
|
+
import { addGithubCredentialsCommand } from "../../../src/commands/misc";
|
|
51
|
+
import { logger } from "../../../src/logger";
|
|
52
|
+
|
|
53
|
+
describe("github-credentials command", () => {
|
|
54
|
+
/**
|
|
55
|
+
* This test verifies the EARLY silencing logic in cli.ts main().
|
|
56
|
+
* The problem: modules load BEFORE parseAsync, so any module that emits
|
|
57
|
+
* warnings (e.g. Terminal module: no TunnelHandler) does so before the
|
|
58
|
+
* action's logger.silence() call can stop it.
|
|
59
|
+
*
|
|
60
|
+
* The fix: cli.ts checks process.argv before module loading and silences early.
|
|
61
|
+
* This test simulates that logic directly.
|
|
62
|
+
*/
|
|
63
|
+
describe("early silencing (pre-module-load)", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
logger.unsilence();
|
|
66
|
+
logger.installConsoleOverload();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
logger.unsilence();
|
|
71
|
+
logger.uninstallConsoleOverload();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("silences before module loading when github-credentials is in argv", () => {
|
|
75
|
+
const originalArgv = process.argv;
|
|
76
|
+
process.argv = ["node", "knowhow", "github-credentials", "get"];
|
|
77
|
+
|
|
78
|
+
// Simulate the exact early-detection logic from cli.ts main()
|
|
79
|
+
const rawArgs = process.argv.slice(2);
|
|
80
|
+
const SILENT_COMMANDS = ["github-credentials"];
|
|
81
|
+
if (rawArgs.some((a) => SILENT_COMMANDS.includes(a))) {
|
|
82
|
+
logger.silence();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Now any module-load-time console.log/warn should be suppressed
|
|
86
|
+
const consoleSpy = jest.spyOn(process.stdout, "write");
|
|
87
|
+
console.warn("⚠️ Terminal module: no TunnelHandler in context — terminal addon not registered");
|
|
88
|
+
console.log("some other module loading noise");
|
|
89
|
+
|
|
90
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
91
|
+
consoleSpy.mockRestore();
|
|
92
|
+
process.argv = originalArgv;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("does NOT silence for other commands", () => {
|
|
96
|
+
const originalArgv = process.argv;
|
|
97
|
+
process.argv = ["node", "knowhow", "chat"];
|
|
98
|
+
|
|
99
|
+
const rawArgs = process.argv.slice(2);
|
|
100
|
+
const SILENT_COMMANDS = ["github-credentials"];
|
|
101
|
+
if (rawArgs.some((a) => SILENT_COMMANDS.includes(a))) {
|
|
102
|
+
logger.silence();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
expect(logger.isSilenced()).toBe(false);
|
|
106
|
+
process.argv = originalArgv;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let program: Command;
|
|
111
|
+
let stdoutSpy: jest.SpyInstance;
|
|
112
|
+
let writtenToStdout: string[];
|
|
113
|
+
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
jest.clearAllMocks();
|
|
116
|
+
|
|
117
|
+
// Reset logger silence state between tests
|
|
118
|
+
logger.unsilence();
|
|
119
|
+
|
|
120
|
+
// Capture process.stdout.write — this is what the credential helper uses
|
|
121
|
+
writtenToStdout = [];
|
|
122
|
+
stdoutSpy = jest
|
|
123
|
+
.spyOn(process.stdout, "write")
|
|
124
|
+
.mockImplementation((chunk: any) => {
|
|
125
|
+
writtenToStdout.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
program = new Command();
|
|
130
|
+
program.exitOverride(); // prevent process.exit during tests
|
|
131
|
+
addGithubCredentialsCommand(program);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
afterEach(() => {
|
|
135
|
+
stdoutSpy.mockRestore();
|
|
136
|
+
logger.unsilence();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("outputs only credential lines to stdout for 'get' action", async () => {
|
|
140
|
+
await program.parseAsync([
|
|
141
|
+
"node", "knowhow", "github-credentials", "get", "--repo", "myorg/myrepo",
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
expect(writtenToStdout).toHaveLength(1);
|
|
145
|
+
expect(writtenToStdout[0]).toBe(
|
|
146
|
+
"protocol=https\nhost=github.com\nusername=x-access-token\npassword=ghu_TESTTOKEN123\n"
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("silences the logger immediately so module logs don't pollute stdout", async () => {
|
|
151
|
+
await program.parseAsync([
|
|
152
|
+
"node", "knowhow", "github-credentials", "get", "--repo", "myorg/myrepo",
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// The action must have called logger.silence() — state persists after action
|
|
156
|
+
expect(logger.isSilenced()).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("produces exactly 4 credential field lines and nothing else", async () => {
|
|
160
|
+
await program.parseAsync([
|
|
161
|
+
"node", "knowhow", "github-credentials", "get", "--repo", "myorg/myrepo",
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
const allOutput = writtenToStdout.join("");
|
|
165
|
+
const lines = allOutput.trim().split("\n");
|
|
166
|
+
|
|
167
|
+
expect(lines).toHaveLength(4);
|
|
168
|
+
expect(lines[0]).toMatch(/^protocol=/);
|
|
169
|
+
expect(lines[1]).toMatch(/^host=/);
|
|
170
|
+
expect(lines[2]).toMatch(/^username=/);
|
|
171
|
+
expect(lines[3]).toMatch(/^password=/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("exits cleanly for 'store' action without writing credentials", async () => {
|
|
175
|
+
let exitCode: number | undefined;
|
|
176
|
+
// Throw to stop execution after exit() is called — otherwise the mock
|
|
177
|
+
// just sets a flag and the action continues to fetch credentials.
|
|
178
|
+
const exitSpy = jest
|
|
179
|
+
.spyOn(process, "exit")
|
|
180
|
+
.mockImplementation(((code?: number) => {
|
|
181
|
+
exitCode = code ?? 0;
|
|
182
|
+
throw new Error(`process.exit(${exitCode})`);
|
|
183
|
+
}) as any);
|
|
184
|
+
|
|
185
|
+
await expect(
|
|
186
|
+
program.parseAsync(["node", "knowhow", "github-credentials", "store"])
|
|
187
|
+
).rejects.toThrow("process.exit(0)");
|
|
188
|
+
|
|
189
|
+
expect(exitCode).toBe(0);
|
|
190
|
+
expect(writtenToStdout).toHaveLength(0);
|
|
191
|
+
exitSpy.mockRestore();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("exits cleanly for 'erase' action without writing credentials", async () => {
|
|
195
|
+
let exitCode: number | undefined;
|
|
196
|
+
const exitSpy = jest
|
|
197
|
+
.spyOn(process, "exit")
|
|
198
|
+
.mockImplementation(((code?: number) => {
|
|
199
|
+
exitCode = code ?? 0;
|
|
200
|
+
throw new Error(`process.exit(${exitCode})`);
|
|
201
|
+
}) as any);
|
|
202
|
+
|
|
203
|
+
await expect(
|
|
204
|
+
program.parseAsync(["node", "knowhow", "github-credentials", "erase"])
|
|
205
|
+
).rejects.toThrow("process.exit(0)");
|
|
206
|
+
|
|
207
|
+
expect(exitCode).toBe(0);
|
|
208
|
+
expect(writtenToStdout).toHaveLength(0);
|
|
209
|
+
exitSpy.mockRestore();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -24,20 +24,27 @@ jest.mock("../../../src/services", () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
import { ModulesService } from "../../../src/services/modules";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
ModuleContext,
|
|
29
|
+
KnowhowModule,
|
|
30
|
+
} from "../../../src/services/modules/types";
|
|
28
31
|
import { getConfig, getGlobalConfig } from "../../../src/config";
|
|
29
32
|
|
|
30
33
|
const mockGetConfig = getConfig as jest.MockedFunction<typeof getConfig>;
|
|
31
|
-
const mockGetGlobalConfig = getGlobalConfig as jest.MockedFunction<
|
|
34
|
+
const mockGetGlobalConfig = getGlobalConfig as jest.MockedFunction<
|
|
35
|
+
typeof getGlobalConfig
|
|
36
|
+
>;
|
|
32
37
|
|
|
33
38
|
function makeContext(overrides?: Partial<ModuleContext>): ModuleContext {
|
|
34
39
|
return {
|
|
40
|
+
Events: {
|
|
41
|
+
log: jest.fn(),
|
|
42
|
+
} as any,
|
|
35
43
|
Agents: {
|
|
36
44
|
registerAgent: jest.fn(),
|
|
37
45
|
} as any,
|
|
38
46
|
Plugins: {
|
|
39
47
|
registerPlugin: jest.fn(),
|
|
40
|
-
loadPluginsFromConfig: jest.fn().mockResolvedValue(undefined),
|
|
41
48
|
} as any,
|
|
42
49
|
Clients: {
|
|
43
50
|
registerClient: jest.fn(),
|
|
@@ -87,7 +94,8 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
87
94
|
const context = makeContext();
|
|
88
95
|
|
|
89
96
|
// Mock require used inside loadModulesFromConfig
|
|
90
|
-
const requireSpy = jest
|
|
97
|
+
const requireSpy = jest
|
|
98
|
+
.spyOn(service as any, "loadModulesFromConfig")
|
|
91
99
|
.mockImplementation(async (ctx?: ModuleContext) => {
|
|
92
100
|
const resolvedCtx = ctx || context;
|
|
93
101
|
await mockModule.init({ config: {} as Config, cwd: process.cwd() });
|
|
@@ -113,26 +121,35 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
113
121
|
};
|
|
114
122
|
const mockToolHandler = jest.fn();
|
|
115
123
|
const mockModule = makeModule({
|
|
116
|
-
tools: [
|
|
124
|
+
tools: [
|
|
125
|
+
{ name: "myTool", handler: mockToolHandler, definition: mockToolDef },
|
|
126
|
+
],
|
|
117
127
|
});
|
|
118
128
|
|
|
119
129
|
const service = new ModulesService();
|
|
120
130
|
const context = makeContext();
|
|
121
131
|
|
|
122
|
-
const spy = jest
|
|
132
|
+
const spy = jest
|
|
133
|
+
.spyOn(service as any, "loadModulesFromConfig")
|
|
123
134
|
.mockImplementation(async (ctx?: ModuleContext) => {
|
|
124
135
|
const resolvedCtx = ctx || context;
|
|
125
136
|
await mockModule.init({ config: {} as Config, cwd: process.cwd() });
|
|
126
137
|
for (const tool of mockModule.tools) {
|
|
127
138
|
resolvedCtx.Tools.addTool(tool.definition);
|
|
128
|
-
resolvedCtx.Tools.setFunction(
|
|
139
|
+
resolvedCtx.Tools.setFunction(
|
|
140
|
+
tool.definition.function.name,
|
|
141
|
+
tool.handler
|
|
142
|
+
);
|
|
129
143
|
}
|
|
130
144
|
});
|
|
131
145
|
|
|
132
146
|
await service.loadModulesFromConfig(context);
|
|
133
147
|
|
|
134
148
|
expect(context.Tools.addTool).toHaveBeenCalledWith(mockToolDef);
|
|
135
|
-
expect(context.Tools.setFunction).toHaveBeenCalledWith(
|
|
149
|
+
expect(context.Tools.setFunction).toHaveBeenCalledWith(
|
|
150
|
+
"myTool",
|
|
151
|
+
mockToolHandler
|
|
152
|
+
);
|
|
136
153
|
spy.mockRestore();
|
|
137
154
|
});
|
|
138
155
|
|
|
@@ -147,7 +164,9 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
147
164
|
embed: () => Promise.resolve([]),
|
|
148
165
|
};
|
|
149
166
|
// ModulePlugin expects a constructor (class), not an instance
|
|
150
|
-
const MockPluginClass = jest
|
|
167
|
+
const MockPluginClass = jest
|
|
168
|
+
.fn()
|
|
169
|
+
.mockImplementation(() => mockPluginInstance);
|
|
151
170
|
const mockModule = makeModule({
|
|
152
171
|
plugins: [{ name: "test-plugin", plugin: MockPluginClass as any }],
|
|
153
172
|
});
|
|
@@ -155,7 +174,8 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
155
174
|
const service = new ModulesService();
|
|
156
175
|
const context = makeContext();
|
|
157
176
|
|
|
158
|
-
const spy = jest
|
|
177
|
+
const spy = jest
|
|
178
|
+
.spyOn(service as any, "loadModulesFromConfig")
|
|
159
179
|
.mockImplementation(async (ctx?: ModuleContext) => {
|
|
160
180
|
const resolvedCtx = ctx || context;
|
|
161
181
|
await mockModule.init({ config: {} as Config, cwd: process.cwd() });
|
|
@@ -167,36 +187,17 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
167
187
|
|
|
168
188
|
await service.loadModulesFromConfig(context);
|
|
169
189
|
|
|
170
|
-
expect(context.Plugins.registerPlugin).toHaveBeenCalledWith(
|
|
190
|
+
expect(context.Plugins.registerPlugin).toHaveBeenCalledWith(
|
|
191
|
+
"test-plugin",
|
|
192
|
+
mockPluginInstance
|
|
193
|
+
);
|
|
171
194
|
spy.mockRestore();
|
|
172
195
|
});
|
|
173
196
|
|
|
174
|
-
it("should call loadPluginsFromConfig with both global and local configs", async () => {
|
|
175
|
-
const localConfig = {
|
|
176
|
-
modules: [],
|
|
177
|
-
pluginPackages: { asana: "@knowhow/plugin-asana" },
|
|
178
|
-
} as unknown as Config;
|
|
179
|
-
const globalConfig = {
|
|
180
|
-
modules: [],
|
|
181
|
-
pluginPackages: { linear: "@knowhow/plugin-linear" },
|
|
182
|
-
} as unknown as Config;
|
|
183
|
-
|
|
184
|
-
mockGetConfig.mockResolvedValue(localConfig);
|
|
185
|
-
mockGetGlobalConfig.mockResolvedValue(globalConfig);
|
|
186
|
-
|
|
187
|
-
const service = new ModulesService();
|
|
188
|
-
const context = makeContext();
|
|
189
|
-
|
|
190
|
-
await service.loadModulesFromConfig(context);
|
|
191
|
-
|
|
192
|
-
// pluginService.loadPluginsFromConfig should be called twice: once for local, once for global
|
|
193
|
-
expect(context.Plugins.loadPluginsFromConfig).toHaveBeenCalledTimes(2);
|
|
194
|
-
expect(context.Plugins.loadPluginsFromConfig).toHaveBeenCalledWith(localConfig);
|
|
195
|
-
expect(context.Plugins.loadPluginsFromConfig).toHaveBeenCalledWith(globalConfig);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
197
|
it("should load modules from both global and local config paths", async () => {
|
|
199
|
-
const globalModule = makeModule({
|
|
198
|
+
const globalModule = makeModule({
|
|
199
|
+
agents: [{ name: "GlobalAgent" } as any],
|
|
200
|
+
});
|
|
200
201
|
const localModule = makeModule({ agents: [{ name: "LocalAgent" } as any] });
|
|
201
202
|
|
|
202
203
|
mockGetConfig.mockResolvedValue({
|
|
@@ -210,7 +211,8 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
210
211
|
const context = makeContext();
|
|
211
212
|
|
|
212
213
|
const loadedPaths: string[] = [];
|
|
213
|
-
const spy = jest
|
|
214
|
+
const spy = jest
|
|
215
|
+
.spyOn(service as any, "loadModulesFromConfig")
|
|
214
216
|
.mockImplementation(async (ctx?: ModuleContext) => {
|
|
215
217
|
const resolvedCtx = ctx || context;
|
|
216
218
|
for (const [path, mod] of [
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { PluginService } from "../../../src/plugins/plugins";
|
|
2
2
|
import { PluginContext } from "../../../src/plugins/types";
|
|
3
|
-
import { Config } from "../../../src/types";
|
|
4
3
|
|
|
5
4
|
// Minimal mock context for PluginService construction
|
|
6
5
|
// EmbeddingPlugin calls context.Events.on() in its constructor, so we must mock it.
|
|
@@ -65,87 +64,3 @@ describe("PluginService.loadPlugin", () => {
|
|
|
65
64
|
expect(plugin!.meta.key).toBe(mockKey);
|
|
66
65
|
});
|
|
67
66
|
});
|
|
68
|
-
|
|
69
|
-
describe("PluginService.loadPluginsFromConfig", () => {
|
|
70
|
-
it("should load plugins listed in config.pluginPackages", async () => {
|
|
71
|
-
const service = new PluginService(makeContext());
|
|
72
|
-
const loadedSpecs: string[] = [];
|
|
73
|
-
|
|
74
|
-
service.loadPlugin = jest.fn().mockImplementation(async (spec: string) => {
|
|
75
|
-
loadedSpecs.push(spec);
|
|
76
|
-
const key = `plugin-from-${spec}`;
|
|
77
|
-
const instance = {
|
|
78
|
-
meta: { key, name: key },
|
|
79
|
-
isEnabled: () => true,
|
|
80
|
-
enable: () => {},
|
|
81
|
-
disable: () => {},
|
|
82
|
-
call: () => Promise.resolve(""),
|
|
83
|
-
callMany: () => Promise.resolve(""),
|
|
84
|
-
embed: () => Promise.resolve([]),
|
|
85
|
-
};
|
|
86
|
-
(service as any).pluginMap.set(key, instance);
|
|
87
|
-
return key;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const config = {
|
|
91
|
-
pluginPackages: {
|
|
92
|
-
asana: "@knowhow/plugin-asana",
|
|
93
|
-
linear: "@knowhow/plugin-linear",
|
|
94
|
-
},
|
|
95
|
-
} as unknown as Config;
|
|
96
|
-
|
|
97
|
-
await service.loadPluginsFromConfig(config);
|
|
98
|
-
|
|
99
|
-
expect(loadedSpecs).toContain("@knowhow/plugin-asana");
|
|
100
|
-
expect(loadedSpecs).toContain("@knowhow/plugin-linear");
|
|
101
|
-
expect(loadedSpecs.length).toBe(2);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("should handle empty pluginPackages gracefully", async () => {
|
|
105
|
-
const service = new PluginService(makeContext());
|
|
106
|
-
service.loadPlugin = jest.fn();
|
|
107
|
-
|
|
108
|
-
const config = {} as Config;
|
|
109
|
-
await expect(service.loadPluginsFromConfig(config)).resolves.toBeUndefined();
|
|
110
|
-
expect(service.loadPlugin).not.toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("should log a warning and not crash when a plugin fails to load", async () => {
|
|
114
|
-
const context = makeContext();
|
|
115
|
-
const service = new PluginService(context);
|
|
116
|
-
|
|
117
|
-
service.loadPlugin = jest.fn().mockRejectedValue(new Error("Module not found"));
|
|
118
|
-
|
|
119
|
-
const config = {
|
|
120
|
-
pluginPackages: {
|
|
121
|
-
broken: "non-existent-package",
|
|
122
|
-
},
|
|
123
|
-
} as unknown as Config;
|
|
124
|
-
|
|
125
|
-
await expect(service.loadPluginsFromConfig(config)).resolves.toBeUndefined();
|
|
126
|
-
expect(context.Events.log).toHaveBeenCalledWith(
|
|
127
|
-
"PluginService",
|
|
128
|
-
expect.stringContaining("broken"),
|
|
129
|
-
"warn"
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should load each plugin with the correct spec string", async () => {
|
|
134
|
-
const service = new PluginService(makeContext());
|
|
135
|
-
const loadedSpecs: string[] = [];
|
|
136
|
-
|
|
137
|
-
service.loadPlugin = jest.fn().mockImplementation(async (spec: string) => {
|
|
138
|
-
loadedSpecs.push(spec);
|
|
139
|
-
return spec;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const config = {
|
|
143
|
-
pluginPackages: {
|
|
144
|
-
valid: "@knowhow/valid-plugin",
|
|
145
|
-
},
|
|
146
|
-
} as unknown as Config;
|
|
147
|
-
|
|
148
|
-
await service.loadPluginsFromConfig(config);
|
|
149
|
-
expect(loadedSpecs).toEqual(["@knowhow/valid-plugin"]);
|
|
150
|
-
});
|
|
151
|
-
});
|
package/ts_build/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.80f1ac6",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -55,13 +55,12 @@
|
|
|
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",
|
|
62
62
|
"gitignore-to-glob": "^0.3.0",
|
|
63
63
|
"glob": "11.1.0",
|
|
64
|
-
"isolated-vm": "^5.0.4",
|
|
65
64
|
"jiti": "^2.6.1",
|
|
66
65
|
"marked": "^10.0.0",
|
|
67
66
|
"marked-terminal": "^6.2.0",
|
|
@@ -213,6 +213,16 @@ class BaseAgent {
|
|
|
213
213
|
if (trimmed.startsWith("✅") || /^[\d\.\-\*]/.test(trimmed))
|
|
214
214
|
return true;
|
|
215
215
|
}
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(trimmed);
|
|
218
|
+
if (parsed && typeof parsed === "object") {
|
|
219
|
+
if (typeof parsed.answer === "string")
|
|
220
|
+
return true;
|
|
221
|
+
if (typeof parsed.finalAnswer === "string")
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (_) { }
|
|
216
226
|
return false;
|
|
217
227
|
}
|
|
218
228
|
getEnabledTools() {
|