@tyvm/knowhow 0.0.68 → 0.0.70
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/docs/shell-commands.md +174 -0
- package/package.json +2 -2
- package/src/agents/base/base.ts +1 -3
- package/src/agents/developer/developer.ts +21 -16
- package/src/agents/tools/agentCall.ts +4 -2
- package/src/agents/tools/fileSearch.ts +5 -1
- package/src/agents/tools/list.ts +41 -37
- package/src/agents/tools/startAgentTask.ts +131 -22
- package/src/chat/CliChatService.ts +57 -11
- package/src/chat/modules/AgentModule.ts +72 -12
- package/src/chat/modules/CustomCommandsModule.ts +79 -0
- package/src/chat/modules/InternalChatModule.ts +11 -1
- package/src/chat/modules/ShellCommandModule.ts +96 -0
- package/src/chat/modules/index.ts +1 -0
- package/src/chat/types.ts +14 -2
- package/src/chat.ts +16 -13
- package/src/cli.ts +16 -6
- package/src/clients/anthropic.ts +88 -91
- package/src/clients/gemini.ts +495 -94
- package/src/clients/index.ts +125 -0
- package/src/clients/knowhow.ts +81 -0
- package/src/clients/openai.ts +256 -145
- package/src/clients/pricing/anthropic.ts +90 -0
- package/src/clients/pricing/google.ts +65 -0
- package/src/clients/pricing/index.ts +4 -0
- package/src/clients/pricing/openai.ts +134 -0
- package/src/clients/pricing/xai.ts +62 -0
- package/src/clients/types.ts +170 -1
- package/src/clients/xai.ts +275 -46
- package/src/config.ts +61 -15
- package/src/embeddings.ts +9 -1
- package/src/microphone.ts +15 -16
- package/src/migrations.ts +151 -0
- package/src/plugins/AgentsMdPlugin.ts +118 -0
- package/src/plugins/PluginBase.ts +8 -0
- package/src/plugins/downloader/downloader.ts +5 -6
- package/src/plugins/embedding.ts +10 -8
- package/src/plugins/exec.ts +70 -0
- package/src/plugins/github.ts +120 -74
- package/src/plugins/language.ts +11 -13
- package/src/plugins/plugins.ts +25 -4
- package/src/plugins/tmux.ts +132 -0
- package/src/plugins/types.ts +1 -0
- package/src/plugins/vim.ts +14 -1
- package/src/server/index.ts +2 -0
- package/src/services/AgentSyncFs.ts +417 -0
- package/src/services/{AgentSynchronization.ts → AgentSyncKnowhowWeb.ts} +2 -2
- package/src/services/EventService.ts +0 -1
- package/src/services/KnowhowClient.ts +106 -0
- package/src/services/index.ts +4 -2
- package/src/types.ts +57 -4
- package/src/worker.ts +25 -2
- package/tests/manual/modalities/README.md +157 -0
- package/tests/manual/modalities/google.modalities.test.ts +335 -0
- package/tests/manual/modalities/openai.modalities.test.ts +329 -0
- package/tests/manual/modalities/streaming.test.ts +260 -0
- package/tests/manual/modalities/xai.modalities.test.ts +307 -0
- package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -5
- package/tests/plugins/language/languagePlugin-integration.test.ts +1 -1
- package/tests/plugins/language/languagePlugin.test.ts +17 -8
- package/ts_build/package.json +2 -2
- package/ts_build/src/agents/base/base.js +1 -1
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/developer/developer.js +21 -15
- package/ts_build/src/agents/developer/developer.js.map +1 -1
- package/ts_build/src/agents/tools/agentCall.js +4 -2
- package/ts_build/src/agents/tools/agentCall.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +1 -1
- package/ts_build/src/agents/tools/fileSearch.js +2 -1
- package/ts_build/src/agents/tools/fileSearch.js.map +1 -1
- package/ts_build/src/agents/tools/github/index.d.ts +1 -1
- package/ts_build/src/agents/tools/list.js +41 -37
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -1
- package/ts_build/src/agents/tools/startAgentTask.js +118 -17
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/chat/CliChatService.d.ts +4 -0
- package/ts_build/src/chat/CliChatService.js +39 -5
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +4 -1
- package/ts_build/src/chat/modules/AgentModule.js +49 -11
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +9 -0
- package/ts_build/src/chat/modules/CustomCommandsModule.js +58 -0
- package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -0
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +2 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +10 -0
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/ShellCommandModule.d.ts +8 -0
- package/ts_build/src/chat/modules/ShellCommandModule.js +83 -0
- package/ts_build/src/chat/modules/ShellCommandModule.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 +11 -1
- package/ts_build/src/chat.js +16 -13
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +10 -3
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +6 -1
- package/ts_build/src/clients/anthropic.js +47 -92
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/gemini.d.ts +81 -2
- package/ts_build/src/clients/gemini.js +362 -79
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +9 -1
- package/ts_build/src/clients/index.js +65 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/knowhow.d.ts +9 -1
- package/ts_build/src/clients/knowhow.js +43 -0
- package/ts_build/src/clients/knowhow.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +9 -1
- package/ts_build/src/clients/openai.js +201 -133
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +17 -0
- package/ts_build/src/clients/pricing/anthropic.js +93 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -0
- package/ts_build/src/clients/pricing/google.d.ts +73 -0
- package/ts_build/src/clients/pricing/google.js +68 -0
- package/ts_build/src/clients/pricing/google.js.map +1 -0
- package/ts_build/src/clients/pricing/index.d.ts +4 -0
- package/ts_build/src/clients/pricing/index.js +14 -0
- package/ts_build/src/clients/pricing/index.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +7 -0
- package/ts_build/src/clients/pricing/openai.js +137 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -0
- package/ts_build/src/clients/pricing/xai.d.ts +26 -0
- package/ts_build/src/clients/pricing/xai.js +59 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +135 -0
- package/ts_build/src/clients/xai.d.ts +9 -1
- package/ts_build/src/clients/xai.js +178 -46
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +45 -16
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/embeddings.js +8 -1
- package/ts_build/src/embeddings.js.map +1 -1
- package/ts_build/src/microphone.js +7 -9
- package/ts_build/src/microphone.js.map +1 -1
- package/ts_build/src/migrations.d.ts +17 -0
- package/ts_build/src/migrations.js +86 -0
- package/ts_build/src/migrations.js.map +1 -0
- package/ts_build/src/plugins/AgentsMdPlugin.d.ts +13 -0
- package/ts_build/src/plugins/AgentsMdPlugin.js +118 -0
- package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -0
- package/ts_build/src/plugins/PluginBase.d.ts +1 -0
- package/ts_build/src/plugins/PluginBase.js +3 -0
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/downloader/downloader.js +5 -5
- package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
- package/ts_build/src/plugins/embedding.js +9 -8
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/exec.d.ts +10 -0
- package/ts_build/src/plugins/exec.js +56 -0
- package/ts_build/src/plugins/exec.js.map +1 -0
- package/ts_build/src/plugins/github.js +93 -51
- package/ts_build/src/plugins/github.js.map +1 -1
- package/ts_build/src/plugins/language.js +14 -11
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +1 -0
- package/ts_build/src/plugins/plugins.js +19 -1
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/tmux.d.ts +14 -0
- package/ts_build/src/plugins/tmux.js +108 -0
- package/ts_build/src/plugins/tmux.js.map +1 -0
- package/ts_build/src/plugins/types.d.ts +1 -0
- package/ts_build/src/plugins/vim.js +11 -1
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/server/index.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +34 -0
- package/ts_build/src/services/AgentSyncFs.js +325 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +29 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +178 -0
- package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -0
- package/ts_build/src/services/AgentSynchronization.d.ts +1 -1
- package/ts_build/src/services/AgentSynchronization.js +3 -3
- package/ts_build/src/services/AgentSynchronization.js.map +1 -1
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +9 -1
- package/ts_build/src/services/KnowhowClient.js +58 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/index.d.ts +2 -1
- package/ts_build/src/services/index.js +2 -1
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/types.d.ts +26 -1
- package/ts_build/src/types.js +45 -4
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/utils/PersistentInputManager.d.ts +28 -0
- package/ts_build/src/utils/PersistentInputManager.js +293 -0
- package/ts_build/src/utils/PersistentInputManager.js.map +1 -0
- package/ts_build/src/worker.js +11 -2
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/manual/modalities/google.modalities.test.d.ts +1 -0
- package/ts_build/tests/manual/modalities/google.modalities.test.js +252 -0
- package/ts_build/tests/manual/modalities/google.modalities.test.js.map +1 -0
- package/ts_build/tests/manual/modalities/openai.modalities.test.d.ts +1 -0
- package/ts_build/tests/manual/modalities/openai.modalities.test.js +252 -0
- package/ts_build/tests/manual/modalities/openai.modalities.test.js.map +1 -0
- package/ts_build/tests/manual/modalities/streaming.test.d.ts +1 -0
- package/ts_build/tests/manual/modalities/streaming.test.js +206 -0
- package/ts_build/tests/manual/modalities/streaming.test.js.map +1 -0
- package/ts_build/tests/manual/modalities/xai.modalities.test.d.ts +1 -0
- package/ts_build/tests/manual/modalities/xai.modalities.test.js +226 -0
- package/ts_build/tests/manual/modalities/xai.modalities.test.js.map +1 -0
- package/ts_build/tests/manual/persistent-input-test.d.ts +1 -0
- package/ts_build/tests/manual/persistent-input-test.js +35 -0
- package/ts_build/tests/manual/persistent-input-test.js.map +1 -0
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -5
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +1 -1
- package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js +17 -7
- package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { PluginBase, PluginMeta } from "./PluginBase";
|
|
2
|
+
import { PluginContext } from "./types";
|
|
3
|
+
import { execAsync } from "../utils";
|
|
4
|
+
|
|
5
|
+
export class TmuxPlugin extends PluginBase {
|
|
6
|
+
static readonly meta: PluginMeta = {
|
|
7
|
+
key: "tmux",
|
|
8
|
+
name: "Tmux Plugin",
|
|
9
|
+
requires: [],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
meta = TmuxPlugin.meta;
|
|
13
|
+
|
|
14
|
+
constructor(context: PluginContext) {
|
|
15
|
+
super(context);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async embed(userPrompt: string) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if we're currently in a tmux session
|
|
24
|
+
*/
|
|
25
|
+
async isInTmux(): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await execAsync("echo $TMUX");
|
|
28
|
+
return stdout.trim().length > 0;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get current session information
|
|
36
|
+
*/
|
|
37
|
+
async getCurrentSession(): Promise<string> {
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await execAsync(
|
|
40
|
+
"tmux display-message -p '#{session_name}:#{window_index}:#{window_name}'"
|
|
41
|
+
);
|
|
42
|
+
return stdout.trim();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all tmux sessions
|
|
50
|
+
*/
|
|
51
|
+
async getSessions(): Promise<string[]> {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execAsync("tmux list-sessions");
|
|
54
|
+
return stdout
|
|
55
|
+
.trim()
|
|
56
|
+
.split("\n")
|
|
57
|
+
.filter((line) => line.length > 0);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all windows in current session
|
|
65
|
+
*/
|
|
66
|
+
async getWindows(): Promise<string[]> {
|
|
67
|
+
try {
|
|
68
|
+
const { stdout } = await execAsync("tmux list-windows");
|
|
69
|
+
return stdout
|
|
70
|
+
.trim()
|
|
71
|
+
.split("\n")
|
|
72
|
+
.filter((line) => line.length > 0);
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all panes across all sessions
|
|
80
|
+
*/
|
|
81
|
+
async getPanes(): Promise<string[]> {
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await execAsync(
|
|
84
|
+
"tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_title} #{pane_current_command} #{pane_current_path}'"
|
|
85
|
+
);
|
|
86
|
+
return stdout
|
|
87
|
+
.trim()
|
|
88
|
+
.split("\n")
|
|
89
|
+
.filter((line) => line.length > 0);
|
|
90
|
+
} catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async call(): Promise<string> {
|
|
96
|
+
const inTmux = await this.isInTmux();
|
|
97
|
+
|
|
98
|
+
if (!inTmux) {
|
|
99
|
+
return "TMUX PLUGIN: Not currently in a tmux session";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const currentSession = await this.getCurrentSession();
|
|
103
|
+
const sessions = await this.getSessions();
|
|
104
|
+
const windows = await this.getWindows();
|
|
105
|
+
const panes = await this.getPanes();
|
|
106
|
+
|
|
107
|
+
const output = `TMUX PLUGIN: You are currently in a tmux session. This means you can use tmux commands to help debug and navigate.
|
|
108
|
+
|
|
109
|
+
**Current Session/Window**: ${currentSession}
|
|
110
|
+
|
|
111
|
+
**Available Sessions**:
|
|
112
|
+
${sessions.map((s) => ` - ${s}`).join("\n")}
|
|
113
|
+
|
|
114
|
+
**Windows in Current Session**:
|
|
115
|
+
${windows.map((w) => ` - ${w}`).join("\n")}
|
|
116
|
+
|
|
117
|
+
**All Panes** (showing running commands and paths):
|
|
118
|
+
${panes.map((p) => ` - ${p}`).join("\n")}
|
|
119
|
+
|
|
120
|
+
**Useful tmux commands for debugging**:
|
|
121
|
+
- \`tmux send-keys -t <session>:<window>.<pane> "command" Enter\` - Send a command to a specific pane
|
|
122
|
+
- \`tmux capture-pane -t <session>:<window>.<pane> -p\` - Capture output from a pane
|
|
123
|
+
- \`tmux list-panes -a\` - List all panes
|
|
124
|
+
- \`tmux switch-client -t <session>\` - Switch to another session
|
|
125
|
+
- \`tmux select-window -t <window>\` - Switch to another window
|
|
126
|
+
- \`tmux select-pane -t <pane>\` - Switch to another pane
|
|
127
|
+
|
|
128
|
+
You can use execCommand to run these tmux commands to inspect running processes, send commands to other panes, or capture output for debugging.`;
|
|
129
|
+
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/plugins/types.ts
CHANGED
package/src/plugins/vim.ts
CHANGED
|
@@ -73,7 +73,20 @@ export class VimPlugin extends PluginBase {
|
|
|
73
73
|
async call() {
|
|
74
74
|
const vimFiles = await this.getVimFiles();
|
|
75
75
|
const fileContents = await Promise.all(
|
|
76
|
-
vimFiles.map((f) =>
|
|
76
|
+
vimFiles.map(async (f) => {
|
|
77
|
+
const loaded = await this.getFileContents(f);
|
|
78
|
+
|
|
79
|
+
const preview =
|
|
80
|
+
loaded.content.length > 1000
|
|
81
|
+
? loaded.content.slice(0, 1000) +
|
|
82
|
+
"... file trimmed, read file for full content"
|
|
83
|
+
: loaded.content;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
sourceFile: loaded.filePath,
|
|
87
|
+
content: loaded.content.slice(0, 1000),
|
|
88
|
+
};
|
|
89
|
+
})
|
|
77
90
|
);
|
|
78
91
|
if (fileContents.length === 0) {
|
|
79
92
|
return "VIM PLUGIN: No files open in vim";
|
package/src/server/index.ts
CHANGED
|
@@ -3,12 +3,14 @@ import express from "express";
|
|
|
3
3
|
const app = express();
|
|
4
4
|
const port = 4545;
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
app.use((req, res) => {
|
|
7
8
|
const { path, body, query } = req;
|
|
8
9
|
console.log({ path, body, query });
|
|
9
10
|
res.send("Hello World!");
|
|
10
11
|
});
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
app.listen(port, () => {
|
|
13
15
|
console.log(`Server started at http://localhost:${port}`);
|
|
14
16
|
});
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Synchronization Service - Filesystem-based synchronization
|
|
3
|
+
* Handles synchronization via filesystem files in .knowhow/processes/agents/taskId/
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAgent } from "../agents/base/base";
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { watch } from "fs";
|
|
9
|
+
|
|
10
|
+
export interface FsSyncOptions {
|
|
11
|
+
taskId: string;
|
|
12
|
+
prompt: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AgentSyncFs handles filesystem-based synchronization for agent tasks
|
|
17
|
+
* Creates files in .knowhow/processes/agents/{taskId}/ for status and input
|
|
18
|
+
*/
|
|
19
|
+
export class AgentSyncFs {
|
|
20
|
+
private taskId: string | undefined;
|
|
21
|
+
private basePath: string = ".knowhow/processes/agents";
|
|
22
|
+
private taskPath: string | undefined;
|
|
23
|
+
private eventHandlersSetup: boolean = false;
|
|
24
|
+
private watcher: ReturnType<typeof watch> | null = null;
|
|
25
|
+
private lastInputContent: string = "";
|
|
26
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
27
|
+
private finalizationPromise: Promise<void> | null = null;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
// Start cleanup process when created
|
|
31
|
+
this.startCleanupProcess();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create filesystem sync for a task
|
|
36
|
+
*/
|
|
37
|
+
async createTask(options: FsSyncOptions): Promise<string> {
|
|
38
|
+
this.taskId = options.taskId;
|
|
39
|
+
this.taskPath = path.join(this.basePath, this.taskId);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Create directory structure
|
|
43
|
+
await fs.mkdir(this.taskPath, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// Create initial files
|
|
46
|
+
await this.writeStatus("running");
|
|
47
|
+
await this.writeInput("");
|
|
48
|
+
await this.writeMetadata({
|
|
49
|
+
taskId: this.taskId,
|
|
50
|
+
prompt: options.prompt,
|
|
51
|
+
startTime: new Date().toISOString(),
|
|
52
|
+
status: "running",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(`✅ Created filesystem sync at: ${this.taskPath}`);
|
|
56
|
+
return this.taskId;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`❌ Failed to create filesystem sync:`, error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update task status
|
|
65
|
+
*/
|
|
66
|
+
private async writeStatus(status: string): Promise<void> {
|
|
67
|
+
if (!this.taskPath) return;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const statusPath = path.join(this.taskPath, "status.txt");
|
|
71
|
+
await fs.writeFile(statusPath, status, "utf8");
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(`❌ Failed to write status:`, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Write input file (used for initial state)
|
|
79
|
+
*/
|
|
80
|
+
private async writeInput(content: string): Promise<void> {
|
|
81
|
+
if (!this.taskPath) return;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const inputPath = path.join(this.taskPath, "input.txt");
|
|
85
|
+
await fs.writeFile(inputPath, content, "utf8");
|
|
86
|
+
this.lastInputContent = content;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`❌ Failed to write input:`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Write metadata file
|
|
94
|
+
*/
|
|
95
|
+
private async writeMetadata(data: any): Promise<void> {
|
|
96
|
+
if (!this.taskPath) return;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const metadataPath = path.join(this.taskPath, "metadata.json");
|
|
100
|
+
await fs.writeFile(metadataPath, JSON.stringify(data, null, 2), "utf8");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`❌ Failed to write metadata:`, error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Update metadata file with current agent state
|
|
108
|
+
*/
|
|
109
|
+
private async updateMetadata(agent: BaseAgent, inProgress: boolean, result?: string): Promise<void> {
|
|
110
|
+
if (!this.taskPath) return;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const metadataPath = path.join(this.taskPath, "metadata.json");
|
|
114
|
+
let metadata: any = {};
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const existingData = await fs.readFile(metadataPath, "utf8");
|
|
118
|
+
metadata = JSON.parse(existingData);
|
|
119
|
+
} catch {
|
|
120
|
+
// File doesn't exist or is invalid, start fresh
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
metadata.threads = agent.getThreads();
|
|
124
|
+
metadata.totalCostUsd = agent.getTotalCostUsd();
|
|
125
|
+
metadata.inProgress = inProgress;
|
|
126
|
+
metadata.lastUpdate = new Date().toISOString();
|
|
127
|
+
|
|
128
|
+
if (result !== undefined) {
|
|
129
|
+
metadata.result = result;
|
|
130
|
+
metadata.status = "completed";
|
|
131
|
+
await this.writeStatus("completed");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`❌ Failed to update metadata:`, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Read and process status file changes
|
|
142
|
+
*/
|
|
143
|
+
private async readStatus(): Promise<string | null> {
|
|
144
|
+
if (!this.taskPath) return null;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const statusPath = path.join(this.taskPath, "status.txt");
|
|
148
|
+
const content = await fs.readFile(statusPath, "utf8");
|
|
149
|
+
return content.trim();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// File might not exist or be accessible
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Read and process input file changes
|
|
158
|
+
*/
|
|
159
|
+
private async readInput(): Promise<string | null> {
|
|
160
|
+
if (!this.taskPath) return null;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const inputPath = path.join(this.taskPath, "input.txt");
|
|
164
|
+
const content = await fs.readFile(inputPath, "utf8");
|
|
165
|
+
return content;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// File might not exist or be accessible
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check for file changes and process them
|
|
174
|
+
*/
|
|
175
|
+
private async checkForChanges(agent: BaseAgent): Promise<void> {
|
|
176
|
+
if (!this.taskPath) return;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// Check status changes
|
|
180
|
+
const status = await this.readStatus();
|
|
181
|
+
if (status === "paused") {
|
|
182
|
+
console.log(`⏸️ Agent task ${this.taskId} paused via filesystem`);
|
|
183
|
+
await agent.pause();
|
|
184
|
+
await this.waitForResume(agent);
|
|
185
|
+
} else if (status === "killed") {
|
|
186
|
+
console.log(`🛑 Agent task ${this.taskId} killed via filesystem`);
|
|
187
|
+
await agent.kill();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check for new input/messages
|
|
191
|
+
const input = await this.readInput();
|
|
192
|
+
if (input && input !== this.lastInputContent && input.trim() !== "") {
|
|
193
|
+
console.log(`📬 New message received via filesystem for task ${this.taskId}`);
|
|
194
|
+
this.lastInputContent = input;
|
|
195
|
+
|
|
196
|
+
agent.addPendingUserMessage({
|
|
197
|
+
role: "user",
|
|
198
|
+
content: input,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Clear the input file after processing
|
|
202
|
+
await this.writeInput("");
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(`❌ Error checking for changes:`, error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Wait for resume by monitoring status file
|
|
211
|
+
*/
|
|
212
|
+
private async waitForResume(agent: BaseAgent): Promise<void> {
|
|
213
|
+
const POLL_INTERVAL_MS = 2000;
|
|
214
|
+
const MAX_WAIT_MS = 60 * 60 * 1000; // 1 hour
|
|
215
|
+
const startTime = Date.now();
|
|
216
|
+
|
|
217
|
+
while (Date.now() - startTime < MAX_WAIT_MS) {
|
|
218
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
219
|
+
|
|
220
|
+
const status = await this.readStatus();
|
|
221
|
+
|
|
222
|
+
if (status === "killed") {
|
|
223
|
+
console.log(`🛑 Agent task ${this.taskId} killed while paused`);
|
|
224
|
+
await agent.kill();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (status === "running") {
|
|
229
|
+
console.log(`▶️ Agent task ${this.taskId} resumed`);
|
|
230
|
+
await agent.unpause();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.warn(`⚠️ Timeout waiting for resume on task ${this.taskId}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Setup filesystem watching for the task
|
|
240
|
+
*/
|
|
241
|
+
private setupFileWatcher(agent: BaseAgent): void {
|
|
242
|
+
if (!this.taskPath || this.watcher) return;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
this.watcher = watch(this.taskPath, async (eventType, filename) => {
|
|
246
|
+
if (filename === "status.txt" || filename === "input.txt") {
|
|
247
|
+
await this.checkForChanges(agent);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
console.log(`👁️ Watching filesystem at: ${this.taskPath}`);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(`❌ Failed to setup file watcher:`, error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Set up event-based synchronization for an agent task
|
|
259
|
+
*/
|
|
260
|
+
async setupAgentSync(agent: BaseAgent, taskId?: string): Promise<void> {
|
|
261
|
+
if (!taskId) return;
|
|
262
|
+
|
|
263
|
+
this.taskId = taskId;
|
|
264
|
+
this.taskPath = path.join(this.basePath, this.taskId);
|
|
265
|
+
|
|
266
|
+
// Ensure directory exists (might have been created by createTask)
|
|
267
|
+
try {
|
|
268
|
+
await fs.mkdir(this.taskPath, { recursive: true });
|
|
269
|
+
} catch (error) {
|
|
270
|
+
// Directory might already exist
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!this.eventHandlersSetup) {
|
|
274
|
+
this.setupEventHandlers(agent);
|
|
275
|
+
this.setupFileWatcher(agent);
|
|
276
|
+
this.eventHandlersSetup = true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Set up event handlers for automatic synchronization
|
|
282
|
+
*/
|
|
283
|
+
private setupEventHandlers(agent: BaseAgent): void {
|
|
284
|
+
// Listen to thread updates to sync state
|
|
285
|
+
agent.agentEvents.on(agent.eventTypes.threadUpdate, async () => {
|
|
286
|
+
if (!this.taskId) return;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await this.updateMetadata(agent, true);
|
|
290
|
+
await this.checkForChanges(agent);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(`❌ Error during threadUpdate sync:`, error);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Listen to completion event to finalize task
|
|
297
|
+
agent.agentEvents.on(agent.eventTypes.done, (result: string) => {
|
|
298
|
+
if (!this.taskId) {
|
|
299
|
+
console.warn(`⚠️ [AgentSyncFs] Cannot finalize: taskId=${this.taskId}`);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log(`🎯 [AgentSyncFs] Done event received for task: ${this.taskId}`);
|
|
304
|
+
|
|
305
|
+
// Store finalization promise so callers can await it (same pattern as AgentSyncKnowhowWeb)
|
|
306
|
+
this.finalizationPromise = (async () => {
|
|
307
|
+
try {
|
|
308
|
+
await this.updateMetadata(agent, false, result);
|
|
309
|
+
console.log(`✅ Completed filesystem sync for task: ${this.taskId}`);
|
|
310
|
+
await this.cleanup();
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.error(`❌ Error finalizing task:`, error);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
})();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Wait for finalization to complete (for CLI usage)
|
|
321
|
+
*/
|
|
322
|
+
async waitForFinalization(): Promise<void> {
|
|
323
|
+
if (this.finalizationPromise) {
|
|
324
|
+
await this.finalizationPromise;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Cleanup task directory and watcher
|
|
330
|
+
*/
|
|
331
|
+
async cleanup(): Promise<void> {
|
|
332
|
+
if (this.watcher) {
|
|
333
|
+
this.watcher.close();
|
|
334
|
+
this.watcher = null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Note: We don't delete the directory here to preserve task history
|
|
338
|
+
// The cleanup process will handle old directories
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Clean up old task directories (older than 3 days)
|
|
343
|
+
*/
|
|
344
|
+
private async cleanupOldTasks(): Promise<void> {
|
|
345
|
+
try {
|
|
346
|
+
const agentsPath = this.basePath;
|
|
347
|
+
|
|
348
|
+
// Check if directory exists
|
|
349
|
+
try {
|
|
350
|
+
await fs.access(agentsPath);
|
|
351
|
+
} catch {
|
|
352
|
+
// Directory doesn't exist, nothing to clean
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const entries = await fs.readdir(agentsPath, { withFileTypes: true });
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
const threeDaysMs = 3 * 24 * 60 * 60 * 1000;
|
|
359
|
+
|
|
360
|
+
for (const entry of entries) {
|
|
361
|
+
if (!entry.isDirectory()) continue;
|
|
362
|
+
|
|
363
|
+
const taskPath = path.join(agentsPath, entry.name);
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const stats = await fs.stat(taskPath);
|
|
367
|
+
const age = now - stats.mtimeMs;
|
|
368
|
+
|
|
369
|
+
if (age > threeDaysMs) {
|
|
370
|
+
console.log(`🧹 Cleaning up old task directory: ${entry.name}`);
|
|
371
|
+
await fs.rm(taskPath, { recursive: true, force: true });
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
// Skip if we can't stat or delete
|
|
375
|
+
console.error(`❌ Error cleaning up ${entry.name}:`, error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error(`❌ Error during cleanup:`, error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Start periodic cleanup process
|
|
385
|
+
*/
|
|
386
|
+
private startCleanupProcess(): void {
|
|
387
|
+
// Run cleanup every hour
|
|
388
|
+
this.cleanupInterval = setInterval(() => {
|
|
389
|
+
this.cleanupOldTasks();
|
|
390
|
+
}, 60 * 60 * 1000);
|
|
391
|
+
|
|
392
|
+
// Also run once on startup
|
|
393
|
+
this.cleanupOldTasks();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Stop cleanup process
|
|
398
|
+
*/
|
|
399
|
+
stopCleanup(): void {
|
|
400
|
+
if (this.cleanupInterval) {
|
|
401
|
+
clearInterval(this.cleanupInterval);
|
|
402
|
+
this.cleanupInterval = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Reset synchronization state
|
|
408
|
+
*/
|
|
409
|
+
reset(): void {
|
|
410
|
+
this.cleanup();
|
|
411
|
+
this.taskId = undefined;
|
|
412
|
+
this.taskPath = undefined;
|
|
413
|
+
this.eventHandlersSetup = false;
|
|
414
|
+
this.lastInputContent = "";
|
|
415
|
+
this.finalizationPromise = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -27,7 +27,7 @@ export interface TaskSyncState {
|
|
|
27
27
|
* AgentSynchronization handles all communication with the Knowhow API
|
|
28
28
|
* for task creation, updates, status polling, and message synchronization
|
|
29
29
|
*/
|
|
30
|
-
export class
|
|
30
|
+
export class AgentSyncKnowhowWeb {
|
|
31
31
|
private client: KnowhowSimpleClient;
|
|
32
32
|
private baseUrl: string;
|
|
33
33
|
private knowhowTaskId: string | undefined;
|
|
@@ -254,7 +254,7 @@ export class AgentSynchronization {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
console.log(`🎯 [AgentSync] Done event received for task: ${this.knowhowTaskId}`);
|
|
257
|
-
|
|
257
|
+
|
|
258
258
|
// Create a promise that tracks finalization
|
|
259
259
|
this.finalizationPromise = (async () => {
|
|
260
260
|
try {
|
|
@@ -40,7 +40,6 @@ export class EventService extends EventEmitter {
|
|
|
40
40
|
*/
|
|
41
41
|
async emitBlocking(event: string, ...args: any[]): Promise<any[]> {
|
|
42
42
|
const results: any[] = [];
|
|
43
|
-
|
|
44
43
|
const handlers = this.blockingHandlers.get(event) || [];
|
|
45
44
|
|
|
46
45
|
for (const { handler } of handlers) {
|