@tyvm/knowhow 0.0.146 → 0.0.148
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/autodoc/cli-reference.md +36 -7
- package/bin/knowhow.js +8 -6
- package/package.json +1 -1
- package/src/agents/base/base.ts +51 -23
- package/src/agents/historyRollback.ts +36 -0
- package/src/agents/tools/execCommand.ts +102 -320
- package/src/agents/tools/list.ts +2 -2
- package/src/agents/tools/startAgentTask.ts +91 -34
- package/src/ai.ts +4 -1
- package/src/auth/browserLogin.ts +9 -3
- package/src/auth/keyAuth.ts +115 -0
- package/src/auth/keyManager.ts +166 -0
- package/src/chat/CliChatService.ts +23 -2
- package/src/chat/modules/AgentModule.ts +114 -33
- package/src/chat/modules/AskModule.ts +2 -0
- package/src/chat/modules/BaseChatModule.ts +0 -1
- package/src/chat/modules/CustomCommandsModule.ts +2 -0
- package/src/chat/modules/InternalChatModule.ts +7 -0
- package/src/chat/modules/RemoteSyncModule.ts +4 -0
- package/src/chat/modules/RendererModule.ts +2 -0
- package/src/chat/modules/SearchModule.ts +2 -0
- package/src/chat/modules/SessionsModule.ts +103 -34
- package/src/chat/modules/SetupModule.ts +2 -0
- package/src/chat/modules/ShellCommandModule.ts +2 -0
- package/src/chat/modules/SystemModule.ts +2 -0
- package/src/chat/modules/ToolCommandModule.ts +102 -0
- package/src/chat/modules/index.ts +1 -0
- package/src/chat/types.ts +3 -1
- package/src/cli.ts +10 -3
- package/src/clients/http.ts +67 -7
- package/src/clients/index.ts +4 -0
- package/src/commands/agent.ts +1 -38
- package/src/commands/agents.ts +455 -60
- package/src/commands/misc.ts +3 -2
- package/src/commands/processes.ts +138 -0
- package/src/commands/services.ts +23 -1
- package/src/index.ts +1 -0
- package/src/login.ts +89 -28
- package/src/processes/ProcessManager.ts +95 -0
- package/src/processes/managedProcessShim.ts +115 -0
- package/src/services/AgentService.ts +2 -0
- package/src/services/AgentSyncFs.ts +77 -1
- package/src/services/AgentSyncKnowhowWeb.ts +22 -4
- package/src/services/BehaviorsService.ts +2 -0
- package/src/services/DockerService.ts +2 -0
- package/src/services/EmbeddingService.ts +2 -0
- package/src/services/EmbeddingsService.ts +3 -0
- package/src/services/ExtensionsService.ts +25 -0
- package/src/services/KnowhowClient.ts +68 -7
- package/src/services/LazyToolsService.ts +24 -20
- package/src/services/Mcp.ts +2 -0
- package/src/services/McpServer.ts +2 -0
- package/src/services/MessageProcessor.ts +2 -0
- package/src/services/MinimalToolsService.ts +2 -0
- package/src/services/SessionManager.ts +5 -0
- package/src/services/SkillsService.ts +2 -0
- package/src/services/SyncedAgentWatcher.ts +2 -0
- package/src/services/SyncerService.ts +23 -8
- package/src/services/TaskRegistry.ts +2 -0
- package/src/services/Tools.ts +12 -0
- package/src/services/index.ts +6 -0
- package/src/services/modules/index.ts +10 -1
- package/src/services/modules/types.ts +4 -0
- package/src/types.ts +2 -0
- package/src/util/Trace.ts +1 -1
- package/src/utils/http.ts +18 -2
- package/tests/unit/agents/historyRollback.test.ts +53 -0
- package/tests/unit/auth/keyAuth.test.ts +102 -0
- package/tests/unit/auth/keyManager.test.ts +67 -0
- package/tests/unit/chat/ToolCommandModule.test.ts +71 -0
- package/tests/unit/chat/historyCommandArgs.test.ts +37 -0
- package/tests/unit/commands/agents.test.ts +111 -0
- package/tests/unit/login.test.ts +119 -0
- package/tests/unit/services/AgentSyncKnowhowWeb.test.ts +38 -0
- package/tests/unit/services/LazyToolsService.test.ts +53 -0
- package/tests/unit/services/SessionManager.test.ts +43 -0
- package/tests/unit/services/SyncerService.test.ts +45 -0
- package/tests/unit/util/Trace.test.ts +119 -0
- package/tests/unit/utils/http.test.ts +43 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +2 -1
- package/ts_build/src/agents/base/base.js +47 -14
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/historyRollback.d.ts +2 -0
- package/ts_build/src/agents/historyRollback.js +22 -0
- package/ts_build/src/agents/historyRollback.js.map +1 -0
- package/ts_build/src/agents/tools/execCommand.d.ts +4 -1
- package/ts_build/src/agents/tools/execCommand.js +100 -223
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -2
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.d.ts +16 -1
- package/ts_build/src/agents/tools/startAgentTask.js +71 -28
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/ai.js +2 -1
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/auth/browserLogin.d.ts +1 -0
- package/ts_build/src/auth/browserLogin.js +5 -2
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/auth/keyAuth.d.ts +5 -0
- package/ts_build/src/auth/keyAuth.js +79 -0
- package/ts_build/src/auth/keyAuth.js.map +1 -0
- package/ts_build/src/auth/keyManager.d.ts +14 -0
- package/ts_build/src/auth/keyManager.js +145 -0
- package/ts_build/src/auth/keyManager.js.map +1 -0
- package/ts_build/src/chat/CliChatService.d.ts +6 -0
- package/ts_build/src/chat/CliChatService.js +17 -2
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +13 -1
- package/ts_build/src/chat/modules/AgentModule.js +83 -24
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/AskModule.js +12 -2
- package/ts_build/src/chat/modules/AskModule.js.map +1 -1
- package/ts_build/src/chat/modules/BaseChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.js +12 -2
- package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +21 -2
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -2
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/chat/modules/RendererModule.js +17 -2
- package/ts_build/src/chat/modules/RendererModule.js.map +1 -1
- package/ts_build/src/chat/modules/SearchModule.js +12 -2
- package/ts_build/src/chat/modules/SearchModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.d.ts +6 -0
- package/ts_build/src/chat/modules/SessionsModule.js +113 -18
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/modules/SetupModule.js +17 -2
- package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
- package/ts_build/src/chat/modules/ShellCommandModule.js +12 -2
- package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -1
- package/ts_build/src/chat/modules/SystemModule.js +12 -2
- package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
- package/ts_build/src/chat/modules/ToolCommandModule.d.ts +10 -0
- package/ts_build/src/chat/modules/ToolCommandModule.js +86 -0
- package/ts_build/src/chat/modules/ToolCommandModule.js.map +1 -0
- package/ts_build/src/chat/modules/index.d.ts +1 -0
- package/ts_build/src/chat/modules/index.js +3 -1
- package/ts_build/src/chat/modules/index.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +3 -1
- package/ts_build/src/cli.js +7 -3
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/http.d.ts +4 -0
- package/ts_build/src/clients/http.js +68 -6
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.js +23 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/commands/agent.js +1 -21
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/agents.d.ts +2 -1
- package/ts_build/src/commands/agents.js +216 -23
- package/ts_build/src/commands/agents.js.map +1 -1
- package/ts_build/src/commands/misc.js +3 -2
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/processes.d.ts +2 -0
- package/ts_build/src/commands/processes.js +213 -0
- package/ts_build/src/commands/processes.js.map +1 -0
- package/ts_build/src/commands/services.js +11 -1
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +1 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/login.d.ts +1 -1
- package/ts_build/src/login.js +53 -15
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/processes/ProcessManager.d.ts +51 -0
- package/ts_build/src/processes/ProcessManager.js +160 -0
- package/ts_build/src/processes/ProcessManager.js.map +1 -0
- package/ts_build/src/processes/managedProcessShim.d.ts +1 -0
- package/ts_build/src/processes/managedProcessShim.js +161 -0
- package/ts_build/src/processes/managedProcessShim.js.map +1 -0
- package/ts_build/src/services/AgentService.js +18 -2
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +3 -0
- package/ts_build/src/services/AgentSyncFs.js +68 -16
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +2 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +28 -6
- package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
- package/ts_build/src/services/BehaviorsService.js +12 -2
- package/ts_build/src/services/BehaviorsService.js.map +1 -1
- package/ts_build/src/services/DockerService.js +20 -8
- package/ts_build/src/services/DockerService.js.map +1 -1
- package/ts_build/src/services/EmbeddingService.js +16 -2
- package/ts_build/src/services/EmbeddingService.js.map +1 -1
- package/ts_build/src/services/EmbeddingsService.js +12 -2
- package/ts_build/src/services/EmbeddingsService.js.map +1 -1
- package/ts_build/src/services/ExtensionsService.d.ts +10 -0
- package/ts_build/src/services/ExtensionsService.js +17 -0
- package/ts_build/src/services/ExtensionsService.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +7 -2
- package/ts_build/src/services/KnowhowClient.js +108 -10
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +2 -0
- package/ts_build/src/services/LazyToolsService.js +26 -9
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/Mcp.js +12 -2
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/McpServer.js +17 -2
- package/ts_build/src/services/McpServer.js.map +1 -1
- package/ts_build/src/services/MessageProcessor.js +16 -2
- package/ts_build/src/services/MessageProcessor.js.map +1 -1
- package/ts_build/src/services/MinimalToolsService.js +16 -2
- package/ts_build/src/services/MinimalToolsService.js.map +1 -1
- package/ts_build/src/services/SessionManager.js +19 -2
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/SkillsService.js +16 -2
- package/ts_build/src/services/SkillsService.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.js +16 -2
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
- package/ts_build/src/services/SyncerService.d.ts +1 -0
- package/ts_build/src/services/SyncerService.js +27 -5
- package/ts_build/src/services/SyncerService.js.map +1 -1
- package/ts_build/src/services/TaskRegistry.js +12 -2
- package/ts_build/src/services/TaskRegistry.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +1 -0
- package/ts_build/src/services/Tools.js +19 -2
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/index.d.ts +5 -0
- package/ts_build/src/services/index.js +7 -2
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +4 -1
- package/ts_build/src/services/modules/index.js +6 -1
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +3 -0
- package/ts_build/src/types.d.ts +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/util/Trace.d.ts +1 -3
- package/ts_build/src/utils/http.d.ts +6 -2
- package/ts_build/src/utils/http.js +8 -2
- package/ts_build/src/utils/http.js.map +1 -1
- package/ts_build/tests/unit/agents/historyRollback.test.d.ts +1 -0
- package/ts_build/tests/unit/agents/historyRollback.test.js +42 -0
- package/ts_build/tests/unit/agents/historyRollback.test.js.map +1 -0
- package/ts_build/tests/unit/auth/keyAuth.test.d.ts +1 -0
- package/ts_build/tests/unit/auth/keyAuth.test.js +77 -0
- package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -0
- package/ts_build/tests/unit/auth/keyManager.test.d.ts +1 -0
- package/ts_build/tests/unit/auth/keyManager.test.js +52 -0
- package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -0
- package/ts_build/tests/unit/chat/ToolCommandModule.test.d.ts +1 -0
- package/ts_build/tests/unit/chat/ToolCommandModule.test.js +61 -0
- package/ts_build/tests/unit/chat/ToolCommandModule.test.js.map +1 -0
- package/ts_build/tests/unit/chat/historyCommandArgs.test.d.ts +1 -0
- package/ts_build/tests/unit/chat/historyCommandArgs.test.js +29 -0
- package/ts_build/tests/unit/chat/historyCommandArgs.test.js.map +1 -0
- package/ts_build/tests/unit/commands/agents.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/agents.test.js +119 -0
- package/ts_build/tests/unit/commands/agents.test.js.map +1 -0
- package/ts_build/tests/unit/login.test.d.ts +1 -0
- package/ts_build/tests/unit/login.test.js +97 -0
- package/ts_build/tests/unit/login.test.js.map +1 -0
- package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.d.ts +1 -0
- package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js +32 -0
- package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js.map +1 -0
- package/ts_build/tests/unit/services/LazyToolsService.test.d.ts +1 -0
- package/ts_build/tests/unit/services/LazyToolsService.test.js +47 -0
- package/ts_build/tests/unit/services/LazyToolsService.test.js.map +1 -0
- package/ts_build/tests/unit/services/SessionManager.test.d.ts +1 -0
- package/ts_build/tests/unit/services/SessionManager.test.js +72 -0
- package/ts_build/tests/unit/services/SessionManager.test.js.map +1 -0
- package/ts_build/tests/unit/services/SyncerService.test.d.ts +1 -0
- package/ts_build/tests/unit/services/SyncerService.test.js +40 -0
- package/ts_build/tests/unit/services/SyncerService.test.js.map +1 -0
- package/ts_build/tests/unit/util/Trace.test.d.ts +1 -0
- package/ts_build/tests/unit/util/Trace.test.js +107 -0
- package/ts_build/tests/unit/util/Trace.test.js.map +1 -0
- package/ts_build/tests/unit/utils/http.test.d.ts +1 -0
- package/ts_build/tests/unit/utils/http.test.js +69 -0
- package/ts_build/tests/unit/utils/http.test.js.map +1 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { exec, spawn, ExecException } from "child_process";
|
|
2
|
-
import { promisify } from "util";
|
|
3
1
|
import * as fs from "fs";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
6
4
|
import { services, ToolsService } from "../../services";
|
|
5
|
+
import { getProcessesDir, ManagedProcess, processDirectory, spawnManaged } from "../../processes/ProcessManager";
|
|
7
6
|
|
|
8
7
|
export const execAsync = promisify(exec);
|
|
9
8
|
|
|
10
9
|
export interface ExecCommandOptions {
|
|
11
|
-
timeout?: number;
|
|
12
|
-
continueInBackground?: boolean;
|
|
13
|
-
maxBuffer?: number;
|
|
14
|
-
logFileName?: string;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
continueInBackground?: boolean;
|
|
12
|
+
maxBuffer?: number;
|
|
13
|
+
logFileName?: string;
|
|
14
|
+
parentProcessId?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
type ExecResult = {
|
|
@@ -20,361 +20,143 @@ type ExecResult = {
|
|
|
20
20
|
timedOut: boolean;
|
|
21
21
|
killed: boolean;
|
|
22
22
|
pid?: number;
|
|
23
|
-
|
|
23
|
+
processId: string;
|
|
24
|
+
processDirectory: string;
|
|
25
|
+
logPath: string;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const STARTED_PIDS = new Set<number>();
|
|
30
|
-
setupProcessCleanup();
|
|
31
|
-
|
|
32
|
-
// ---------- utils ----------
|
|
33
|
-
function stripTrailingAmp(cmd: string) {
|
|
34
|
-
const t = cmd.trim();
|
|
35
|
-
return t.endsWith("&") ? t.replace(/&\s*$/, "").trim() : t;
|
|
28
|
+
function stripTrailingAmp(command: string): string {
|
|
29
|
+
const trimmed = command.trim();
|
|
30
|
+
return trimmed.endsWith("&") ? trimmed.replace(/&\s*$/, "").trim() : trimmed;
|
|
36
31
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Use custom filename if provided, otherwise derive from command
|
|
46
|
-
let baseName = customFileName
|
|
47
|
-
? customFileName.replace(/[^\w.-]+/g, "_")
|
|
48
|
-
: commandNameFrom(cmd);
|
|
49
|
-
|
|
50
|
-
let logPath = path.join(PROCESSES_DIR, `${baseName}.txt`);
|
|
51
|
-
|
|
52
|
-
// If file already exists, append epoch seconds to ensure uniqueness
|
|
53
|
-
if (fs.existsSync(logPath)) {
|
|
54
|
-
const epochSeconds = Math.floor(Date.now() / 1000);
|
|
55
|
-
baseName = `${baseName}_${epochSeconds}`;
|
|
56
|
-
logPath = path.join(PROCESSES_DIR, `${baseName}.txt`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return logPath;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function setupProcessCleanup() {
|
|
63
|
-
const killAll = () => {
|
|
64
|
-
for (const pid of STARTED_PIDS) {
|
|
65
|
-
try {
|
|
66
|
-
if (os.platform() === "win32") {
|
|
67
|
-
spawn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
68
|
-
stdio: "ignore",
|
|
69
|
-
detached: true,
|
|
70
|
-
}).unref();
|
|
71
|
-
} else {
|
|
72
|
-
try {
|
|
73
|
-
process.kill(-pid, "SIGTERM");
|
|
74
|
-
} catch {
|
|
75
|
-
try {
|
|
76
|
-
process.kill(pid, "SIGTERM");
|
|
77
|
-
} catch {}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
} catch {}
|
|
81
|
-
}
|
|
32
|
+
function readOutput(managed: ManagedProcess, maxBuffer: number): { stdout: string; stderr: string } {
|
|
33
|
+
const read = (file: string) => {
|
|
34
|
+
try {
|
|
35
|
+
const value = fs.readFileSync(file, "utf8");
|
|
36
|
+
return value.length > maxBuffer
|
|
37
|
+
? `${value.slice(0, maxBuffer)}\n[output truncated after ${maxBuffer} characters]`
|
|
38
|
+
: value;
|
|
39
|
+
} catch { return ""; }
|
|
82
40
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
process.exit(143);
|
|
91
|
-
});
|
|
92
|
-
process.once("uncaughtException", (e) => {
|
|
93
|
-
console.error(e);
|
|
94
|
-
killAll();
|
|
95
|
-
process.exit(1);
|
|
96
|
-
});
|
|
97
|
-
process.once("unhandledRejection", (e: any) => {
|
|
98
|
-
console.error(e);
|
|
99
|
-
killAll();
|
|
100
|
-
process.exit(1);
|
|
101
|
-
});
|
|
41
|
+
return { stdout: read(managed.stdoutPath), stderr: read(managed.stderrPath) };
|
|
42
|
+
}
|
|
43
|
+
async function stopAfterTimeout(managed: ManagedProcess): Promise<void> {
|
|
44
|
+
try { managed.signal("SIGTERM"); } catch {}
|
|
45
|
+
try { await managed.wait(3000); return; } catch {}
|
|
46
|
+
try { managed.signal("SIGKILL"); } catch {}
|
|
47
|
+
try { await managed.wait(1000); } catch {}
|
|
102
48
|
}
|
|
103
49
|
|
|
104
|
-
// ---------- core ----------
|
|
105
50
|
const execWithTimeout = async (
|
|
106
51
|
command: string,
|
|
107
|
-
|
|
52
|
+
options: ExecCommandOptions = {}
|
|
108
53
|
): Promise<ExecResult> => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// Foreground, indefinite wait → stream with spawn
|
|
119
|
-
if (timeout === -1 && !shouldBg) {
|
|
120
|
-
return new Promise<ExecResult>((resolve) => {
|
|
121
|
-
let out = "";
|
|
122
|
-
let err = "";
|
|
123
|
-
const child = spawn(cleaned, { shell: true });
|
|
124
|
-
child.stdout?.on("data", (d) => (out += d.toString()));
|
|
125
|
-
child.stderr?.on("data", (d) => (err += d.toString()));
|
|
126
|
-
child.once("error", (e) =>
|
|
127
|
-
resolve({
|
|
128
|
-
stdout: out,
|
|
129
|
-
stderr: err || String(e),
|
|
130
|
-
timedOut: false,
|
|
131
|
-
killed: false,
|
|
132
|
-
pid: child.pid ?? undefined,
|
|
133
|
-
})
|
|
134
|
-
);
|
|
135
|
-
child.once("exit", () =>
|
|
136
|
-
resolve({
|
|
137
|
-
stdout: out,
|
|
138
|
-
stderr: err,
|
|
139
|
-
timedOut: false,
|
|
140
|
-
killed: false,
|
|
141
|
-
pid: child.pid ?? undefined,
|
|
142
|
-
})
|
|
143
|
-
);
|
|
144
|
-
});
|
|
54
|
+
const timeout = options.timeout ?? 5000;
|
|
55
|
+
const maxBuffer = options.maxBuffer ?? 16 * 1024 * 1024;
|
|
56
|
+
const background = options.continueInBackground || command.trim().endsWith("&");
|
|
57
|
+
let requestedId = options.logFileName?.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
58
|
+
if (requestedId) {
|
|
59
|
+
const base = requestedId;
|
|
60
|
+
let suffix = 0;
|
|
61
|
+
while (fs.existsSync(processDirectory(requestedId, getProcessesDir()))) requestedId = `${base}-${Date.now()}-${++suffix}`;
|
|
145
62
|
}
|
|
63
|
+
const managed = await spawnManaged(stripTrailingAmp(command), [], {
|
|
64
|
+
id: requestedId,
|
|
65
|
+
shell: true,
|
|
66
|
+
background,
|
|
67
|
+
parentPid: process.pid,
|
|
68
|
+
parentProcessId: options.parentProcessId,
|
|
69
|
+
});
|
|
70
|
+
const initial = managed.status;
|
|
71
|
+
const common = {
|
|
72
|
+
pid: initial.pid || undefined,
|
|
73
|
+
processId: managed.id,
|
|
74
|
+
processDirectory: managed.directory,
|
|
75
|
+
logPath: managed.stdoutPath,
|
|
76
|
+
};
|
|
146
77
|
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
const logPath = makeLogPath(cleaned, opts.logFileName);
|
|
150
|
-
|
|
151
|
-
// Open the log file now; we'll pass this FD to the child so it writes directly.
|
|
152
|
-
// Use 'w' to truncate old logs and guarantee our header goes first.
|
|
153
|
-
const fd = fs.openSync(logPath, "w");
|
|
154
|
-
|
|
155
|
-
// Spawn detached; bind stdout/stderr to the same file.
|
|
156
|
-
const child = spawn(cleaned, {
|
|
157
|
-
shell: true,
|
|
158
|
-
detached: true,
|
|
159
|
-
stdio: ["ignore", fd, fd], // child writes directly to file for its entire lifetime
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Immediately write header (first line includes PID), then close *our* fd.
|
|
163
|
-
// The child keeps its own duplicated handle open.
|
|
164
|
-
const pid = child.pid!;
|
|
165
|
-
const header =
|
|
166
|
-
`PID: ${pid}\n` +
|
|
167
|
-
`CMD: ${cleaned}\n` +
|
|
168
|
-
`START: ${new Date().toISOString()}\n` +
|
|
169
|
-
`---\n`;
|
|
170
|
-
fs.writeSync(fd, header);
|
|
171
|
-
fs.fsyncSync(fd); // flush header before we let go
|
|
172
|
-
try {
|
|
173
|
-
fs.closeSync(fd);
|
|
174
|
-
} catch {}
|
|
175
|
-
|
|
176
|
-
// We only wait 'timeout' ms to return; process keeps running/logging after that.
|
|
177
|
-
return await new Promise<ExecResult>((resolve) => {
|
|
178
|
-
let settled = false;
|
|
179
|
-
|
|
180
|
-
const done = (res: ExecResult) => {
|
|
181
|
-
if (settled) return;
|
|
182
|
-
settled = true;
|
|
183
|
-
resolve({ ...res, pid, logPath });
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
child.once("error", (e) =>
|
|
187
|
-
done({
|
|
188
|
-
stdout: "",
|
|
189
|
-
stderr: `Failed to start command: ${String(e)}`,
|
|
190
|
-
timedOut: false,
|
|
191
|
-
killed: false,
|
|
192
|
-
})
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
const tid = setTimeout(() => {
|
|
196
|
-
// fully detach from our side
|
|
197
|
-
try {
|
|
198
|
-
child.unref();
|
|
199
|
-
} catch {}
|
|
200
|
-
STARTED_PIDS.add(pid);
|
|
201
|
-
done({
|
|
202
|
-
stdout: "",
|
|
203
|
-
stderr:
|
|
204
|
-
`Command timed out after ${timeout}ms but continues in background\n` +
|
|
205
|
-
`Logs: ${logPath}\n` +
|
|
206
|
-
`Tip: read first line for PID; kill by PID for cleanup.\n`,
|
|
207
|
-
timedOut: true,
|
|
208
|
-
killed: false,
|
|
209
|
-
});
|
|
210
|
-
}, timeout);
|
|
211
|
-
|
|
212
|
-
// If it finishes early, report success and avoid “timed out” messaging
|
|
213
|
-
child.once("exit", () => {
|
|
214
|
-
clearTimeout(tid);
|
|
215
|
-
done({
|
|
216
|
-
stdout: "",
|
|
217
|
-
stderr: `Process finished before timeout. Logs: ${logPath}\n`,
|
|
218
|
-
timedOut: false,
|
|
219
|
-
killed: false,
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
});
|
|
78
|
+
if (background) {
|
|
79
|
+
return { stdout: "", stderr: "", timedOut: false, killed: false, ...common };
|
|
223
80
|
}
|
|
224
81
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
stdout: out,
|
|
238
|
-
stderr: err || error.message,
|
|
239
|
-
timedOut: (error as any).killed ?? false,
|
|
240
|
-
killed: !!(error as any).killed,
|
|
241
|
-
pid: (child as any).pid ?? undefined,
|
|
242
|
-
});
|
|
243
|
-
} else {
|
|
244
|
-
resolve({
|
|
245
|
-
stdout: out,
|
|
246
|
-
stderr: err,
|
|
247
|
-
timedOut: false,
|
|
248
|
-
killed: false,
|
|
249
|
-
pid: (child as any).pid ?? undefined,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
child.stdout?.on("data", (d) => (out += d.toString()));
|
|
255
|
-
child.stderr?.on("data", (d) => (err += d.toString()));
|
|
256
|
-
});
|
|
82
|
+
let timedOut = false;
|
|
83
|
+
if (timeout === -1) {
|
|
84
|
+
await managed.wait();
|
|
85
|
+
} else {
|
|
86
|
+
try { await managed.wait(timeout); }
|
|
87
|
+
catch {
|
|
88
|
+
timedOut = true;
|
|
89
|
+
await stopAfterTimeout(managed);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const output = readOutput(managed, maxBuffer);
|
|
93
|
+
return { ...output, timedOut, killed: timedOut, ...common };
|
|
257
94
|
};
|
|
258
95
|
|
|
259
|
-
|
|
96
|
+
/** Execute a shell command through the durable managed-process subsystem. */
|
|
260
97
|
export async function execCommand(
|
|
261
98
|
command: string,
|
|
262
99
|
timeout?: number | string,
|
|
263
100
|
continueInBackground?: boolean,
|
|
264
|
-
logFileName?: string
|
|
101
|
+
logFileName?: string,
|
|
102
|
+
_ctx?: { taskId?: string }
|
|
265
103
|
): Promise<string> {
|
|
266
104
|
if (!command || typeof command !== "string") {
|
|
267
105
|
throw new Error("Invalid command. We received a non-string value. Please ensure you are sending strings of 4k tokens or less.");
|
|
268
106
|
}
|
|
269
107
|
|
|
270
|
-
|
|
271
|
-
const toolService = (
|
|
272
|
-
this instanceof ToolsService ? this : services().Tools
|
|
273
|
-
) as ToolsService;
|
|
274
|
-
|
|
275
|
-
// Coerce timeout to a number. Agents (and some tool-call serializers) often
|
|
276
|
-
// pass numeric arguments as strings (e.g. "120000"). Node's exec()/setTimeout
|
|
277
|
-
// reject non-integer values with "The value of \"timeout\" is out of range.
|
|
278
|
-
// It must be an unsigned integer", so normalize here before any use.
|
|
108
|
+
const toolService = (this instanceof ToolsService ? this : services().Tools) as ToolsService;
|
|
279
109
|
let normalizedTimeout: number | undefined;
|
|
280
110
|
const rawTimeout = typeof timeout === "string" ? Number(timeout.trim()) : timeout;
|
|
281
|
-
if (typeof rawTimeout !== "number" || !Number.isFinite(rawTimeout))
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// -1 is the sentinel for "wait indefinitely" — leave as-is.
|
|
285
|
-
normalizedTimeout = -1;
|
|
286
|
-
} else {
|
|
287
|
-
// Clamp to a non-negative integer so the underlying APIs never throw.
|
|
288
|
-
normalizedTimeout = Math.max(0, Math.floor(rawTimeout));
|
|
289
|
-
}
|
|
111
|
+
if (typeof rawTimeout !== "number" || !Number.isFinite(rawTimeout)) normalizedTimeout = undefined;
|
|
112
|
+
else if (rawTimeout === -1) normalizedTimeout = -1;
|
|
113
|
+
else normalizedTimeout = Math.max(0, Math.floor(rawTimeout));
|
|
290
114
|
|
|
291
|
-
// Detect suspiciously small timeout values — agents often pass seconds instead of ms.
|
|
292
|
-
// Any value < 100 is almost certainly a mistake (100ms is still very fast for a shell cmd).
|
|
293
|
-
// We auto-correct to seconds and include a warning in the output so the agent learns.
|
|
294
115
|
let correctedTimeout = normalizedTimeout;
|
|
295
116
|
let timeoutWarning = "";
|
|
296
|
-
if (
|
|
297
|
-
normalizedTimeout !== undefined &&
|
|
298
|
-
normalizedTimeout !== -1 &&
|
|
299
|
-
normalizedTimeout < 100
|
|
300
|
-
) {
|
|
117
|
+
if (normalizedTimeout !== undefined && normalizedTimeout !== -1 && normalizedTimeout < 100) {
|
|
301
118
|
correctedTimeout = normalizedTimeout * 1000;
|
|
302
|
-
timeoutWarning =
|
|
303
|
-
`⚠️ Warning: timeout was ${normalizedTimeout}ms which is extremely small and likely a mistake. ` +
|
|
304
|
-
`The timeout unit is milliseconds, not seconds. ` +
|
|
305
|
-
`Auto-corrected to ${correctedTimeout}ms (${normalizedTimeout}s).\n\n`;
|
|
119
|
+
timeoutWarning = `⚠️ Warning: timeout was ${normalizedTimeout}ms which is extremely small and likely a mistake. The timeout unit is milliseconds, not seconds. Auto-corrected to ${correctedTimeout}ms (${normalizedTimeout}s).\n\n`;
|
|
306
120
|
}
|
|
307
121
|
|
|
308
122
|
const context = toolService.getContext();
|
|
309
|
-
|
|
310
|
-
// Emit pre-run blocking event — handlers can throw to block the command
|
|
311
123
|
if (context.Events) {
|
|
312
|
-
await context.Events.emitBlocking("exec:pre-run", {
|
|
313
|
-
command,
|
|
314
|
-
timeout,
|
|
315
|
-
continueInBackground,
|
|
316
|
-
logFileName,
|
|
317
|
-
});
|
|
124
|
+
await context.Events.emitBlocking("exec:pre-run", { command, timeout, continueInBackground, logFileName });
|
|
318
125
|
}
|
|
319
126
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
127
|
+
|
|
128
|
+
const parentProcessId = process.env.KNOWHOW_MANAGED_PROCESS_ID || _ctx?.taskId;
|
|
129
|
+
const result = await execWithTimeout(command, {
|
|
130
|
+
timeout: correctedTimeout,
|
|
131
|
+
continueInBackground,
|
|
132
|
+
logFileName,
|
|
133
|
+
parentProcessId,
|
|
134
|
+
});
|
|
135
|
+
const { stdout, stderr, timedOut, killed, pid, processId, processDirectory, logPath } = result;
|
|
326
136
|
|
|
327
137
|
let output = "";
|
|
328
|
-
if (stderr) output += stderr + "\n";
|
|
138
|
+
if (stderr) output += stderr + (stderr.endsWith("\n") ? "" : "\n");
|
|
329
139
|
if (stdout) output += stdout;
|
|
330
|
-
|
|
331
|
-
const statusMsg =
|
|
332
|
-
?
|
|
140
|
+
const background = continueInBackground || command.trim().endsWith("&");
|
|
141
|
+
const statusMsg = background
|
|
142
|
+
? ` (managed background process, id=${processId}, pid=${pid || "starting"})`
|
|
143
|
+
: timedOut
|
|
333
144
|
? " (killed due to timeout)"
|
|
334
|
-
:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
/*
|
|
340
|
-
*const lines = output.split("\n");
|
|
341
|
-
*const maxLines = 1000;
|
|
342
|
-
*const maxChars = 40000;
|
|
343
|
-
*const trimmed = (lines.length > maxLines ? lines.slice(0, maxLines) : lines)
|
|
344
|
-
* .join("\n")
|
|
345
|
-
* .slice(0, maxChars);
|
|
346
|
-
*const trimmedMsg =
|
|
347
|
-
* lines.length > maxLines
|
|
348
|
-
* ? ` (${lines.length - maxLines} results trimmed)`
|
|
349
|
-
* : "";
|
|
350
|
-
*/
|
|
351
|
-
|
|
352
|
-
const result = `$ ${command}${statusMsg}\n${output}`;
|
|
145
|
+
: "";
|
|
146
|
+
if (background) {
|
|
147
|
+
output += `Process directory: ${processDirectory}\nStdout: ${logPath}\nStderr: ${result.processDirectory}/stderr\n`;
|
|
148
|
+
}
|
|
149
|
+
const rendered = `$ ${command}${statusMsg}\n${output}`;
|
|
353
150
|
|
|
354
|
-
// Emit post-run blocking event — handlers can append extra context
|
|
355
151
|
let eventResults: any[] = [];
|
|
356
152
|
if (context.Events) {
|
|
357
153
|
eventResults = await context.Events.emitBlocking("exec:post-run", {
|
|
358
|
-
command,
|
|
359
|
-
|
|
360
|
-
continueInBackground,
|
|
361
|
-
logFileName,
|
|
362
|
-
stdout,
|
|
363
|
-
stderr,
|
|
364
|
-
timedOut,
|
|
365
|
-
killed,
|
|
366
|
-
pid,
|
|
367
|
-
logPath,
|
|
368
|
-
output,
|
|
154
|
+
command, timeout, continueInBackground, logFileName,
|
|
155
|
+
stdout, stderr, timedOut, killed, pid, logPath, processId, processDirectory, output,
|
|
369
156
|
});
|
|
370
157
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
"\n\nAdditional Information:\n" + JSON.stringify(eventResults, null, 2);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return timeoutWarning + result + eventResultsText;
|
|
380
|
-
};
|
|
158
|
+
const eventResultsText = eventResults?.length
|
|
159
|
+
? "\n\nAdditional Information:\n" + JSON.stringify(eventResults, null, 2)
|
|
160
|
+
: "";
|
|
161
|
+
return timeoutWarning + rendered + eventResultsText;
|
|
162
|
+
}
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const includedTools = [
|
|
|
88
88
|
function: {
|
|
89
89
|
name: "execCommand",
|
|
90
90
|
description:
|
|
91
|
-
"Execute a command
|
|
91
|
+
"Execute a command as a durable managed process. Use this to run tests and terminal tasks. Use timeout: -1 to wait indefinitely. Commands ending with '&' or with continueInBackground=true survive the calling agent and return a process ID plus a .knowhow/processes/<process-id>/ directory containing status, stdin, and output logs. Use `knowhow processes` to list, inspect, send input, attach, or stop them.",
|
|
92
92
|
parameters: {
|
|
93
93
|
type: "object",
|
|
94
94
|
positional: true,
|
|
@@ -110,7 +110,7 @@ export const includedTools = [
|
|
|
110
110
|
logFileName: {
|
|
111
111
|
type: "string",
|
|
112
112
|
description:
|
|
113
|
-
"Optional
|
|
113
|
+
"Optional preferred managed process ID (sanitized for use as a directory name). If it already exists, a timestamp suffix is added.",
|
|
114
114
|
},
|
|
115
115
|
},
|
|
116
116
|
required: ["command"],
|