@tyvm/knowhow 0.0.146 → 0.0.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/autodoc/cli-reference.md +36 -7
  2. package/bin/knowhow.js +8 -6
  3. package/package.json +1 -1
  4. package/src/agents/base/base.ts +51 -23
  5. package/src/agents/historyRollback.ts +36 -0
  6. package/src/agents/tools/execCommand.ts +102 -320
  7. package/src/agents/tools/list.ts +2 -2
  8. package/src/agents/tools/startAgentTask.ts +91 -34
  9. package/src/ai.ts +4 -1
  10. package/src/auth/browserLogin.ts +9 -3
  11. package/src/auth/keyAuth.ts +115 -0
  12. package/src/auth/keyManager.ts +166 -0
  13. package/src/chat/CliChatService.ts +23 -2
  14. package/src/chat/modules/AgentModule.ts +114 -33
  15. package/src/chat/modules/AskModule.ts +2 -0
  16. package/src/chat/modules/BaseChatModule.ts +0 -1
  17. package/src/chat/modules/CustomCommandsModule.ts +2 -0
  18. package/src/chat/modules/InternalChatModule.ts +7 -0
  19. package/src/chat/modules/RemoteSyncModule.ts +4 -0
  20. package/src/chat/modules/RendererModule.ts +2 -0
  21. package/src/chat/modules/SearchModule.ts +2 -0
  22. package/src/chat/modules/SessionsModule.ts +103 -34
  23. package/src/chat/modules/SetupModule.ts +2 -0
  24. package/src/chat/modules/ShellCommandModule.ts +2 -0
  25. package/src/chat/modules/SystemModule.ts +2 -0
  26. package/src/chat/modules/ToolCommandModule.ts +102 -0
  27. package/src/chat/modules/index.ts +1 -0
  28. package/src/chat/types.ts +3 -1
  29. package/src/cli.ts +10 -3
  30. package/src/clients/http.ts +67 -7
  31. package/src/clients/index.ts +4 -0
  32. package/src/commands/agent.ts +1 -38
  33. package/src/commands/agents.ts +455 -60
  34. package/src/commands/misc.ts +3 -2
  35. package/src/commands/processes.ts +138 -0
  36. package/src/commands/services.ts +23 -1
  37. package/src/index.ts +1 -0
  38. package/src/login.ts +89 -28
  39. package/src/processes/ProcessManager.ts +95 -0
  40. package/src/processes/managedProcessShim.ts +115 -0
  41. package/src/services/AgentService.ts +2 -0
  42. package/src/services/AgentSyncFs.ts +77 -1
  43. package/src/services/AgentSyncKnowhowWeb.ts +22 -4
  44. package/src/services/BehaviorsService.ts +2 -0
  45. package/src/services/DockerService.ts +2 -0
  46. package/src/services/EmbeddingService.ts +2 -0
  47. package/src/services/EmbeddingsService.ts +3 -0
  48. package/src/services/ExtensionsService.ts +25 -0
  49. package/src/services/KnowhowClient.ts +68 -7
  50. package/src/services/LazyToolsService.ts +24 -20
  51. package/src/services/Mcp.ts +2 -0
  52. package/src/services/McpServer.ts +2 -0
  53. package/src/services/MessageProcessor.ts +2 -0
  54. package/src/services/MinimalToolsService.ts +2 -0
  55. package/src/services/SessionManager.ts +5 -0
  56. package/src/services/SkillsService.ts +2 -0
  57. package/src/services/SyncedAgentWatcher.ts +2 -0
  58. package/src/services/SyncerService.ts +23 -8
  59. package/src/services/TaskRegistry.ts +2 -0
  60. package/src/services/Tools.ts +12 -0
  61. package/src/services/index.ts +6 -0
  62. package/src/services/modules/index.ts +10 -1
  63. package/src/services/modules/types.ts +4 -0
  64. package/src/types.ts +2 -0
  65. package/src/util/Trace.ts +1 -1
  66. package/src/utils/http.ts +18 -2
  67. package/tests/unit/agents/historyRollback.test.ts +53 -0
  68. package/tests/unit/auth/keyAuth.test.ts +102 -0
  69. package/tests/unit/auth/keyManager.test.ts +67 -0
  70. package/tests/unit/chat/ToolCommandModule.test.ts +71 -0
  71. package/tests/unit/chat/historyCommandArgs.test.ts +37 -0
  72. package/tests/unit/commands/agents.test.ts +111 -0
  73. package/tests/unit/login.test.ts +119 -0
  74. package/tests/unit/services/AgentSyncKnowhowWeb.test.ts +38 -0
  75. package/tests/unit/services/LazyToolsService.test.ts +53 -0
  76. package/tests/unit/services/SessionManager.test.ts +43 -0
  77. package/tests/unit/services/SyncerService.test.ts +45 -0
  78. package/tests/unit/util/Trace.test.ts +119 -0
  79. package/tests/unit/utils/http.test.ts +43 -0
  80. package/ts_build/package.json +1 -1
  81. package/ts_build/src/agents/base/base.d.ts +2 -1
  82. package/ts_build/src/agents/base/base.js +47 -14
  83. package/ts_build/src/agents/base/base.js.map +1 -1
  84. package/ts_build/src/agents/historyRollback.d.ts +2 -0
  85. package/ts_build/src/agents/historyRollback.js +22 -0
  86. package/ts_build/src/agents/historyRollback.js.map +1 -0
  87. package/ts_build/src/agents/tools/execCommand.d.ts +4 -1
  88. package/ts_build/src/agents/tools/execCommand.js +100 -223
  89. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  90. package/ts_build/src/agents/tools/list.js +2 -2
  91. package/ts_build/src/agents/tools/list.js.map +1 -1
  92. package/ts_build/src/agents/tools/startAgentTask.d.ts +16 -1
  93. package/ts_build/src/agents/tools/startAgentTask.js +71 -28
  94. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  95. package/ts_build/src/ai.js +2 -1
  96. package/ts_build/src/ai.js.map +1 -1
  97. package/ts_build/src/auth/browserLogin.d.ts +1 -0
  98. package/ts_build/src/auth/browserLogin.js +5 -2
  99. package/ts_build/src/auth/browserLogin.js.map +1 -1
  100. package/ts_build/src/auth/keyAuth.d.ts +5 -0
  101. package/ts_build/src/auth/keyAuth.js +79 -0
  102. package/ts_build/src/auth/keyAuth.js.map +1 -0
  103. package/ts_build/src/auth/keyManager.d.ts +14 -0
  104. package/ts_build/src/auth/keyManager.js +145 -0
  105. package/ts_build/src/auth/keyManager.js.map +1 -0
  106. package/ts_build/src/chat/CliChatService.d.ts +6 -0
  107. package/ts_build/src/chat/CliChatService.js +17 -2
  108. package/ts_build/src/chat/CliChatService.js.map +1 -1
  109. package/ts_build/src/chat/modules/AgentModule.d.ts +13 -1
  110. package/ts_build/src/chat/modules/AgentModule.js +83 -24
  111. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  112. package/ts_build/src/chat/modules/AskModule.js +12 -2
  113. package/ts_build/src/chat/modules/AskModule.js.map +1 -1
  114. package/ts_build/src/chat/modules/BaseChatModule.js.map +1 -1
  115. package/ts_build/src/chat/modules/CustomCommandsModule.js +12 -2
  116. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  117. package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
  118. package/ts_build/src/chat/modules/InternalChatModule.js +21 -2
  119. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  120. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -2
  121. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  122. package/ts_build/src/chat/modules/RendererModule.js +17 -2
  123. package/ts_build/src/chat/modules/RendererModule.js.map +1 -1
  124. package/ts_build/src/chat/modules/SearchModule.js +12 -2
  125. package/ts_build/src/chat/modules/SearchModule.js.map +1 -1
  126. package/ts_build/src/chat/modules/SessionsModule.d.ts +6 -0
  127. package/ts_build/src/chat/modules/SessionsModule.js +113 -18
  128. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  129. package/ts_build/src/chat/modules/SetupModule.js +17 -2
  130. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  131. package/ts_build/src/chat/modules/ShellCommandModule.js +12 -2
  132. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -1
  133. package/ts_build/src/chat/modules/SystemModule.js +12 -2
  134. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  135. package/ts_build/src/chat/modules/ToolCommandModule.d.ts +10 -0
  136. package/ts_build/src/chat/modules/ToolCommandModule.js +86 -0
  137. package/ts_build/src/chat/modules/ToolCommandModule.js.map +1 -0
  138. package/ts_build/src/chat/modules/index.d.ts +1 -0
  139. package/ts_build/src/chat/modules/index.js +3 -1
  140. package/ts_build/src/chat/modules/index.js.map +1 -1
  141. package/ts_build/src/chat/types.d.ts +3 -1
  142. package/ts_build/src/cli.js +7 -3
  143. package/ts_build/src/cli.js.map +1 -1
  144. package/ts_build/src/clients/http.d.ts +4 -0
  145. package/ts_build/src/clients/http.js +68 -6
  146. package/ts_build/src/clients/http.js.map +1 -1
  147. package/ts_build/src/clients/index.js +23 -4
  148. package/ts_build/src/clients/index.js.map +1 -1
  149. package/ts_build/src/commands/agent.js +1 -21
  150. package/ts_build/src/commands/agent.js.map +1 -1
  151. package/ts_build/src/commands/agents.d.ts +2 -1
  152. package/ts_build/src/commands/agents.js +216 -23
  153. package/ts_build/src/commands/agents.js.map +1 -1
  154. package/ts_build/src/commands/misc.js +3 -2
  155. package/ts_build/src/commands/misc.js.map +1 -1
  156. package/ts_build/src/commands/processes.d.ts +2 -0
  157. package/ts_build/src/commands/processes.js +213 -0
  158. package/ts_build/src/commands/processes.js.map +1 -0
  159. package/ts_build/src/commands/services.js +11 -1
  160. package/ts_build/src/commands/services.js.map +1 -1
  161. package/ts_build/src/index.d.ts +1 -0
  162. package/ts_build/src/index.js +1 -0
  163. package/ts_build/src/index.js.map +1 -1
  164. package/ts_build/src/login.d.ts +1 -1
  165. package/ts_build/src/login.js +53 -15
  166. package/ts_build/src/login.js.map +1 -1
  167. package/ts_build/src/processes/ProcessManager.d.ts +51 -0
  168. package/ts_build/src/processes/ProcessManager.js +160 -0
  169. package/ts_build/src/processes/ProcessManager.js.map +1 -0
  170. package/ts_build/src/processes/managedProcessShim.d.ts +1 -0
  171. package/ts_build/src/processes/managedProcessShim.js +161 -0
  172. package/ts_build/src/processes/managedProcessShim.js.map +1 -0
  173. package/ts_build/src/services/AgentService.js +18 -2
  174. package/ts_build/src/services/AgentService.js.map +1 -1
  175. package/ts_build/src/services/AgentSyncFs.d.ts +3 -0
  176. package/ts_build/src/services/AgentSyncFs.js +68 -16
  177. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  178. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +2 -0
  179. package/ts_build/src/services/AgentSyncKnowhowWeb.js +28 -6
  180. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
  181. package/ts_build/src/services/BehaviorsService.js +12 -2
  182. package/ts_build/src/services/BehaviorsService.js.map +1 -1
  183. package/ts_build/src/services/DockerService.js +20 -8
  184. package/ts_build/src/services/DockerService.js.map +1 -1
  185. package/ts_build/src/services/EmbeddingService.js +16 -2
  186. package/ts_build/src/services/EmbeddingService.js.map +1 -1
  187. package/ts_build/src/services/EmbeddingsService.js +12 -2
  188. package/ts_build/src/services/EmbeddingsService.js.map +1 -1
  189. package/ts_build/src/services/ExtensionsService.d.ts +10 -0
  190. package/ts_build/src/services/ExtensionsService.js +17 -0
  191. package/ts_build/src/services/ExtensionsService.js.map +1 -0
  192. package/ts_build/src/services/KnowhowClient.d.ts +7 -2
  193. package/ts_build/src/services/KnowhowClient.js +108 -10
  194. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  195. package/ts_build/src/services/LazyToolsService.d.ts +2 -0
  196. package/ts_build/src/services/LazyToolsService.js +26 -9
  197. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  198. package/ts_build/src/services/Mcp.js +12 -2
  199. package/ts_build/src/services/Mcp.js.map +1 -1
  200. package/ts_build/src/services/McpServer.js +17 -2
  201. package/ts_build/src/services/McpServer.js.map +1 -1
  202. package/ts_build/src/services/MessageProcessor.js +16 -2
  203. package/ts_build/src/services/MessageProcessor.js.map +1 -1
  204. package/ts_build/src/services/MinimalToolsService.js +16 -2
  205. package/ts_build/src/services/MinimalToolsService.js.map +1 -1
  206. package/ts_build/src/services/SessionManager.js +19 -2
  207. package/ts_build/src/services/SessionManager.js.map +1 -1
  208. package/ts_build/src/services/SkillsService.js +16 -2
  209. package/ts_build/src/services/SkillsService.js.map +1 -1
  210. package/ts_build/src/services/SyncedAgentWatcher.js +16 -2
  211. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  212. package/ts_build/src/services/SyncerService.d.ts +1 -0
  213. package/ts_build/src/services/SyncerService.js +27 -5
  214. package/ts_build/src/services/SyncerService.js.map +1 -1
  215. package/ts_build/src/services/TaskRegistry.js +12 -2
  216. package/ts_build/src/services/TaskRegistry.js.map +1 -1
  217. package/ts_build/src/services/Tools.d.ts +1 -0
  218. package/ts_build/src/services/Tools.js +19 -2
  219. package/ts_build/src/services/Tools.js.map +1 -1
  220. package/ts_build/src/services/index.d.ts +5 -0
  221. package/ts_build/src/services/index.js +7 -2
  222. package/ts_build/src/services/index.js.map +1 -1
  223. package/ts_build/src/services/modules/index.d.ts +4 -1
  224. package/ts_build/src/services/modules/index.js +6 -1
  225. package/ts_build/src/services/modules/index.js.map +1 -1
  226. package/ts_build/src/services/modules/types.d.ts +3 -0
  227. package/ts_build/src/types.d.ts +1 -0
  228. package/ts_build/src/types.js.map +1 -1
  229. package/ts_build/src/util/Trace.d.ts +1 -3
  230. package/ts_build/src/utils/http.d.ts +6 -2
  231. package/ts_build/src/utils/http.js +8 -2
  232. package/ts_build/src/utils/http.js.map +1 -1
  233. package/ts_build/tests/unit/agents/historyRollback.test.d.ts +1 -0
  234. package/ts_build/tests/unit/agents/historyRollback.test.js +42 -0
  235. package/ts_build/tests/unit/agents/historyRollback.test.js.map +1 -0
  236. package/ts_build/tests/unit/auth/keyAuth.test.d.ts +1 -0
  237. package/ts_build/tests/unit/auth/keyAuth.test.js +77 -0
  238. package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -0
  239. package/ts_build/tests/unit/auth/keyManager.test.d.ts +1 -0
  240. package/ts_build/tests/unit/auth/keyManager.test.js +52 -0
  241. package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -0
  242. package/ts_build/tests/unit/chat/ToolCommandModule.test.d.ts +1 -0
  243. package/ts_build/tests/unit/chat/ToolCommandModule.test.js +61 -0
  244. package/ts_build/tests/unit/chat/ToolCommandModule.test.js.map +1 -0
  245. package/ts_build/tests/unit/chat/historyCommandArgs.test.d.ts +1 -0
  246. package/ts_build/tests/unit/chat/historyCommandArgs.test.js +29 -0
  247. package/ts_build/tests/unit/chat/historyCommandArgs.test.js.map +1 -0
  248. package/ts_build/tests/unit/commands/agents.test.d.ts +1 -0
  249. package/ts_build/tests/unit/commands/agents.test.js +119 -0
  250. package/ts_build/tests/unit/commands/agents.test.js.map +1 -0
  251. package/ts_build/tests/unit/login.test.d.ts +1 -0
  252. package/ts_build/tests/unit/login.test.js +97 -0
  253. package/ts_build/tests/unit/login.test.js.map +1 -0
  254. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.d.ts +1 -0
  255. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js +32 -0
  256. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js.map +1 -0
  257. package/ts_build/tests/unit/services/LazyToolsService.test.d.ts +1 -0
  258. package/ts_build/tests/unit/services/LazyToolsService.test.js +47 -0
  259. package/ts_build/tests/unit/services/LazyToolsService.test.js.map +1 -0
  260. package/ts_build/tests/unit/services/SessionManager.test.d.ts +1 -0
  261. package/ts_build/tests/unit/services/SessionManager.test.js +72 -0
  262. package/ts_build/tests/unit/services/SessionManager.test.js.map +1 -0
  263. package/ts_build/tests/unit/services/SyncerService.test.d.ts +1 -0
  264. package/ts_build/tests/unit/services/SyncerService.test.js +40 -0
  265. package/ts_build/tests/unit/services/SyncerService.test.js.map +1 -0
  266. package/ts_build/tests/unit/util/Trace.test.d.ts +1 -0
  267. package/ts_build/tests/unit/util/Trace.test.js +107 -0
  268. package/ts_build/tests/unit/util/Trace.test.js.map +1 -0
  269. package/ts_build/tests/unit/utils/http.test.d.ts +1 -0
  270. package/ts_build/tests/unit/utils/http.test.js +69 -0
  271. package/ts_build/tests/unit/utils/http.test.js.map +1 -0
@@ -23,8 +23,9 @@ export function addLoginCommand(program: Command): void {
23
23
  .command("login")
24
24
  .description("Login to knowhow")
25
25
  .option("--jwt", "Use manual JWT input instead of browser login")
26
- .action(async (opts) => {
27
- await login(opts.jwt);
26
+ .option("-i, --identity <path>", "Ed25519 private key to use for public-key login")
27
+ .action(async (options: { jwt?: boolean; identity?: string }) => {
28
+ await login(options.jwt, options.identity);
28
29
  });
29
30
  }
30
31
 
@@ -0,0 +1,138 @@
1
+ import { Command } from "commander";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import {
5
+ getProcessesDir, listManagedProcesses, ManagedProcess,
6
+ ManagedProcessInfo, processDirectory,
7
+ } from "../processes/ProcessManager";
8
+ import { runManagedProcessShim } from "../processes/managedProcessShim";
9
+
10
+ const terminal = new Set(["exited", "failed", "stopped"]);
11
+ function resolveProcess(value: string): ManagedProcess {
12
+ const root = getProcessesDir();
13
+ const matches = listManagedProcesses(root).filter((p) => p.id === value || p.id.includes(value));
14
+ if (!matches.length) throw new Error(`Managed process not found: ${value}`);
15
+ const exact = matches.find((p) => p.id === value);
16
+ if (!exact && matches.length > 1) throw new Error(`Ambiguous process id '${value}': ${matches.map((p) => p.id).join(", ")}`);
17
+ const item = exact || matches[0];
18
+ return new ManagedProcess(item.id, item.directory);
19
+ }
20
+ function commandOptions(options: any, command?: Command): any {
21
+ const fromOptions = typeof options?.optsWithGlobals === "function" ? options.optsWithGlobals() : options || {};
22
+ const fromCommand = typeof command?.optsWithGlobals === "function" ? command.optsWithGlobals() : {};
23
+ return { ...fromOptions, ...fromCommand };
24
+ }
25
+ function filtered(options: any): ManagedProcessInfo[] {
26
+ return listManagedProcesses().filter((item) => {
27
+ if (!options.all && terminal.has(item.status.state)) return false;
28
+ if (options.parent && item.status.parentProcessId !== options.parent) return false;
29
+ if (options.background !== undefined && item.status.background !== options.background) return false;
30
+ return true;
31
+ });
32
+ }
33
+ function printList(options: any): void {
34
+ const rows = filtered(options).map(({ id, status }) => ({
35
+ id, state: status.state, pid: status.pid || "-", background: status.background ? "yes" : "no",
36
+ parent: status.parentProcessId || "-", command: [status.command, ...status.args].join(" "),
37
+ }));
38
+ if (options.json) console.log(JSON.stringify(rows, null, 2));
39
+ else if (rows.length) console.table(rows);
40
+ else console.log("No managed processes.");
41
+ }
42
+ function addFilters(command: Command): Command {
43
+ return command.option("-a, --all", "include completed processes")
44
+ .option("--parent <id>", "filter by parent process/task id")
45
+ .option("--background", "show only background processes")
46
+ .option("--json", "emit JSON");
47
+ }
48
+ function tailText(file: string, lines: number): string {
49
+ if (!fs.existsSync(file)) return "";
50
+ return fs.readFileSync(file, "utf8").split("\n").slice(-lines - 1).join("\n");
51
+ }
52
+ async function followFiles(files: string[], fromEnd = true): Promise<void> {
53
+ const offsets = new Map<string, number>();
54
+ for (const file of files) offsets.set(file, fromEnd && fs.existsSync(file) ? fs.statSync(file).size : 0);
55
+ await new Promise<void>((resolve) => {
56
+ const poll = setInterval(() => {
57
+ for (const file of files) {
58
+ if (!fs.existsSync(file)) continue;
59
+ const size = fs.statSync(file).size; const offset = offsets.get(file) || 0;
60
+ if (size < offset) offsets.set(file, 0);
61
+ if (size > (offsets.get(file) || 0)) {
62
+ const start = offsets.get(file) || 0;
63
+ const fd = fs.openSync(file, "r"); const data = Buffer.alloc(size - start);
64
+ fs.readSync(fd, data, 0, data.length, start); fs.closeSync(fd);
65
+ process.stdout.write(data); offsets.set(file, size);
66
+ }
67
+ }
68
+ }, 150);
69
+ const done = () => { clearInterval(poll); resolve(); };
70
+ process.once("SIGINT", done); process.once("SIGTERM", done);
71
+ });
72
+ }
73
+
74
+ export function addProcessesCommand(program: Command): void {
75
+ const processes = addFilters(program.command("processes").description("inspect and control managed processes"));
76
+ processes.action((rawOptions, command) => printList(commandOptions(rawOptions, command)));
77
+ addFilters(processes.command("list").description("list managed processes"))
78
+ .action((rawOptions, command) => printList(commandOptions(rawOptions, command)));
79
+
80
+ processes.command("run").description("internal managed process shim").requiredOption("--config <path>")
81
+ .action(async ({ config }) => runManagedProcessShim(path.resolve(config)));
82
+
83
+ processes.command("logs <id>").alias("tail").option("-n, --lines <number>", "lines to show", "100")
84
+ .option("-f, --follow", "follow output").option("--stderr", "show stderr instead of stdout")
85
+ .option("--both", "show stdout and stderr").action(async (id, rawOptions, command) => {
86
+ const options = commandOptions(rawOptions, command);
87
+ const managed = resolveProcess(id);
88
+ const files = options.both ? [managed.stdoutPath, managed.stderrPath] : [options.stderr ? managed.stderrPath : managed.stdoutPath];
89
+ for (const file of files) process.stdout.write(tailText(file, Math.max(0, Number(options.lines) || 100)));
90
+ if (options.follow) await followFiles(files);
91
+ });
92
+
93
+ processes.command("send <id> [input...]").option("-n, --no-newline", "do not append newline")
94
+ .action(async (id, input: string[], rawOptions, command) => {
95
+ const options = commandOptions(rawOptions, command);
96
+ const managed = resolveProcess(id);
97
+ await managed.write((input || []).join(" ") + (options.newline ? "\n" : ""));
98
+ });
99
+
100
+ processes.command("signal <id> [signal]").action((id, signal = "SIGTERM") => resolveProcess(id).signal(signal.toUpperCase() as NodeJS.Signals));
101
+ processes.command("stop <id>").action((id) => resolveProcess(id).signal("SIGTERM"));
102
+ processes.command("kill <id>").action((id) => resolveProcess(id).signal("SIGKILL"));
103
+
104
+ processes.command("attach <id>").description("attach terminal (Ctrl-] to detach)").action(async (id) => {
105
+ const managed = resolveProcess(id); let detached = false;
106
+ const raw = !!process.stdin.isTTY; if (raw) process.stdin.setRawMode(true);
107
+ process.stdin.resume();
108
+ const onData = async (data: Buffer) => {
109
+ if (data.includes(0x1d)) { detached = true; return; }
110
+ try { await managed.write(data.toString()); } catch (error) { console.error(String(error)); detached = true; }
111
+ };
112
+ process.stdin.on("data", onData);
113
+ const offsets = [managed.stdoutPath, managed.stderrPath].map((f) => fs.existsSync(f) ? fs.statSync(f).size : 0);
114
+ await new Promise<void>((resolve) => {
115
+ const timer = setInterval(() => {
116
+ [managed.stdoutPath, managed.stderrPath].forEach((file, i) => {
117
+ if (!fs.existsSync(file)) return; const size = fs.statSync(file).size;
118
+ if (size > offsets[i]) { const fd = fs.openSync(file, "r"); const b = Buffer.alloc(size - offsets[i]); fs.readSync(fd, b, 0, b.length, offsets[i]); fs.closeSync(fd); process.stdout.write(b); offsets[i] = size; }
119
+ });
120
+ if (detached || terminal.has(managed.status.state)) { clearInterval(timer); resolve(); }
121
+ }, 100);
122
+ });
123
+ process.stdin.off("data", onData); process.stdin.pause(); if (raw) process.stdin.setRawMode(false);
124
+ });
125
+
126
+ processes.command("tree").option("-a, --all", "include completed processes").action((rawOptions, command) => {
127
+ const options = commandOptions(rawOptions, command);
128
+ const items = listManagedProcesses().filter((p) => options.all || !terminal.has(p.status.state));
129
+ const ids = new Set(items.map((p) => p.id));
130
+ const children = new Map<string | null, ManagedProcessInfo[]>();
131
+ for (const item of items) { const parent = item.status.parentProcessId && ids.has(item.status.parentProcessId) ? item.status.parentProcessId : null; children.set(parent, [...(children.get(parent) || []), item]); }
132
+ const render = (parent: string | null, prefix: string) => (children.get(parent) || []).forEach((item, index, array) => {
133
+ const last = index === array.length - 1; console.log(`${prefix}${last ? "└─" : "├─"}${item.id} [${item.status.state}] ${item.status.command}`);
134
+ render(item.id, prefix + (last ? " " : "│ "));
135
+ });
136
+ render(null, "");
137
+ });
138
+ }
@@ -3,7 +3,8 @@ import * as allTools from "../agents/tools";
3
3
  import { LazyToolsService, services, MinimalToolsService, TracingService } from "../services";
4
4
  import { agents } from "../agents";
5
5
  import { ModulesService } from "../services/modules";
6
- import { getConfig } from "../config";
6
+ import { getConfig, getConfigSync } from "../config";
7
+ import { authenticateWithKey } from "../auth/keyAuth";
7
8
 
8
9
  /**
9
10
  * Shared service setup used by commands that need full services (chat, agent, worker, etc.)
@@ -20,6 +21,7 @@ export async function setupServices() {
20
21
  MediaProcessor,
21
22
  Behaviors,
22
23
  RuntimeReload,
24
+ Extensions,
23
25
  } = services();
24
26
 
25
27
  await Plugins.refreshConfiguredState();
@@ -76,6 +78,25 @@ export async function setupServices() {
76
78
  `⚠ Some MCP servers failed to connect (continuing without them): ${msg}`
77
79
  );
78
80
  }
81
+ // Attempt silent JWT refresh via SSH identity before registering models.
82
+ // This handles the case where the JWT has expired but the user has a valid
83
+ // SSH identity registered — they shouldn't need to run `knowhow login` again
84
+ // unless they want to switch organizations.
85
+ try {
86
+ const startupConfig = getConfigSync();
87
+ if (startupConfig.orgId && !process.env.KNOWHOW_JWT) {
88
+ await authenticateWithKey(
89
+ startupConfig.orgId,
90
+ process.env.KNOWHOW_API_URL || "https://api.knowhow.tyvm.ai",
91
+ startupConfig.cliIdentityPath
92
+ );
93
+ }
94
+ } catch (_err) {
95
+ // Non-fatal: if SSH refresh fails (no key, revoked, network issue), we
96
+ // continue with the existing JWT (or no JWT). The user will see model
97
+ // warnings if no valid JWT is available.
98
+ }
99
+
79
100
  console.log("Connecting to clients...");
80
101
  await Clients.registerConfiguredModels();
81
102
  console.log("✓ Services are set up and ready to go!");
@@ -92,6 +113,7 @@ export async function setupServices() {
92
113
  Behaviors,
93
114
  Events,
94
115
  Tracing: TracingService,
116
+ Extensions,
95
117
  };
96
118
 
97
119
  // Call destroy() on all modules when the process is shutting down so they
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export * as embeddings from "./embeddings";
65
65
  export * as types from "./types";
66
66
  export * as processors from "./processors";
67
67
  export * as ai from "./ai";
68
+ export * from "./processes/ProcessManager";
68
69
 
69
70
  // Export module system types for external modules
70
71
  export * from "./services/modules/types";
package/src/login.ts CHANGED
@@ -1,13 +1,28 @@
1
1
  import http from "./utils/http";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { chmod } from "fs/promises";
5
4
  import { ask } from "./utils";
6
5
  import { getConfig, updateConfig } from "./config";
7
6
  import { KNOWHOW_API_URL } from "./services/KnowhowClient";
8
7
  import { BrowserLoginService } from "./auth/browserLogin";
9
-
10
- export async function login(jwtFlag?: boolean): Promise<void> {
8
+ import { authenticateWithKey, hasKeyPair, registerPublicKey } from "./auth/keyAuth";
9
+ import { getOrCreatePublicKey } from "./auth/keyManager";
10
+
11
+ /**
12
+ * Log in to Knowhow.
13
+ *
14
+ * Strategy (in order):
15
+ * 1. `--jwt` flag: prompt for a raw JWT and write it to disk.
16
+ * 2. Key auth: if an Ed25519 key and configured organization exist, sign a
17
+ * server challenge and exchange it for an organization-bound JWT.
18
+ * 3. Browser: open the browser approval flow (first-time login or key not yet registered).
19
+ *
20
+ * After any successful login the local config is updated with the current user's orgId.
21
+ *
22
+ * @param jwtFlag True when --jwt flag is passed.
23
+ * @param identityPath Optional path to a specific private key file (--identity flag).
24
+ */
25
+ export async function login(jwtFlag?: boolean, identityPath?: string): Promise<void> {
11
26
  if (!KNOWHOW_API_URL) {
12
27
  throw new Error("Error: KNOWHOW_API_URL environment variable not set.");
13
28
  }
@@ -15,9 +30,8 @@ export async function login(jwtFlag?: boolean): Promise<void> {
15
30
  if (jwtFlag) {
16
31
  const jwt = await ask("Enter your JWT: ");
17
32
 
18
- // Update the JWT file
19
33
  const configDir = path.join(process.cwd(), ".knowhow");
20
- const jwtFile = path.join(process.cwd(), ".knowhow", ".jwt");
34
+ const jwtFile = path.join(configDir, ".jwt");
21
35
 
22
36
  if (!fs.existsSync(configDir)) {
23
37
  fs.mkdirSync(configDir, { recursive: true });
@@ -26,23 +40,69 @@ export async function login(jwtFlag?: boolean): Promise<void> {
26
40
  fs.chmodSync(jwtFile, 0o600);
27
41
  console.log("JWT updated successfully.");
28
42
  } else {
29
- // Use browser login as default method
30
- console.log("Starting browser-based authentication...");
43
+ const config = await getConfig();
44
+ const orgId = config.orgId;
45
+
46
+ // A local key is not proof of registration. Let the selected environment
47
+ // check it, but only when an organization is available to bind the attempt.
48
+ if (orgId && hasKeyPair(identityPath)) {
49
+ console.log("Found CLI identity — authenticating with public key...");
50
+ try {
51
+ const success = await authenticateWithKey(orgId, KNOWHOW_API_URL, identityPath);
52
+ if (success) {
53
+ console.log("✅ Successfully authenticated via public key!");
54
+ return await postLoginConfigUpdate(identityPath);
55
+ }
56
+ console.warn("Key authentication returned no JWT, falling back to browser login...");
57
+ } catch (error: unknown) {
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ console.warn(`Key authentication failed (${message}), falling back to browser login...`);
60
+ }
61
+ }
62
+
63
+ // Fall back when no org/key is configured or when key auth is unavailable.
64
+ await doBrowserLogin(identityPath);
65
+ }
66
+
67
+ await postLoginConfigUpdate(identityPath);
68
+ }
69
+
70
+ /** Complete browser PKCE, then best-effort bootstrap the selected identity. */
71
+ async function doBrowserLogin(identityPath?: string): Promise<void> {
72
+ console.log("Starting browser-based authentication...");
73
+ try {
74
+ const existingConfig = await getConfig();
75
+ const existingOrgId = existingConfig?.orgId;
76
+ const browserLoginService = new BrowserLoginService(undefined, existingOrgId);
77
+ await browserLoginService.login();
78
+ console.log("✅ Successfully authenticated via browser!");
79
+
31
80
  try {
32
- // Pass existing orgId from config (if any) so the browser pre-selects the right org
33
- const existingConfig = await getConfig();
34
- const existingOrgId = existingConfig?.orgId;
35
- const browserLoginService = new BrowserLoginService(undefined, existingOrgId);
36
- await browserLoginService.login();
37
- console.log("Successfully authenticated via browser!");
38
- } catch (error) {
39
- console.error("Browser authentication failed:", error.message);
40
- console.log("You can try manual JWT login with: knowhow login --jwt");
41
- throw error;
81
+ const keyPair = getOrCreatePublicKey(identityPath);
82
+ await registerPublicKey(
83
+ await loadJwt(),
84
+ keyPair.publicKeyBase64,
85
+ KNOWHOW_API_URL
86
+ );
87
+ console.log(`Registered CLI identity ${keyPair.fingerprint}`);
88
+ } catch (registrationError: unknown) {
89
+ // Browser login remains valid when key registration is unavailable (for
90
+ // example during a rolling backend deployment or for a partial identity).
91
+ const message = registrationError instanceof Error
92
+ ? registrationError.message
93
+ : String(registrationError);
94
+ console.warn(`Could not register CLI identity (${message}). Browser login is still active.`);
42
95
  }
96
+ } catch (error: unknown) {
97
+ const msg = error instanceof Error ? error.message : String(error);
98
+ console.error("Browser authentication failed:", msg);
99
+ console.log("You can try manual JWT login with: knowhow login --jwt");
100
+ throw error;
43
101
  }
102
+ }
44
103
 
45
- // Get current user/org information
104
+ /** After any successful login, update the local config with the current user/org. */
105
+ async function postLoginConfigUpdate(identityPath?: string): Promise<void> {
46
106
  try {
47
107
  const storedJwt = await loadJwt();
48
108
  const { user, currentOrg } = await checkJwt(storedJwt);
@@ -59,25 +119,26 @@ export async function login(jwtFlag?: boolean): Promise<void> {
59
119
  }
60
120
 
61
121
  const hasProvider = config.modelProviders.find(
62
- (provider) => provider.provider === "knowhow"
122
+ (provider: { provider: string }) => provider.provider === "knowhow"
63
123
  );
64
124
  if (!hasProvider) {
65
- config.modelProviders.push({
66
- provider: "knowhow",
67
- });
125
+ config.modelProviders.push({ provider: "knowhow" });
68
126
  }
69
- // Save orgId to config so sync:remote and other features use the correct org
127
+
70
128
  if (orgId) {
71
129
  config.orgId = orgId;
72
130
  }
73
131
 
132
+ if (identityPath) {
133
+ config.cliIdentityPath = path.resolve(identityPath);
134
+ }
135
+
74
136
  await updateConfig(config);
75
- } catch (error) {
137
+ } catch (error: unknown) {
76
138
  if (http.isHttpError(error) && error.response) {
77
- // error.body is the parsed JSON response body (set by http.ts when response is not ok)
78
- const errData = error.body ?? { message: "Unknown error" };
139
+ const errData = (error as any).body ?? { message: "Unknown error" };
79
140
  throw new Error(
80
- `Error: ${error.status} - ${errData?.message || "Unknown error"}`
141
+ `Error: ${(error as any).status} - ${errData?.message || "Unknown error"}`
81
142
  );
82
143
  }
83
144
  console.log(
@@ -112,7 +173,7 @@ export async function checkJwt(storedJwt: string) {
112
173
  const orgs = user.orgs;
113
174
  const orgId = response.data.orgId;
114
175
 
115
- const currentOrg = orgs.find((org) => {
176
+ const currentOrg = orgs.find((org: { organizationId: string }) => {
116
177
  return org.organizationId === orgId;
117
178
  });
118
179
 
@@ -0,0 +1,95 @@
1
+ import { spawn } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { randomBytes } from "crypto";
5
+
6
+ export type ManagedProcessState = "starting" | "running" | "exited" | "failed" | "stopped";
7
+ export interface ManagedProcessStatus {
8
+ id: string; pid: number | null; shimPid: number | null; state: ManagedProcessState;
9
+ command: string; args: string[]; cwd: string; parentProcessId: string | null;
10
+ parentPid: number | null; background: boolean; startedAt: string; endedAt: string | null;
11
+ exitCode: number | null; signal: string | null; error?: string;
12
+ }
13
+ export interface SpawnManagedOptions {
14
+ id?: string; cwd?: string; env?: NodeJS.ProcessEnv; background?: boolean;
15
+ parentProcessId?: string; parentPid?: number; shell?: boolean; processesDir?: string;
16
+ }
17
+ export interface ManagedProcessInfo { id: string; directory: string; status: ManagedProcessStatus; }
18
+
19
+ export function getProcessesDir(cwd = process.cwd()): string {
20
+ return process.env.KNOWHOW_PROCESSES_DIR || path.join(cwd, ".knowhow", "processes");
21
+ }
22
+ export function processDirectory(id: string, processesDir = getProcessesDir()): string {
23
+ if (!/^[a-zA-Z0-9._-]+$/.test(id)) throw new Error(`Invalid process id: ${id}`);
24
+ return path.join(processesDir, id);
25
+ }
26
+ export function readManagedStatus(id: string, processesDir = getProcessesDir()): ManagedProcessStatus {
27
+ return JSON.parse(fs.readFileSync(path.join(processDirectory(id, processesDir), "status.json"), "utf8"));
28
+ }
29
+ export function listManagedProcesses(processesDir = getProcessesDir()): ManagedProcessInfo[] {
30
+ if (!fs.existsSync(processesDir)) return [];
31
+ return fs.readdirSync(processesDir, { withFileTypes: true })
32
+ .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(processesDir, entry.name, "status.json")))
33
+ .flatMap((entry) => { try { return [{ id: entry.name, directory: path.join(processesDir, entry.name), status: readManagedStatus(entry.name, processesDir) }]; } catch { return []; } })
34
+ .sort((a, b) => b.status.startedAt.localeCompare(a.status.startedAt));
35
+ }
36
+ function makeId(command: string): string {
37
+ const name = path.basename(command).replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 28) || "process";
38
+ return `${name}-${Date.now()}-${randomBytes(3).toString("hex")}`;
39
+ }
40
+ function waitForFile(file: string, timeoutMs: number): Promise<void> {
41
+ const started = Date.now();
42
+ return new Promise((resolve, reject) => { const check = () => {
43
+ if (fs.existsSync(file)) return resolve();
44
+ if (Date.now() - started >= timeoutMs) return reject(new Error(`Managed process did not initialize: ${file}`));
45
+ setTimeout(check, 20);
46
+ }; check(); });
47
+ }
48
+ export class ManagedProcess {
49
+ constructor(public readonly id: string, public readonly directory: string) {}
50
+ get status(): ManagedProcessStatus { return readManagedStatus(this.id, path.dirname(this.directory)); }
51
+ get stdoutPath(): string { return path.join(this.directory, "stdout"); }
52
+ get stderrPath(): string { return path.join(this.directory, "stderr"); }
53
+ get inputLogPath(): string { return path.join(this.directory, "input.log"); }
54
+ get stdinPath(): string { return path.join(this.directory, "stdin"); }
55
+ async write(input: string): Promise<void> {
56
+ if (process.platform === "win32") {
57
+ await fs.promises.appendFile(this.stdinPath, input);
58
+ return;
59
+ }
60
+ // A non-blocking open fails promptly with ENXIO if the shim is gone instead
61
+ // of leaving callers hung forever waiting for a FIFO reader.
62
+ const handle = await fs.promises.open(this.stdinPath, fs.constants.O_WRONLY | fs.constants.O_NONBLOCK);
63
+ try { await handle.writeFile(input); } finally { await handle.close(); }
64
+ }
65
+ signal(signal: NodeJS.Signals = "SIGTERM"): void {
66
+ const status = this.status;
67
+ if (!status.pid) throw new Error(`Process ${this.id} has no active pid`);
68
+ try { process.kill(-status.pid, signal); } catch { process.kill(status.pid, signal); }
69
+ }
70
+ async wait(timeoutMs?: number): Promise<ManagedProcessStatus> {
71
+ const started = Date.now();
72
+ while (true) {
73
+ const status = this.status;
74
+ if (!["starting", "running"].includes(status.state)) return status;
75
+ if (timeoutMs !== undefined && Date.now() - started >= timeoutMs) throw new Error(`Timed out waiting for managed process ${this.id} after ${timeoutMs}ms`);
76
+ await new Promise((resolve) => setTimeout(resolve, 100));
77
+ }
78
+ }
79
+ }
80
+ export async function spawnManaged(command: string, args: string[] = [], options: SpawnManagedOptions = {}): Promise<ManagedProcess> {
81
+ const cwd = path.resolve(options.cwd || process.cwd());
82
+ const processesDir = options.processesDir || getProcessesDir(cwd);
83
+ const id = options.id || makeId(command);
84
+ const directory = processDirectory(id, processesDir);
85
+ fs.mkdirSync(processesDir, { recursive: true });
86
+ fs.mkdirSync(directory);
87
+ const configPath = path.join(directory, "launch.json");
88
+ fs.writeFileSync(configPath, JSON.stringify({ id, command, args, cwd, env: options.env || {}, background: options.background === true, parentProcessId: options.parentProcessId || null, parentPid: options.parentPid ?? process.pid, shell: options.shell === true, processesDir }, null, 2));
89
+ const cliPath = path.resolve(__dirname, "..", "cli.js");
90
+ const shim = spawn(process.execPath, [cliPath, "processes", "run", "--config", configPath], { cwd, detached: true, stdio: "ignore", env: process.env });
91
+ shim.unref();
92
+ try { await waitForFile(path.join(directory, "status.json"), 5000); }
93
+ catch (error) { try { fs.rmSync(directory, { recursive: true, force: true }); } catch {} throw error; }
94
+ return new ManagedProcess(id, directory);
95
+ }
@@ -0,0 +1,115 @@
1
+ import { spawn, spawnSync, ChildProcess } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { ManagedProcessStatus } from "./ProcessManager";
5
+
6
+ interface LaunchConfig {
7
+ id: string; command: string; args: string[]; cwd: string; env: NodeJS.ProcessEnv;
8
+ background: boolean; parentProcessId: string | null; parentPid: number | null;
9
+ shell: boolean; processesDir: string;
10
+ }
11
+
12
+ function atomicJson(file: string, value: unknown): void {
13
+ const tmp = `${file}.${process.pid}.tmp`;
14
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2));
15
+ fs.renameSync(tmp, file);
16
+ }
17
+ function alive(pid: number): boolean {
18
+ try { process.kill(pid, 0); return true; } catch (error: any) { return error?.code === "EPERM"; }
19
+ }
20
+ function terminateGroup(child: ChildProcess, signal: NodeJS.Signals): void {
21
+ if (!child.pid) return;
22
+ try { process.kill(-child.pid, signal); } catch { try { child.kill(signal); } catch {} }
23
+ }
24
+
25
+ export async function runManagedProcessShim(configPath: string): Promise<void> {
26
+ const config: LaunchConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
27
+ const dir = path.dirname(configPath);
28
+ const startedAt = new Date().toISOString();
29
+ const statusPath = path.join(dir, "status.json");
30
+ const stdoutPath = path.join(dir, "stdout");
31
+ const stderrPath = path.join(dir, "stderr");
32
+ const inputLogPath = path.join(dir, "input.log");
33
+ const fifoPath = path.join(dir, "stdin");
34
+ fs.closeSync(fs.openSync(stdoutPath, "a"));
35
+ fs.closeSync(fs.openSync(stderrPath, "a"));
36
+ fs.closeSync(fs.openSync(inputLogPath, "a"));
37
+
38
+ if (process.platform !== "win32") {
39
+ const result = spawnSync("mkfifo", [fifoPath]);
40
+ if (result.status !== 0 && !fs.existsSync(fifoPath)) throw new Error(`Unable to create stdin FIFO: ${result.stderr?.toString()}`);
41
+ } else {
42
+ // Windows has no POSIX FIFO. Keep the same path as an append-only inbox fallback.
43
+ fs.closeSync(fs.openSync(fifoPath, "a"));
44
+ }
45
+
46
+ let status: ManagedProcessStatus = {
47
+ id: config.id, pid: null, shimPid: process.pid, state: "starting", command: config.command,
48
+ args: config.args, cwd: config.cwd, parentProcessId: config.parentProcessId,
49
+ parentPid: config.parentPid, background: config.background, startedAt, endedAt: null,
50
+ exitCode: null, signal: null,
51
+ };
52
+ const writeStatus = () => atomicJson(statusPath, status);
53
+ atomicJson(path.join(dir, "metadata.json"), {
54
+ id: config.id, command: config.command, args: config.args, cwd: config.cwd,
55
+ parentProcessId: config.parentProcessId, parentPid: config.parentPid,
56
+ background: config.background, createdAt: startedAt, shimPid: process.pid,
57
+ });
58
+ fs.writeFileSync(path.join(dir, "pid"), "");
59
+ writeStatus();
60
+
61
+ const stdoutFd = fs.openSync(stdoutPath, "a");
62
+ const stderrFd = fs.openSync(stderrPath, "a");
63
+ const child = spawn(config.command, config.args, {
64
+ cwd: config.cwd, shell: config.shell, detached: process.platform !== "win32",
65
+ stdio: ["pipe", stdoutFd, stderrFd],
66
+ env: { ...process.env, ...config.env, KNOWHOW_MANAGED_PROCESS_ID: config.id, KNOWHOW_MANAGED_PROCESS_DIR: dir },
67
+ });
68
+ fs.closeSync(stdoutFd); fs.closeSync(stderrFd);
69
+ status = { ...status, pid: child.pid || null, state: "running" };
70
+ fs.writeFileSync(path.join(dir, "pid"), child.pid ? `${child.pid}\n` : "");
71
+ writeStatus();
72
+
73
+ let fifoFd: number | undefined;
74
+ let fifoStream: fs.ReadStream | undefined;
75
+ if (process.platform !== "win32") {
76
+ // O_RDWR prevents transient writers disconnecting from delivering EOF to the shim.
77
+ fifoFd = fs.openSync(fifoPath, fs.constants.O_RDWR);
78
+ fifoStream = fs.createReadStream("", { fd: fifoFd, autoClose: false });
79
+ fifoStream.on("data", (chunk) => {
80
+ fs.appendFileSync(inputLogPath, chunk);
81
+ if (child.stdin?.writable) child.stdin.write(chunk);
82
+ });
83
+ }
84
+
85
+ let stoppingForParent = false;
86
+ const parentTimer = !config.background && config.parentPid
87
+ ? setInterval(() => {
88
+ if (!alive(config.parentPid!)) {
89
+ stoppingForParent = true;
90
+ terminateGroup(child, "SIGTERM");
91
+ setTimeout(() => { if (alive(child.pid || -1)) terminateGroup(child, "SIGKILL"); }, 3000).unref();
92
+ }
93
+ }, 500)
94
+ : undefined;
95
+ parentTimer?.unref();
96
+
97
+ const forward = (signal: NodeJS.Signals) => terminateGroup(child, signal);
98
+ process.once("SIGINT", () => forward("SIGINT"));
99
+ process.once("SIGTERM", () => forward("SIGTERM"));
100
+ process.once("SIGHUP", () => forward("SIGHUP"));
101
+
102
+ await new Promise<void>((resolve) => {
103
+ child.once("error", (error) => {
104
+ status = { ...status, state: "failed", error: error.message, endedAt: new Date().toISOString() };
105
+ writeStatus(); resolve();
106
+ });
107
+ child.once("exit", (code, signal) => {
108
+ status = { ...status, state: stoppingForParent || signal ? "stopped" : code === 0 ? "exited" : "failed", endedAt: new Date().toISOString(), exitCode: code, signal };
109
+ writeStatus(); resolve();
110
+ });
111
+ });
112
+ if (parentTimer) clearInterval(parentTimer);
113
+ fifoStream?.destroy();
114
+ if (fifoFd !== undefined) try { fs.closeSync(fifoFd); } catch {}
115
+ }
@@ -3,8 +3,10 @@ import { IAgent } from "../agents/interface";
3
3
  import { EventService } from "./EventService";
4
4
  import { ToolsService } from "./Tools";
5
5
  import { ConfigAgent } from "../agents/configurable/ConfigAgent";
6
+ import { TraceAll } from "../util/Trace";
6
7
  import { AgentContext } from "../agents/base/base";
7
8
 
9
+ @TraceAll()
8
10
  export class AgentService {
9
11
  private agents: Map<string, IAgent> = new Map();
10
12
  private agentContext: AgentContext | null = null;