@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,174 @@
1
+ # Shell Command Execution
2
+
3
+ Knowhow provides multiple ways to execute shell commands during chat sessions, both for interactive terminal use and for sending command output to the AI agent.
4
+
5
+ ## Quick Commands: `/!` and `/!!`
6
+
7
+ Available in `agent` and `agent:attached` modes:
8
+
9
+ ### `/!` - Interactive Shell Command
10
+ Execute a command and display output in the console. The command runs interactively, allowing you to interact with it if needed.
11
+
12
+ ```
13
+ /! git status
14
+ /! npm test
15
+ /! terraform plan
16
+ ```
17
+
18
+ Output is displayed to you but **not sent to the AI agent**.
19
+
20
+ ### `/!!` - Send Output to AI
21
+ Execute a command and send the output to the AI agent for analysis.
22
+
23
+ ```
24
+ /!! git diff
25
+ /!! npm run lint
26
+ /!! cat error.log
27
+ ```
28
+
29
+ Output is displayed to you **and sent to the AI agent** for processing.
30
+
31
+ ## Custom Commands via Language Config
32
+
33
+ You can define custom shell commands in `.knowhow/language.json` that integrate with the language plugin system.
34
+
35
+ ### Example: `/git` Command
36
+
37
+ ```json
38
+ {
39
+ "/git": {
40
+ "events": [],
41
+ "handled": true,
42
+ "sources": [
43
+ {
44
+ "kind": "exec",
45
+ "data": [
46
+ "git status"
47
+ ]
48
+ }
49
+ ]
50
+ }
51
+ }
52
+ ```
53
+
54
+ This creates a `/git` command that runs `git status` when invoked.
55
+
56
+ ### Example: `/tfplan` Command
57
+
58
+ ```json
59
+ {
60
+ "/tfplan": {
61
+ "events": [],
62
+ "handled": true,
63
+ "sources": [
64
+ {
65
+ "kind": "exec",
66
+ "data": [
67
+ "cd terraform && terraform plan"
68
+ ]
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### The `handled` Property
76
+
77
+ - **`handled: true`** - Command output is displayed to the user only, **not sent to the AI agent**
78
+ - **`handled: false`** (default) - Command output is sent to the AI agent for processing
79
+
80
+ This allows you to:
81
+ - Use `handled: true` for commands where you just want to see output (like `/tfplan`)
82
+ - Use `handled: false` for commands where you want the AI to analyze the output
83
+
84
+ ### Multiple Commands
85
+
86
+ You can also chain multiple commands or execute more complex operations:
87
+
88
+ ```json
89
+ {
90
+ "/deploy-status": {
91
+ "events": [],
92
+ "handled": false,
93
+ "sources": [
94
+ {
95
+ "kind": "exec",
96
+ "data": [
97
+ "kubectl get pods && kubectl get services"
98
+ ]
99
+ }
100
+ ]
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Exec Plugin
106
+
107
+ The exec plugin is automatically enabled and allows language config entries to execute shell commands. It's used internally by the custom command system.
108
+
109
+ ### Plugin Configuration
110
+
111
+ The exec plugin is enabled by default in `.knowhow/knowhow.json`:
112
+
113
+ ```json
114
+ {
115
+ "plugins": {
116
+ "enabled": [
117
+ "exec",
118
+ // ... other plugins
119
+ ]
120
+ }
121
+ }
122
+ ```
123
+
124
+ ## Use Cases
125
+
126
+ ### Development Workflow
127
+ ```json
128
+ {
129
+ "/build": {
130
+ "handled": false,
131
+ "sources": [{ "kind": "exec", "data": ["npm run build"] }]
132
+ },
133
+ "/test": {
134
+ "handled": false,
135
+ "sources": [{ "kind": "exec", "data": ["npm test"] }]
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Infrastructure Management
141
+ ```json
142
+ {
143
+ "/infra": {
144
+ "handled": true,
145
+ "sources": [{ "kind": "exec", "data": ["terraform plan"] }]
146
+ },
147
+ "/pods": {
148
+ "handled": false,
149
+ "sources": [{ "kind": "exec", "data": ["kubectl get pods"] }]
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Git Workflows
155
+ ```json
156
+ {
157
+ "/changes": {
158
+ "handled": false,
159
+ "sources": [{ "kind": "exec", "data": ["git diff --cached"] }]
160
+ },
161
+ "/branches": {
162
+ "handled": true,
163
+ "sources": [{ "kind": "exec", "data": ["git branch -a"] }]
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## Security Notes
169
+
170
+ - Commands are executed in your current working directory
171
+ - Commands run with your user permissions
172
+ - Be cautious with commands that modify system state
173
+ - The exec plugin has a 10MB output buffer limit
174
+ - Interactive commands work with `/!` but not with language config exec sources
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.69",
3
+ "version": "0.0.71",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import { EventEmitter } from "events";
2
2
  import {
3
3
  GenericClient,
4
4
  Message,
5
+ MessageContent,
5
6
  OutputMessage,
6
7
  Tool,
7
8
  ToolCall,
@@ -318,7 +319,7 @@ export abstract class BaseAgent implements IAgent {
318
319
  return this.summaries;
319
320
  }
320
321
 
321
- abstract getInitialMessages(userInput: string): Promise<Message[]>;
322
+ abstract getInitialMessages(userInput: string | MessageContent[]): Promise<Message[]>;
322
323
 
323
324
  async processToolMessages(toolCall: ToolCall) {
324
325
  this.agentEvents.emit(this.eventTypes.toolCall, { toolCall });
@@ -479,7 +480,7 @@ export abstract class BaseAgent implements IAgent {
479
480
  } as Message);
480
481
  }
481
482
 
482
- async call(userInput: string, _messages?: Message[]) {
483
+ async call(userInput: string | MessageContent[], _messages?: Message[]) {
483
484
  if (this.status === this.eventTypes.notStarted) {
484
485
  this.status = this.eventTypes.inProgress;
485
486
  }
@@ -758,9 +759,7 @@ export abstract class BaseAgent implements IAgent {
758
759
  logStatus() {
759
760
  const statusMessage = this.getStatusMessage();
760
761
  console.log(
761
- `\n● ${this.name} status: $${this.getTotalCostUsd().toPrecision(
762
- 3
763
- )}\n${statusMessage}`
762
+ `\n● ${this.name} status: ${statusMessage}`
764
763
  );
765
764
  }
766
765
 
@@ -8,12 +8,10 @@ export class DeveloperAgent extends BaseAgent {
8
8
 
9
9
  constructor(context: AgentContext) {
10
10
  super(context);
11
- this.disableTool("patchFile");
12
-
13
11
  this.setModelPreferences([
14
12
  {
15
- model: Models.google.Gemini_20_Flash,
16
- provider: "google",
13
+ model: Models.anthropic.Sonnet4_6,
14
+ provider: "anthropic",
17
15
  },
18
16
  ]);
19
17
  }
@@ -32,8 +30,21 @@ export class DeveloperAgent extends BaseAgent {
32
30
  You delegate some tasks to specialized agents. If a request doesn't require the use of a specialized agent, you can handle it yourself.
33
31
 
34
32
  # How to call other agents
35
- You can use the agentCall tool to call other agents.
36
- Do not try to use VIM/ or Patching tools directly. If you must write to a file yourself use writeFileChunk
33
+ You can use the startAgentTask tool to call other agents.
34
+ This is a wrapper for the shell command knowhow agent --input "your prompt"
35
+
36
+ If sync-fs is active:
37
+ When you start a knowhow agent, it will create a folder in .knowhow/processes/agents/
38
+ For that agent you can use the input.txt file to send it messages.
39
+
40
+ If you send a message to an agent, you can tell it your task directory/input.txt file path and they can write there to respond
41
+ Your task id is:
42
+ ${this.currentTaskId}
43
+
44
+ If you need to write a longer task, you can you knowhow agent --prompt-file <filepath>
45
+ This way you can write out specs and launch the agent on that
46
+
47
+ You can use the status.txt to pause an agent, or pause yourself and have another agent unpause you, or you can use shell commands to wait for an agent's status to change, with a timeout
37
48
 
38
49
  # Which Agent to Use:
39
50
  Researcher -
@@ -43,16 +54,13 @@ export class DeveloperAgent extends BaseAgent {
43
54
  - General Questions about codebase or file structure
44
55
 
45
56
  Patcher
57
+ - this is the default agent
46
58
  - For making modifications to files / code
47
59
  - Great for big files
48
60
 
49
- # Thought process
50
- 1. Is the user asking you a question about the codebase or files? Foreward the question to the Researcher.
51
- 2. Do you need to make changes to files?
52
- 2.a Do we have enough information to know exactly what to modify? If not, ask the Researcher.
53
- 2.b If we know what to modify, ask Patcher to make the changes with all the context required.
54
- 3. If the agent you call has declared it has completed a task, you may need to check it's modifications to see if there's some follow up work required.
55
- 4. If the user is asking for a general task, like webbrowsing or terminal commands, or general questions you may accomplish this yourself.
61
+
62
+ If the user has asked you to do multiple things that are parallelizable, you can start an agent for each task
63
+ Each agent will have it's own log files. You can check the logs of each agent to see their progress.
56
64
  `,
57
65
  },
58
66
  {
@@ -1,5 +1,6 @@
1
1
  import { getConfig } from "../../config";
2
2
  import { services, ToolsService } from "../../services";
3
+ import { getEnabledPlugins } from "../../types";
3
4
 
4
5
  export async function agentCall(agentName: string, userInput: string) {
5
6
  return new Promise(async (resolve, reject) => {
@@ -11,8 +12,9 @@ export async function agentCall(agentName: string, userInput: string) {
11
12
  const { Events, Plugins } = toolService.getContext();
12
13
 
13
14
  let fullPrompt = `${userInput}`;
14
- if (config.plugins?.length) {
15
- const pluginText = await Plugins.callMany(config.plugins, userInput);
15
+ const enabledPlugins = getEnabledPlugins(config.plugins);
16
+ if (enabledPlugins?.length) {
17
+ const pluginText = await Plugins.callMany(enabledPlugins, userInput);
16
18
  fullPrompt += `\n ${pluginText}`;
17
19
  }
18
20
 
@@ -15,7 +15,11 @@ export async function fileSearch(searchTerm) {
15
15
  });
16
16
 
17
17
  const embeddings = await getConfiguredEmbeddings();
18
- const embeddingFiles = embeddings.filter((embedding) =>
18
+
19
+ // Ensure embeddings is always an array
20
+ const embeddingsArray = Array.isArray(embeddings) ? embeddings : [];
21
+
22
+ const embeddingFiles = embeddingsArray.filter((embedding) =>
19
23
  embedding.id.toLowerCase().includes(searchTermLower)
20
24
  );
21
25
 
@@ -1,8 +1,11 @@
1
1
  import { Tool } from "../../clients/types";
2
- import { execCommand } from "./execCommand";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { spawn } from "child_process";
3
5
 
4
6
  interface StartAgentTaskParams {
5
- messageId: string;
7
+ messageId?: string;
8
+ syncFs?: boolean;
6
9
  prompt: string;
7
10
  provider?: string;
8
11
  model?: string;
@@ -11,56 +14,154 @@ interface StartAgentTaskParams {
11
14
  maxSpendLimit?: number;
12
15
  }
13
16
 
17
+ const PROCESSES_DIR = path.join(process.cwd(), ".knowhow", "processes");
18
+ const AGENTS_DIR = path.join(process.cwd(), ".knowhow", "processes", "agents");
19
+
20
+ /**
21
+ * Generate a task ID matching the format used by SessionManager.generateTaskId()
22
+ * Format: {epochSeconds}-{words-from-prompt}
23
+ */
24
+ function generateTaskId(prompt: string): string {
25
+ const words = prompt
26
+ .toLowerCase()
27
+ .replace(/[^\w\s]/g, "")
28
+ .split(/\s+/)
29
+ .filter((word) => word.length > 2)
30
+ .slice(0, 9);
31
+ const wordPart = words.join("-") || "task";
32
+ const epochSeconds = Math.floor(Date.now() / 1000);
33
+ return `${epochSeconds}-${wordPart}`;
34
+ }
35
+
14
36
  /**
15
37
  * Creates a chat task in Knowhow based on a message ID and prompt.
16
- * This allows external agents to start tasks that can receive real-time updates.
38
+ * Spawns the knowhow CLI with the prompt piped via stdin to avoid
39
+ * shell escaping issues with special characters (quotes, backticks,
40
+ * newlines, template expressions, etc.).
41
+ *
42
+ * When syncFs is true, the agent creates a directory at:
43
+ * .knowhow/processes/agents/{taskId}/
44
+ * with files: status.txt, input.txt, metadata.json
45
+ *
46
+ * To send follow-up messages to the agent, write content to:
47
+ * .knowhow/processes/agents/{taskId}/input.txt
48
+ * The agent will pick up the new content and process it as a new message.
17
49
  */
18
- export async function startAgentTask(params: StartAgentTaskParams) {
50
+ export async function startAgentTask(params: StartAgentTaskParams): Promise<string> {
19
51
  const {
20
52
  messageId,
21
53
  prompt,
54
+ syncFs,
22
55
  provider,
23
56
  model,
24
57
  agentName,
25
58
  maxTimeLimit,
26
59
  maxSpendLimit,
27
60
  } = params;
28
-
29
- if (!messageId) {
30
- throw new Error("messageId is required to create a chat task");
31
- }
32
-
33
61
  if (!prompt) {
34
62
  throw new Error("prompt is required to create a chat task");
35
63
  }
36
64
 
37
- const escapedPrompt = prompt.replace(/"/g, '\\"');
65
+ // Pre-generate taskId so we can return the agents dir path to the caller
66
+ const taskId = generateTaskId(prompt);
67
+ const agentTaskDir = path.join(AGENTS_DIR, taskId);
38
68
 
39
- // Build the command with all optional parameters
40
- let command = `knowhow agent --input "${escapedPrompt}" --message-id ${messageId}`;
69
+ // Build args array (no shell escaping needed - args are passed directly)
70
+ const args: string[] = ["agent"];
71
+
72
+ if (messageId) {
73
+ args.push("--message-id", messageId);
74
+ } else if (syncFs) {
75
+ args.push("--sync-fs");
76
+ // Pass the pre-generated taskId so the agent dir path is predictable
77
+ args.push("--task-id", taskId);
78
+ }
41
79
 
42
80
  if (provider) {
43
- command += ` --provider ${provider}`;
81
+ args.push("--provider", provider);
44
82
  }
45
83
 
46
84
  if (model) {
47
- command += ` --model "${model}"`;
85
+ args.push("--model", model);
48
86
  }
49
87
 
50
88
  if (agentName) {
51
- command += ` --agent-name "${agentName}"`;
89
+ args.push("--agent-name", agentName);
52
90
  }
53
91
 
54
92
  if (maxTimeLimit !== undefined) {
55
- command += ` --max-time-limit ${maxTimeLimit}`;
93
+ args.push("--max-time-limit", String(maxTimeLimit));
56
94
  }
57
95
 
58
96
  if (maxSpendLimit !== undefined) {
59
- command += ` --max-spend-limit ${maxSpendLimit}`;
97
+ args.push("--max-spend-limit", String(maxSpendLimit));
60
98
  }
61
99
 
62
- const timeout = maxTimeLimit || 60000;
63
- return execCommand(command, timeout, true);
100
+ const timeoutMs = maxTimeLimit ? maxTimeLimit * 60 * 1000 : 60 * 60 * 1000;
101
+
102
+ // Set up log file for background process output
103
+ fs.mkdirSync(PROCESSES_DIR, { recursive: true });
104
+ const logBaseName = `knowhow_${Math.floor(Date.now() / 1000)}`;
105
+ const logPath = path.join(PROCESSES_DIR, `${logBaseName}.txt`);
106
+ const fd = fs.openSync(logPath, "w");
107
+
108
+ const header =
109
+ `CMD: knowhow ${args.join(" ")}\n` +
110
+ `START: ${new Date().toISOString()}\n` +
111
+ `---\n`;
112
+ fs.writeSync(fd, header);
113
+
114
+ // Spawn with prompt piped via stdin - no shell escaping issues
115
+ const child = spawn("knowhow", args, {
116
+ stdio: ["pipe", fd, fd],
117
+ detached: true,
118
+ });
119
+
120
+ const pid = child.pid!;
121
+ fs.writeSync(fd, `PID: ${pid}\n`);
122
+
123
+ // Write prompt to stdin and close it so the process reads it
124
+ child.stdin!.write(prompt, "utf8");
125
+ child.stdin!.end();
126
+
127
+ return new Promise<string>((resolve) => {
128
+ let settled = false;
129
+ const done = (msg: string) => {
130
+ if (settled) return;
131
+ settled = true;
132
+ try { fs.closeSync(fd); } catch {}
133
+ resolve(msg);
134
+ };
135
+
136
+ child.once("error", (e) => {
137
+ done(`Failed to start agent: ${String(e)}\nLogs: ${logPath}`);
138
+ });
139
+
140
+ const syncFsNote = syncFs
141
+ ? `\nTask ID: ${taskId}\nAgent dir: ${agentTaskDir}\n` +
142
+ `To send follow-up messages, write to: ${agentTaskDir}/input.txt\n` +
143
+ `To check status, read: ${agentTaskDir}/status.txt\n`
144
+ : "";
145
+
146
+ // Give the agent 30 seconds to finish before detaching
147
+ const detachTime = 30 * 1000; // 30 seconds
148
+ const tid = setTimeout(() => {
149
+ try { child.unref(); } catch {}
150
+ done(
151
+ `Agent started (pid=${pid}), running in background.\n` +
152
+ `Logs: ${logPath}\n` +
153
+ syncFsNote
154
+ );
155
+ }, detachTime);
156
+
157
+ child.once("exit", (code) => {
158
+ clearTimeout(tid);
159
+ done(
160
+ `Agent finished with exit code ${code}.\nLogs: ${logPath}\n` +
161
+ syncFsNote
162
+ );
163
+ });
164
+ });
64
165
  }
65
166
 
66
167
  export const startAgentTaskDefinition: Tool = {
@@ -68,14 +169,22 @@ export const startAgentTaskDefinition: Tool = {
68
169
  function: {
69
170
  name: "startAgentTask",
70
171
  description:
71
- "Create a new chat task in Knowhow based on a message ID and prompt. This allows worker agents to start tasks and update knowhow's backend with all CLI agent options",
172
+ "Create a new chat task in Knowhow based on a message ID and prompt. This allows worker agents to start tasks and update knowhow's backend with all CLI agent options. " +
173
+ "When syncFs is true, the agent creates a directory at .knowhow/processes/agents/{taskId}/ with status.txt, input.txt, and metadata.json. " +
174
+ "You can send follow-up messages to the running agent by writing content to .knowhow/processes/agents/{taskId}/input.txt. " +
175
+ "The return value includes the taskId and agent directory path when syncFs is used.",
72
176
  parameters: {
73
177
  type: "object",
74
178
  properties: {
75
179
  messageId: {
76
180
  type: "string",
77
181
  description:
78
- "The ID of the message in Knowhow to associate with this task",
182
+ "The ID of the message in Knowhow to associate with this task (optional)",
183
+ },
184
+ syncFs: {
185
+ type: "boolean",
186
+ description:
187
+ "Enable filesystem-based synchronization for the task. Use this when no messageId is available.",
79
188
  },
80
189
  prompt: {
81
190
  type: "string",
@@ -103,7 +212,7 @@ export const startAgentTaskDefinition: Tool = {
103
212
  description: "Cost limit for agent execution in dollars. Default: 10",
104
213
  },
105
214
  },
106
- required: ["messageId", "prompt"],
215
+ required: ["prompt"],
107
216
  },
108
217
  },
109
218
  };
@@ -1,6 +1,5 @@
1
1
  import * as fs from "fs";
2
- import { embed } from "../../";
3
- import { lintFile } from ".";
2
+ import { services, ToolsService } from "../../services";
4
3
  import { fileExists } from "../../utils";
5
4
 
6
5
  export async function stringReplace(
@@ -8,6 +7,12 @@ export async function stringReplace(
8
7
  replaceString: string,
9
8
  filePaths: string[]
10
9
  ): Promise<string> {
10
+ // Get context from bound ToolsService
11
+ const toolService = (
12
+ this instanceof ToolsService ? this : services().Tools
13
+ ) as ToolsService;
14
+ const context = toolService.getContext();
15
+
11
16
  if (
12
17
  !findString ||
13
18
  replaceString === undefined ||
@@ -41,6 +46,20 @@ export async function stringReplace(
41
46
  continue;
42
47
  }
43
48
 
49
+ // Emit pre-edit blocking event
50
+ const eventResults: any[] = [];
51
+ if (context.Events) {
52
+ eventResults.push(
53
+ ...(await context.Events.emitBlocking("file:pre-edit", {
54
+ filePath,
55
+ operation: "stringReplace",
56
+ findString,
57
+ replaceString,
58
+ originalContent,
59
+ }))
60
+ );
61
+ }
62
+
44
63
  // Perform the replacement
45
64
  const newContent = content.replace(
46
65
  new RegExp(findString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
@@ -53,18 +72,29 @@ export async function stringReplace(
53
72
  totalReplacements += matches;
54
73
  results.push(`✅ Replaced ${matches} occurrence(s) in: ${filePath}`);
55
74
 
56
- let lintResult = "";
57
- try {
58
- lintResult = await lintFile(filePath);
59
- if (lintResult) {
60
- results.push(`$Linting Result:\n" + ${lintResult}`);
61
- }
62
- } catch (lintError: any) {
63
- console.warn("Linting failed after patching:", lintError);
64
- lintResult = `Linting after patch failed: ${lintError.message}`;
75
+ // Emit post-edit blocking event to get event results
76
+ if (context.Events) {
77
+ eventResults.push(
78
+ ...(await context.Events.emitBlocking("file:post-edit", {
79
+ filePath,
80
+ operation: "stringReplace",
81
+ findString,
82
+ replaceString,
83
+ originalContent,
84
+ updatedContent: newContent,
85
+ }))
86
+ );
65
87
  }
66
88
 
67
- await embed();
89
+ // Format event results if any
90
+ if (eventResults && eventResults.length > 0) {
91
+ const eventResultsText = eventResults
92
+ .filter((r) => r && typeof r === "string" && r.trim())
93
+ .join("\n");
94
+ if (eventResultsText) {
95
+ results.push(eventResultsText);
96
+ }
97
+ }
68
98
  } catch (error) {
69
99
  results.push(`❌ Error processing ${filePath}: ${error.message}`);
70
100
  }