@tyvm/knowhow 0.0.69 → 0.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/docs/shell-commands.md +174 -0
  2. package/package.json +1 -1
  3. package/src/agents/base/base.ts +4 -5
  4. package/src/agents/developer/developer.ts +21 -13
  5. package/src/agents/tools/agentCall.ts +4 -2
  6. package/src/agents/tools/fileSearch.ts +5 -1
  7. package/src/agents/tools/startAgentTask.ts +131 -22
  8. package/src/agents/tools/stringReplace.ts +42 -12
  9. package/src/chat/CliChatService.ts +57 -11
  10. package/src/chat/modules/AgentModule.ts +72 -12
  11. package/src/chat/modules/CustomCommandsModule.ts +79 -0
  12. package/src/chat/modules/InternalChatModule.ts +11 -1
  13. package/src/chat/modules/ShellCommandModule.ts +96 -0
  14. package/src/chat/modules/index.ts +1 -0
  15. package/src/chat/types.ts +14 -2
  16. package/src/chat.ts +16 -13
  17. package/src/cli.ts +16 -6
  18. package/src/clients/anthropic.ts +82 -112
  19. package/src/clients/gemini.ts +445 -87
  20. package/src/clients/index.ts +125 -0
  21. package/src/clients/knowhow.ts +81 -0
  22. package/src/clients/openai.ts +256 -145
  23. package/src/clients/pricing/anthropic.ts +90 -0
  24. package/src/clients/pricing/google.ts +65 -0
  25. package/src/clients/pricing/index.ts +4 -0
  26. package/src/clients/pricing/openai.ts +134 -0
  27. package/src/clients/pricing/xai.ts +62 -0
  28. package/src/clients/types.ts +170 -1
  29. package/src/clients/xai.ts +275 -46
  30. package/src/config.ts +61 -15
  31. package/src/embeddings.ts +9 -1
  32. package/src/microphone.ts +15 -16
  33. package/src/migrations.ts +151 -0
  34. package/src/plugins/AgentsMdPlugin.ts +118 -0
  35. package/src/plugins/PluginBase.ts +8 -0
  36. package/src/plugins/downloader/downloader.ts +5 -6
  37. package/src/plugins/embedding.ts +10 -8
  38. package/src/plugins/exec.ts +70 -0
  39. package/src/plugins/github.ts +120 -74
  40. package/src/plugins/language.ts +11 -13
  41. package/src/plugins/plugins.ts +25 -4
  42. package/src/plugins/tmux.ts +132 -0
  43. package/src/plugins/types.ts +1 -0
  44. package/src/plugins/vim.ts +14 -1
  45. package/src/services/AgentSyncFs.ts +417 -0
  46. package/src/services/{AgentSynchronization.ts → AgentSyncKnowhowWeb.ts} +2 -2
  47. package/src/services/EventService.ts +0 -1
  48. package/src/services/KnowhowClient.ts +106 -0
  49. package/src/services/index.ts +4 -2
  50. package/src/types.ts +57 -4
  51. package/src/worker.ts +11 -6
  52. package/tests/manual/modalities/README.md +157 -0
  53. package/tests/manual/modalities/google.modalities.test.ts +335 -0
  54. package/tests/manual/modalities/openai.modalities.test.ts +329 -0
  55. package/tests/manual/modalities/streaming.test.ts +260 -0
  56. package/tests/manual/modalities/xai.modalities.test.ts +307 -0
  57. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -5
  58. package/tests/plugins/language/languagePlugin-integration.test.ts +1 -1
  59. package/tests/plugins/language/languagePlugin.test.ts +17 -8
  60. package/ts_build/package.json +1 -1
  61. package/ts_build/src/agents/base/base.d.ts +3 -3
  62. package/ts_build/src/agents/base/base.js +1 -1
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/developer/developer.js +21 -12
  65. package/ts_build/src/agents/developer/developer.js.map +1 -1
  66. package/ts_build/src/agents/tools/agentCall.js +4 -2
  67. package/ts_build/src/agents/tools/agentCall.js.map +1 -1
  68. package/ts_build/src/agents/tools/executeScript/index.d.ts +1 -1
  69. package/ts_build/src/agents/tools/fileSearch.js +2 -1
  70. package/ts_build/src/agents/tools/fileSearch.js.map +1 -1
  71. package/ts_build/src/agents/tools/github/index.d.ts +1 -1
  72. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -1
  73. package/ts_build/src/agents/tools/startAgentTask.js +118 -17
  74. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  75. package/ts_build/src/agents/tools/stringReplace.js +29 -12
  76. package/ts_build/src/agents/tools/stringReplace.js.map +1 -1
  77. package/ts_build/src/chat/CliChatService.d.ts +4 -0
  78. package/ts_build/src/chat/CliChatService.js +39 -5
  79. package/ts_build/src/chat/CliChatService.js.map +1 -1
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +4 -1
  81. package/ts_build/src/chat/modules/AgentModule.js +49 -11
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +9 -0
  84. package/ts_build/src/chat/modules/CustomCommandsModule.js +58 -0
  85. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -0
  86. package/ts_build/src/chat/modules/InternalChatModule.d.ts +2 -0
  87. package/ts_build/src/chat/modules/InternalChatModule.js +10 -0
  88. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/ShellCommandModule.d.ts +8 -0
  90. package/ts_build/src/chat/modules/ShellCommandModule.js +83 -0
  91. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -0
  92. package/ts_build/src/chat/modules/index.d.ts +1 -0
  93. package/ts_build/src/chat/modules/index.js +3 -1
  94. package/ts_build/src/chat/modules/index.js.map +1 -1
  95. package/ts_build/src/chat/types.d.ts +11 -1
  96. package/ts_build/src/chat.js +16 -13
  97. package/ts_build/src/chat.js.map +1 -1
  98. package/ts_build/src/cli.js +10 -3
  99. package/ts_build/src/cli.js.map +1 -1
  100. package/ts_build/src/clients/anthropic.d.ts +5 -1
  101. package/ts_build/src/clients/anthropic.js +61 -112
  102. package/ts_build/src/clients/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/gemini.d.ts +80 -2
  104. package/ts_build/src/clients/gemini.js +336 -74
  105. package/ts_build/src/clients/gemini.js.map +1 -1
  106. package/ts_build/src/clients/index.d.ts +9 -1
  107. package/ts_build/src/clients/index.js +65 -0
  108. package/ts_build/src/clients/index.js.map +1 -1
  109. package/ts_build/src/clients/knowhow.d.ts +9 -1
  110. package/ts_build/src/clients/knowhow.js +43 -0
  111. package/ts_build/src/clients/knowhow.js.map +1 -1
  112. package/ts_build/src/clients/openai.d.ts +9 -1
  113. package/ts_build/src/clients/openai.js +201 -133
  114. package/ts_build/src/clients/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/anthropic.d.ts +17 -0
  116. package/ts_build/src/clients/pricing/anthropic.js +93 -0
  117. package/ts_build/src/clients/pricing/anthropic.js.map +1 -0
  118. package/ts_build/src/clients/pricing/google.d.ts +73 -0
  119. package/ts_build/src/clients/pricing/google.js +68 -0
  120. package/ts_build/src/clients/pricing/google.js.map +1 -0
  121. package/ts_build/src/clients/pricing/index.d.ts +4 -0
  122. package/ts_build/src/clients/pricing/index.js +14 -0
  123. package/ts_build/src/clients/pricing/index.js.map +1 -0
  124. package/ts_build/src/clients/pricing/openai.d.ts +7 -0
  125. package/ts_build/src/clients/pricing/openai.js +137 -0
  126. package/ts_build/src/clients/pricing/openai.js.map +1 -0
  127. package/ts_build/src/clients/pricing/xai.d.ts +26 -0
  128. package/ts_build/src/clients/pricing/xai.js +59 -0
  129. package/ts_build/src/clients/pricing/xai.js.map +1 -0
  130. package/ts_build/src/clients/types.d.ts +135 -0
  131. package/ts_build/src/clients/xai.d.ts +9 -1
  132. package/ts_build/src/clients/xai.js +178 -46
  133. package/ts_build/src/clients/xai.js.map +1 -1
  134. package/ts_build/src/config.d.ts +1 -0
  135. package/ts_build/src/config.js +45 -16
  136. package/ts_build/src/config.js.map +1 -1
  137. package/ts_build/src/embeddings.js +8 -1
  138. package/ts_build/src/embeddings.js.map +1 -1
  139. package/ts_build/src/microphone.js +7 -9
  140. package/ts_build/src/microphone.js.map +1 -1
  141. package/ts_build/src/migrations.d.ts +17 -0
  142. package/ts_build/src/migrations.js +86 -0
  143. package/ts_build/src/migrations.js.map +1 -0
  144. package/ts_build/src/plugins/AgentsMdPlugin.d.ts +13 -0
  145. package/ts_build/src/plugins/AgentsMdPlugin.js +118 -0
  146. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -0
  147. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  148. package/ts_build/src/plugins/PluginBase.js +3 -0
  149. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  150. package/ts_build/src/plugins/downloader/downloader.js +5 -5
  151. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  152. package/ts_build/src/plugins/embedding.js +9 -8
  153. package/ts_build/src/plugins/embedding.js.map +1 -1
  154. package/ts_build/src/plugins/exec.d.ts +10 -0
  155. package/ts_build/src/plugins/exec.js +56 -0
  156. package/ts_build/src/plugins/exec.js.map +1 -0
  157. package/ts_build/src/plugins/github.js +93 -51
  158. package/ts_build/src/plugins/github.js.map +1 -1
  159. package/ts_build/src/plugins/language.js +14 -11
  160. package/ts_build/src/plugins/language.js.map +1 -1
  161. package/ts_build/src/plugins/plugins.d.ts +1 -0
  162. package/ts_build/src/plugins/plugins.js +19 -1
  163. package/ts_build/src/plugins/plugins.js.map +1 -1
  164. package/ts_build/src/plugins/tmux.d.ts +14 -0
  165. package/ts_build/src/plugins/tmux.js +108 -0
  166. package/ts_build/src/plugins/tmux.js.map +1 -0
  167. package/ts_build/src/plugins/types.d.ts +1 -0
  168. package/ts_build/src/plugins/vim.js +11 -1
  169. package/ts_build/src/plugins/vim.js.map +1 -1
  170. package/ts_build/src/services/AgentSyncFs.d.ts +34 -0
  171. package/ts_build/src/services/AgentSyncFs.js +325 -0
  172. package/ts_build/src/services/AgentSyncFs.js.map +1 -0
  173. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +29 -0
  174. package/ts_build/src/services/AgentSyncKnowhowWeb.js +178 -0
  175. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -0
  176. package/ts_build/src/services/AgentSynchronization.d.ts +1 -1
  177. package/ts_build/src/services/AgentSynchronization.js +3 -3
  178. package/ts_build/src/services/AgentSynchronization.js.map +1 -1
  179. package/ts_build/src/services/EventService.js.map +1 -1
  180. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  181. package/ts_build/src/services/KnowhowClient.js +58 -0
  182. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  183. package/ts_build/src/services/index.d.ts +2 -1
  184. package/ts_build/src/services/index.js +2 -1
  185. package/ts_build/src/services/index.js.map +1 -1
  186. package/ts_build/src/types.d.ts +26 -1
  187. package/ts_build/src/types.js +45 -4
  188. package/ts_build/src/types.js.map +1 -1
  189. package/ts_build/src/utils/PersistentInputManager.d.ts +28 -0
  190. package/ts_build/src/utils/PersistentInputManager.js +293 -0
  191. package/ts_build/src/utils/PersistentInputManager.js.map +1 -0
  192. package/ts_build/src/worker.js +2 -2
  193. package/ts_build/src/worker.js.map +1 -1
  194. package/ts_build/tests/manual/modalities/google.modalities.test.d.ts +1 -0
  195. package/ts_build/tests/manual/modalities/google.modalities.test.js +252 -0
  196. package/ts_build/tests/manual/modalities/google.modalities.test.js.map +1 -0
  197. package/ts_build/tests/manual/modalities/openai.modalities.test.d.ts +1 -0
  198. package/ts_build/tests/manual/modalities/openai.modalities.test.js +252 -0
  199. package/ts_build/tests/manual/modalities/openai.modalities.test.js.map +1 -0
  200. package/ts_build/tests/manual/modalities/streaming.test.d.ts +1 -0
  201. package/ts_build/tests/manual/modalities/streaming.test.js +206 -0
  202. package/ts_build/tests/manual/modalities/streaming.test.js.map +1 -0
  203. package/ts_build/tests/manual/modalities/xai.modalities.test.d.ts +1 -0
  204. package/ts_build/tests/manual/modalities/xai.modalities.test.js +226 -0
  205. package/ts_build/tests/manual/modalities/xai.modalities.test.js.map +1 -0
  206. package/ts_build/tests/manual/persistent-input-test.d.ts +1 -0
  207. package/ts_build/tests/manual/persistent-input-test.js +35 -0
  208. package/ts_build/tests/manual/persistent-input-test.js.map +1 -0
  209. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -5
  210. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  211. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +1 -1
  212. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
  213. package/ts_build/tests/plugins/language/languagePlugin.test.js +17 -7
  214. 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
+ }
@@ -11,6 +11,7 @@ export interface PluginMeta {
11
11
  }
12
12
 
13
13
  export interface Plugin {
14
+ callMany(userInput?: string): Promise<string>;
14
15
  call(userInput?: string): Promise<string>;
15
16
  embed(userInput?: string): Promise<MinimalEmbedding[]>;
16
17
  enable(): void;
@@ -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) => this.getFileContents(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";
@@ -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 AgentSynchronization {
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) {