@tyvm/knowhow 0.0.69 → 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.
Files changed (210) hide show
  1. package/docs/shell-commands.md +174 -0
  2. package/package.json +1 -1
  3. package/src/agents/base/base.ts +1 -3
  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/chat/CliChatService.ts +57 -11
  9. package/src/chat/modules/AgentModule.ts +72 -12
  10. package/src/chat/modules/CustomCommandsModule.ts +79 -0
  11. package/src/chat/modules/InternalChatModule.ts +11 -1
  12. package/src/chat/modules/ShellCommandModule.ts +96 -0
  13. package/src/chat/modules/index.ts +1 -0
  14. package/src/chat/types.ts +14 -2
  15. package/src/chat.ts +16 -13
  16. package/src/cli.ts +16 -6
  17. package/src/clients/anthropic.ts +41 -90
  18. package/src/clients/gemini.ts +445 -87
  19. package/src/clients/index.ts +125 -0
  20. package/src/clients/knowhow.ts +81 -0
  21. package/src/clients/openai.ts +256 -145
  22. package/src/clients/pricing/anthropic.ts +90 -0
  23. package/src/clients/pricing/google.ts +65 -0
  24. package/src/clients/pricing/index.ts +4 -0
  25. package/src/clients/pricing/openai.ts +134 -0
  26. package/src/clients/pricing/xai.ts +62 -0
  27. package/src/clients/types.ts +170 -1
  28. package/src/clients/xai.ts +275 -46
  29. package/src/config.ts +61 -15
  30. package/src/embeddings.ts +9 -1
  31. package/src/microphone.ts +15 -16
  32. package/src/migrations.ts +151 -0
  33. package/src/plugins/AgentsMdPlugin.ts +118 -0
  34. package/src/plugins/PluginBase.ts +8 -0
  35. package/src/plugins/downloader/downloader.ts +5 -6
  36. package/src/plugins/embedding.ts +10 -8
  37. package/src/plugins/exec.ts +70 -0
  38. package/src/plugins/github.ts +120 -74
  39. package/src/plugins/language.ts +11 -13
  40. package/src/plugins/plugins.ts +25 -4
  41. package/src/plugins/tmux.ts +132 -0
  42. package/src/plugins/types.ts +1 -0
  43. package/src/plugins/vim.ts +14 -1
  44. package/src/services/AgentSyncFs.ts +417 -0
  45. package/src/services/{AgentSynchronization.ts → AgentSyncKnowhowWeb.ts} +2 -2
  46. package/src/services/EventService.ts +0 -1
  47. package/src/services/KnowhowClient.ts +106 -0
  48. package/src/services/index.ts +4 -2
  49. package/src/types.ts +57 -4
  50. package/src/worker.ts +11 -6
  51. package/tests/manual/modalities/README.md +157 -0
  52. package/tests/manual/modalities/google.modalities.test.ts +335 -0
  53. package/tests/manual/modalities/openai.modalities.test.ts +329 -0
  54. package/tests/manual/modalities/streaming.test.ts +260 -0
  55. package/tests/manual/modalities/xai.modalities.test.ts +307 -0
  56. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -5
  57. package/tests/plugins/language/languagePlugin-integration.test.ts +1 -1
  58. package/tests/plugins/language/languagePlugin.test.ts +17 -8
  59. package/ts_build/package.json +1 -1
  60. package/ts_build/src/agents/base/base.js +1 -1
  61. package/ts_build/src/agents/base/base.js.map +1 -1
  62. package/ts_build/src/agents/developer/developer.js +21 -12
  63. package/ts_build/src/agents/developer/developer.js.map +1 -1
  64. package/ts_build/src/agents/tools/agentCall.js +4 -2
  65. package/ts_build/src/agents/tools/agentCall.js.map +1 -1
  66. package/ts_build/src/agents/tools/executeScript/index.d.ts +1 -1
  67. package/ts_build/src/agents/tools/fileSearch.js +2 -1
  68. package/ts_build/src/agents/tools/fileSearch.js.map +1 -1
  69. package/ts_build/src/agents/tools/github/index.d.ts +1 -1
  70. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -1
  71. package/ts_build/src/agents/tools/startAgentTask.js +118 -17
  72. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  73. package/ts_build/src/chat/CliChatService.d.ts +4 -0
  74. package/ts_build/src/chat/CliChatService.js +39 -5
  75. package/ts_build/src/chat/CliChatService.js.map +1 -1
  76. package/ts_build/src/chat/modules/AgentModule.d.ts +4 -1
  77. package/ts_build/src/chat/modules/AgentModule.js +49 -11
  78. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  79. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +9 -0
  80. package/ts_build/src/chat/modules/CustomCommandsModule.js +58 -0
  81. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -0
  82. package/ts_build/src/chat/modules/InternalChatModule.d.ts +2 -0
  83. package/ts_build/src/chat/modules/InternalChatModule.js +10 -0
  84. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  85. package/ts_build/src/chat/modules/ShellCommandModule.d.ts +8 -0
  86. package/ts_build/src/chat/modules/ShellCommandModule.js +83 -0
  87. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -0
  88. package/ts_build/src/chat/modules/index.d.ts +1 -0
  89. package/ts_build/src/chat/modules/index.js +3 -1
  90. package/ts_build/src/chat/modules/index.js.map +1 -1
  91. package/ts_build/src/chat/types.d.ts +11 -1
  92. package/ts_build/src/chat.js +16 -13
  93. package/ts_build/src/chat.js.map +1 -1
  94. package/ts_build/src/cli.js +10 -3
  95. package/ts_build/src/cli.js.map +1 -1
  96. package/ts_build/src/clients/anthropic.d.ts +5 -1
  97. package/ts_build/src/clients/anthropic.js +18 -91
  98. package/ts_build/src/clients/anthropic.js.map +1 -1
  99. package/ts_build/src/clients/gemini.d.ts +80 -2
  100. package/ts_build/src/clients/gemini.js +336 -74
  101. package/ts_build/src/clients/gemini.js.map +1 -1
  102. package/ts_build/src/clients/index.d.ts +9 -1
  103. package/ts_build/src/clients/index.js +65 -0
  104. package/ts_build/src/clients/index.js.map +1 -1
  105. package/ts_build/src/clients/knowhow.d.ts +9 -1
  106. package/ts_build/src/clients/knowhow.js +43 -0
  107. package/ts_build/src/clients/knowhow.js.map +1 -1
  108. package/ts_build/src/clients/openai.d.ts +9 -1
  109. package/ts_build/src/clients/openai.js +201 -133
  110. package/ts_build/src/clients/openai.js.map +1 -1
  111. package/ts_build/src/clients/pricing/anthropic.d.ts +17 -0
  112. package/ts_build/src/clients/pricing/anthropic.js +93 -0
  113. package/ts_build/src/clients/pricing/anthropic.js.map +1 -0
  114. package/ts_build/src/clients/pricing/google.d.ts +73 -0
  115. package/ts_build/src/clients/pricing/google.js +68 -0
  116. package/ts_build/src/clients/pricing/google.js.map +1 -0
  117. package/ts_build/src/clients/pricing/index.d.ts +4 -0
  118. package/ts_build/src/clients/pricing/index.js +14 -0
  119. package/ts_build/src/clients/pricing/index.js.map +1 -0
  120. package/ts_build/src/clients/pricing/openai.d.ts +7 -0
  121. package/ts_build/src/clients/pricing/openai.js +137 -0
  122. package/ts_build/src/clients/pricing/openai.js.map +1 -0
  123. package/ts_build/src/clients/pricing/xai.d.ts +26 -0
  124. package/ts_build/src/clients/pricing/xai.js +59 -0
  125. package/ts_build/src/clients/pricing/xai.js.map +1 -0
  126. package/ts_build/src/clients/types.d.ts +135 -0
  127. package/ts_build/src/clients/xai.d.ts +9 -1
  128. package/ts_build/src/clients/xai.js +178 -46
  129. package/ts_build/src/clients/xai.js.map +1 -1
  130. package/ts_build/src/config.d.ts +1 -0
  131. package/ts_build/src/config.js +45 -16
  132. package/ts_build/src/config.js.map +1 -1
  133. package/ts_build/src/embeddings.js +8 -1
  134. package/ts_build/src/embeddings.js.map +1 -1
  135. package/ts_build/src/microphone.js +7 -9
  136. package/ts_build/src/microphone.js.map +1 -1
  137. package/ts_build/src/migrations.d.ts +17 -0
  138. package/ts_build/src/migrations.js +86 -0
  139. package/ts_build/src/migrations.js.map +1 -0
  140. package/ts_build/src/plugins/AgentsMdPlugin.d.ts +13 -0
  141. package/ts_build/src/plugins/AgentsMdPlugin.js +118 -0
  142. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -0
  143. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  144. package/ts_build/src/plugins/PluginBase.js +3 -0
  145. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  146. package/ts_build/src/plugins/downloader/downloader.js +5 -5
  147. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  148. package/ts_build/src/plugins/embedding.js +9 -8
  149. package/ts_build/src/plugins/embedding.js.map +1 -1
  150. package/ts_build/src/plugins/exec.d.ts +10 -0
  151. package/ts_build/src/plugins/exec.js +56 -0
  152. package/ts_build/src/plugins/exec.js.map +1 -0
  153. package/ts_build/src/plugins/github.js +93 -51
  154. package/ts_build/src/plugins/github.js.map +1 -1
  155. package/ts_build/src/plugins/language.js +14 -11
  156. package/ts_build/src/plugins/language.js.map +1 -1
  157. package/ts_build/src/plugins/plugins.d.ts +1 -0
  158. package/ts_build/src/plugins/plugins.js +19 -1
  159. package/ts_build/src/plugins/plugins.js.map +1 -1
  160. package/ts_build/src/plugins/tmux.d.ts +14 -0
  161. package/ts_build/src/plugins/tmux.js +108 -0
  162. package/ts_build/src/plugins/tmux.js.map +1 -0
  163. package/ts_build/src/plugins/types.d.ts +1 -0
  164. package/ts_build/src/plugins/vim.js +11 -1
  165. package/ts_build/src/plugins/vim.js.map +1 -1
  166. package/ts_build/src/services/AgentSyncFs.d.ts +34 -0
  167. package/ts_build/src/services/AgentSyncFs.js +325 -0
  168. package/ts_build/src/services/AgentSyncFs.js.map +1 -0
  169. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +29 -0
  170. package/ts_build/src/services/AgentSyncKnowhowWeb.js +178 -0
  171. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -0
  172. package/ts_build/src/services/AgentSynchronization.d.ts +1 -1
  173. package/ts_build/src/services/AgentSynchronization.js +3 -3
  174. package/ts_build/src/services/AgentSynchronization.js.map +1 -1
  175. package/ts_build/src/services/EventService.js.map +1 -1
  176. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  177. package/ts_build/src/services/KnowhowClient.js +58 -0
  178. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  179. package/ts_build/src/services/index.d.ts +2 -1
  180. package/ts_build/src/services/index.js +2 -1
  181. package/ts_build/src/services/index.js.map +1 -1
  182. package/ts_build/src/types.d.ts +26 -1
  183. package/ts_build/src/types.js +45 -4
  184. package/ts_build/src/types.js.map +1 -1
  185. package/ts_build/src/utils/PersistentInputManager.d.ts +28 -0
  186. package/ts_build/src/utils/PersistentInputManager.js +293 -0
  187. package/ts_build/src/utils/PersistentInputManager.js.map +1 -0
  188. package/ts_build/src/worker.js +2 -2
  189. package/ts_build/src/worker.js.map +1 -1
  190. package/ts_build/tests/manual/modalities/google.modalities.test.d.ts +1 -0
  191. package/ts_build/tests/manual/modalities/google.modalities.test.js +252 -0
  192. package/ts_build/tests/manual/modalities/google.modalities.test.js.map +1 -0
  193. package/ts_build/tests/manual/modalities/openai.modalities.test.d.ts +1 -0
  194. package/ts_build/tests/manual/modalities/openai.modalities.test.js +252 -0
  195. package/ts_build/tests/manual/modalities/openai.modalities.test.js.map +1 -0
  196. package/ts_build/tests/manual/modalities/streaming.test.d.ts +1 -0
  197. package/ts_build/tests/manual/modalities/streaming.test.js +206 -0
  198. package/ts_build/tests/manual/modalities/streaming.test.js.map +1 -0
  199. package/ts_build/tests/manual/modalities/xai.modalities.test.d.ts +1 -0
  200. package/ts_build/tests/manual/modalities/xai.modalities.test.js +226 -0
  201. package/ts_build/tests/manual/modalities/xai.modalities.test.js.map +1 -0
  202. package/ts_build/tests/manual/persistent-input-test.d.ts +1 -0
  203. package/ts_build/tests/manual/persistent-input-test.js +35 -0
  204. package/ts_build/tests/manual/persistent-input-test.js.map +1 -0
  205. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -5
  206. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  207. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +1 -1
  208. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
  209. package/ts_build/tests/plugins/language/languagePlugin.test.js +17 -7
  210. 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.70",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -758,9 +758,7 @@ export abstract class BaseAgent implements IAgent {
758
758
  logStatus() {
759
759
  const statusMessage = this.getStatusMessage();
760
760
  console.log(
761
- `\n● ${this.name} status: $${this.getTotalCostUsd().toPrecision(
762
- 3
763
- )}\n${statusMessage}`
761
+ `\n● ${this.name} status: ${statusMessage}`
764
762
  );
765
763
  }
766
764
 
@@ -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
  };
@@ -5,6 +5,7 @@
5
5
  import {
6
6
  ChatService,
7
7
  ChatContext,
8
+ CommandResult,
8
9
  ChatCommand,
9
10
  ChatMode,
10
11
  InputMethod,
@@ -112,6 +113,10 @@ export class CliChatService implements ChatService {
112
113
  return this.context;
113
114
  }
114
115
 
116
+ getTools() {
117
+ return this.context.selectedAgent?.tools;
118
+ }
119
+
115
120
  setContext(context: Partial<ChatContext>): void {
116
121
  this.context = { ...this.context, ...context };
117
122
  // Keep chatHistory reference synchronized
@@ -144,6 +149,37 @@ export class CliChatService implements ChatService {
144
149
  return this.commands;
145
150
  }
146
151
 
152
+ /**
153
+ * Get commands available in the current mode
154
+ */
155
+ getCommandsForMode(mode: string): ChatCommand[] {
156
+ return this.commands.filter(
157
+ (cmd) => !cmd.modes || cmd.modes.length === 0 || cmd.modes.includes(mode)
158
+ );
159
+ }
160
+
161
+ getCommandsForActiveModes(): ChatCommand[] {
162
+ const activeModes = this.modes
163
+ .filter((mode) => mode.active)
164
+ .map((mode) => mode.name);
165
+ return this.commands.filter(
166
+ (cmd) =>
167
+ !cmd.modes ||
168
+ cmd.modes.length === 0 ||
169
+ cmd.modes.some((mode) => activeModes.includes(mode))
170
+ );
171
+ }
172
+
173
+ setMode(mode: string): void {
174
+ this.modes.forEach((m) => {
175
+ if (m.name !== "default" && m.name !== mode) {
176
+ m.active = false;
177
+ } else if (m.name === mode) {
178
+ m.active = true;
179
+ }
180
+ });
181
+ }
182
+
147
183
  getModes(): ChatMode[] {
148
184
  return this.modes;
149
185
  }
@@ -154,16 +190,28 @@ export class CliChatService implements ChatService {
154
190
 
155
191
  async processInput(input: string): Promise<boolean> {
156
192
  // Note: Input is added to history via setOnNewHistoryEntry callback when user presses Enter
157
- // Note: this actually sends all commands to modules, first to service takes it
193
+ // Note: this actually sends all commands to modules if not handled by a command
158
194
 
159
195
  // Check if input is a command
160
196
  if (input.startsWith("/")) {
161
197
  const [commandName, ...args] = input.slice(1).split(" ");
162
- const command = this.commands.find((cmd) => cmd.name === commandName);
198
+ const availableCommands = this.getCommandsForActiveModes();
199
+ const command = availableCommands.find((cmd) => cmd.name === commandName);
163
200
 
164
201
  if (command) {
165
- await command.handler(args);
166
- return true;
202
+ const result = await command.handler(args);
203
+
204
+ // If handler returns a CommandResult and it's not handled, pass to modules
205
+ if (result && typeof result === "object" && "handled" in result) {
206
+ if (result.handled) {
207
+ return true;
208
+ }
209
+ // Not handled, use contents if provided or original input
210
+ input = result.contents || input;
211
+ } else {
212
+ // Old-style void handler, consider it handled
213
+ return true;
214
+ }
167
215
  }
168
216
  }
169
217
 
@@ -198,7 +246,7 @@ export class CliChatService implements ChatService {
198
246
 
199
247
  async getInput(
200
248
  prompt: string = "> ",
201
- options: string[] = [],
249
+ options: string[] = []
202
250
  ): Promise<string> {
203
251
  if (this.context.inputMethod) {
204
252
  return await this.context.inputMethod.getInput(prompt);
@@ -264,8 +312,9 @@ export class CliChatService implements ChatService {
264
312
 
265
313
  async startChatLoop(): Promise<void> {
266
314
  // Display available commands like the original
267
- const commandNames = this.commands.map((cmd) => `/${cmd.name}`);
268
- console.log("Commands: ", commandNames.join(", "));
315
+ const availableCommands = this.getCommandsForActiveModes();
316
+ const commandNames = availableCommands.map((cmd) => `/${cmd.name}`);
317
+ console.log("Commands:", commandNames.join(", "));
269
318
 
270
319
  while (true) {
271
320
  const promptText =
@@ -274,10 +323,7 @@ export class CliChatService implements ChatService {
274
323
  : `\nAsk knowhow: `;
275
324
  try {
276
325
  // Pass command names as autocomplete options
277
- const input = await this.getInput(
278
- promptText,
279
- commandNames,
280
- );
326
+ const input = await this.getInput(promptText, commandNames);
281
327
 
282
328
  if (input.trim() === "") {
283
329
  continue;