@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
@@ -6,6 +6,8 @@
6
6
  * knowhow agents status <taskId> — one-line summary (status, last tool, cost, elapsed)
7
7
  * knowhow agents tail <taskId> — print last N messages; without -f behaves like read-only attach
8
8
  * knowhow agents attach <taskId> — open the full knowhow chat interface attached to the agent
9
+ * knowhow agents resume <taskId> — resume a task, optionally rolling back interactions
10
+ * knowhow agents fork <taskId> — branch a task's history into a new task
9
11
  *
10
12
  * Both `status` and `tail` accept:
11
13
  * - A full task ID
@@ -19,11 +21,32 @@
19
21
  import { Command } from "commander";
20
22
  import * as fs from "fs";
21
23
  import * as path from "path";
24
+ import { AgentModule } from "../chat/modules/AgentModule";
22
25
  import { startChat } from "../chat";
26
+ import { PlainRenderer } from "../chat/renderer/PlainRenderer";
27
+ import { loadRenderer } from "../chat/renderer/loadRenderer";
28
+ import {
29
+ CompletionOptions,
30
+ isReasoningEffort,
31
+ REASONING_EFFORTS,
32
+ } from "../clients/types";
33
+ import { getConfig } from "../config";
23
34
 
24
35
  const AGENTS_DIR = path.join(".knowhow", "processes", "agents");
25
36
  const SESSIONS_DIR = path.join(".knowhow", "chats", "sessions");
26
37
 
38
+ const TERMINAL_STATUSES = new Set([
39
+ "completed",
40
+ "failed",
41
+ "killed",
42
+ "error",
43
+ "done",
44
+ ]);
45
+ const ACTIVE_STATUSES = new Set(["running", "paused"]);
46
+ // Older task records did not persist a pid. After this grace period, report
47
+ // them as unverifiable rather than continuing to claim they are running.
48
+ const UNVERIFIED_TASK_GRACE_MS = 5 * 60 * 1000;
49
+
27
50
  // ---------------------------------------------------------------------------
28
51
  // Helpers
29
52
  // ---------------------------------------------------------------------------
@@ -57,11 +80,42 @@ function loadStatus(taskId: string): string {
57
80
  }
58
81
  }
59
82
 
60
- /** Get the canonical status — meta.status is live/preferred; status.txt is fallback */
61
- function getStatus(taskId: string, meta: any | null): string {
62
- if (meta?.status) return meta.status;
63
- const fromFile = loadStatus(taskId);
64
- return fromFile ?? "unknown";
83
+ function pidAlive(pid: number): boolean {
84
+ try {
85
+ process.kill(pid, 0);
86
+ return true;
87
+ } catch (error: any) {
88
+ // EPERM means the process exists but cannot be signalled by this user.
89
+ return error?.code === "EPERM";
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get the effective status, reconciling persisted state with process liveness.
95
+ * An agent can be terminated before AgentSyncFs gets a chance to finalize its
96
+ * files; those records must not remain "running" forever.
97
+ */
98
+ export function getStatus(taskId: string, meta: any | null): string {
99
+ const persisted = meta?.status || loadStatus(taskId) || "unknown";
100
+ if (TERMINAL_STATUSES.has(persisted)) return persisted;
101
+ if (meta?.inProgress === false) return "completed";
102
+ if (!ACTIVE_STATUSES.has(persisted)) return persisted;
103
+
104
+ const pid = meta?.pid ?? meta?.processId;
105
+ if (typeof pid === "number") {
106
+ return pidAlive(pid) ? persisted : "failed";
107
+ }
108
+
109
+ const lastKnownTime = meta?.lastUpdate ?? meta?.startTime;
110
+ const lastKnownMs = lastKnownTime ? new Date(lastKnownTime).getTime() : NaN;
111
+ if (
112
+ !Number.isFinite(lastKnownMs) ||
113
+ Date.now() - lastKnownMs > UNVERIFIED_TASK_GRACE_MS
114
+ ) {
115
+ return "stale";
116
+ }
117
+
118
+ return persisted;
65
119
  }
66
120
 
67
121
  function elapsedStr(startIso: string | undefined, endIso?: string): string {
@@ -70,8 +124,11 @@ function elapsedStr(startIso: string | undefined, endIso?: string): string {
70
124
  const end = endIso ? new Date(endIso).getTime() : Date.now();
71
125
  const ms = end - start;
72
126
  if (ms < 60_000) return `${Math.floor(ms / 1000)}s`;
73
- if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`;
74
- return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`;
127
+ if (ms < 3_600_000)
128
+ return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`;
129
+ return `${Math.floor(ms / 3_600_000)}h ${Math.floor(
130
+ (ms % 3_600_000) / 60_000
131
+ )}m`;
75
132
  }
76
133
 
77
134
  function shortId(taskId: string, maxLen = 52): string {
@@ -103,7 +160,11 @@ function lastAssistantMessage(thread: any[]): string | undefined {
103
160
  function lastToolCall(thread: any[]): string | undefined {
104
161
  for (let i = thread.length - 1; i >= 0; i--) {
105
162
  const msg = thread[i];
106
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
163
+ if (
164
+ msg.role === "assistant" &&
165
+ Array.isArray(msg.tool_calls) &&
166
+ msg.tool_calls.length > 0
167
+ ) {
107
168
  const last = msg.tool_calls[msg.tool_calls.length - 1];
108
169
  return last?.function?.name;
109
170
  }
@@ -126,25 +187,40 @@ function formatMessage(msg: any, index: number): string {
126
187
  .join(" | ");
127
188
  }
128
189
 
129
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
190
+ if (
191
+ msg.role === "assistant" &&
192
+ Array.isArray(msg.tool_calls) &&
193
+ msg.tool_calls.length > 0
194
+ ) {
130
195
  const tools = msg.tool_calls.map((tc: any) => {
131
196
  const name = tc?.function?.name ?? "?";
132
197
  let args = "";
133
198
  try {
134
199
  const parsed = JSON.parse(tc?.function?.arguments ?? "{}");
135
200
  const entries = Object.entries(parsed).slice(0, 2);
136
- args = entries.map(([k, v]) => `${k}=${String(v).slice(0, 40)}`).join(", ");
201
+ args = entries
202
+ .map(([k, v]) => `${k}=${String(v).slice(0, 40)}`)
203
+ .join(", ");
137
204
  } catch {
138
205
  args = (tc?.function?.arguments ?? "").slice(0, 80);
139
206
  }
140
207
  return `[TOOL] ${name}(${args})`;
141
208
  });
142
- content = content ? content + "\n " + tools.join("\n ") : tools.join("\n ");
209
+ content = content
210
+ ? content + "\n " + tools.join("\n ")
211
+ : tools.join("\n ");
143
212
  }
144
213
 
145
214
  if (msg.role === "tool") {
146
- const result = typeof msg.content === "string" ? msg.content.slice(0, 200) : JSON.stringify(msg.content).slice(0, 200);
147
- content = `[RESULT] ${result}${(typeof msg.content === "string" ? msg.content : "").length > 200 ? "…" : ""}`;
215
+ const result =
216
+ typeof msg.content === "string"
217
+ ? msg.content.slice(0, 200)
218
+ : JSON.stringify(msg.content).slice(0, 200);
219
+ content = `[RESULT] ${result}${
220
+ (typeof msg.content === "string" ? msg.content : "").length > 200
221
+ ? "…"
222
+ : ""
223
+ }`;
148
224
  }
149
225
 
150
226
  return ` #${String(index).padStart(3)} [${role}] ${content}`;
@@ -155,8 +231,8 @@ function formatMessage(msg: any, index: number): string {
155
231
  // ---------------------------------------------------------------------------
156
232
 
157
233
  type Row = {
158
- taskId: string; // full task ID (not truncated)
159
- displayId: string; // truncated for display
234
+ taskId: string; // full task ID (not truncated)
235
+ displayId: string; // truncated for display
160
236
  agentName: string;
161
237
  status: string;
162
238
  cost: string;
@@ -170,9 +246,9 @@ function buildRows(includeAll: boolean): Row[] {
170
246
 
171
247
  for (const taskId of taskIds) {
172
248
  const meta = loadMeta(taskId);
173
- const status = meta?.status ?? loadStatus(taskId);
249
+ const status = getStatus(taskId, meta);
174
250
 
175
- if (!includeAll && (status === "completed" || status === "killed")) {
251
+ if (!includeAll && (TERMINAL_STATUSES.has(status) || status === "stale")) {
176
252
  const lastUpdate = meta?.lastUpdate ?? "";
177
253
  if (lastUpdate) {
178
254
  const age = Date.now() - new Date(lastUpdate).getTime();
@@ -182,9 +258,19 @@ function buildRows(includeAll: boolean): Row[] {
182
258
  }
183
259
  }
184
260
 
185
- const cost = meta?.totalCostUsd != null ? `$${(meta.totalCostUsd as number).toFixed(3)}` : "";
186
- const elapsed = elapsedStr(meta?.startTime, meta?.status === "completed" ? meta?.lastUpdate : undefined);
187
- const lastUpdate = meta?.lastUpdate ? new Date(meta.lastUpdate).toLocaleString() : "?";
261
+ const cost =
262
+ meta?.totalCostUsd != null
263
+ ? `$${(meta.totalCostUsd as number).toFixed(3)}`
264
+ : "";
265
+ const elapsed = elapsedStr(
266
+ meta?.startTime,
267
+ TERMINAL_STATUSES.has(status) || status === "stale"
268
+ ? meta?.lastUpdate
269
+ : undefined
270
+ );
271
+ const lastUpdate = meta?.lastUpdate
272
+ ? new Date(meta.lastUpdate).toLocaleString()
273
+ : "?";
188
274
 
189
275
  rows.push({
190
276
  taskId,
@@ -221,7 +307,6 @@ function resolveTaskId(value: string, includeAll = false): string | null {
221
307
  return all.find((id) => id.includes(value)) ?? null;
222
308
  }
223
309
 
224
-
225
310
  // ---------------------------------------------------------------------------
226
311
  // Command handlers
227
312
  // ---------------------------------------------------------------------------
@@ -233,15 +318,30 @@ async function cmdList(opts: { all: boolean; json: boolean }): Promise<void> {
233
318
  if (agentTaskIds().length === 0) {
234
319
  console.log("No agent task directories found under", AGENTS_DIR);
235
320
  } else {
236
- console.log("No active agent tasks. Use --all to include completed tasks.");
321
+ console.log(
322
+ "No active agent tasks. Use --all to include completed tasks."
323
+ );
237
324
  }
238
325
  return;
239
326
  }
240
327
 
241
328
  if (opts.json) {
242
- console.log(JSON.stringify(rows.map(({ taskId, agentName, status, cost, elapsed, lastUpdate }) => ({
243
- taskId, agentName, status, cost, elapsed, lastUpdate,
244
- })), null, 2));
329
+ console.log(
330
+ JSON.stringify(
331
+ rows.map(
332
+ ({ taskId, agentName, status, cost, elapsed, lastUpdate }) => ({
333
+ taskId,
334
+ agentName,
335
+ status,
336
+ cost,
337
+ elapsed,
338
+ lastUpdate,
339
+ })
340
+ ),
341
+ null,
342
+ 2
343
+ )
344
+ );
245
345
  return;
246
346
  }
247
347
 
@@ -277,12 +377,15 @@ async function cmdList(opts: { all: boolean; json: boolean }): Promise<void> {
277
377
  "\n"
278
378
  );
279
379
  });
280
- console.log("\nTip: use -i <#> with tail/status/attach to select by row number, e.g. knowhow agents attach -i 1\n");
380
+ console.log(
381
+ "\nTip: use -i <#> with tail/status/attach to select by row number, e.g. knowhow agents attach -i 1\n"
382
+ );
281
383
  }
282
384
 
283
-
284
-
285
- async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: string }): Promise<void> {
385
+ async function cmdStatus(
386
+ taskIdOrIndex: string | undefined,
387
+ opts: { index?: string }
388
+ ): Promise<void> {
286
389
  const value = opts.index ?? taskIdOrIndex;
287
390
  if (!value) {
288
391
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -312,9 +415,23 @@ async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: stri
312
415
  console.log(`Task ID: ${resolvedId}`);
313
416
  console.log(`Agent: ${meta?.agentName ?? "?"}`);
314
417
  console.log(`Status: ${status}`);
315
- console.log(`Cost: ${meta?.totalCostUsd != null ? "$" + (meta.totalCostUsd as number).toFixed(4) : "?"}`);
316
- console.log(`Started: ${meta?.startTime ? new Date(meta.startTime).toLocaleString() : "?"}`);
317
- console.log(`Last update:${meta?.lastUpdate ? " " + new Date(meta.lastUpdate).toLocaleString() : " ?"}`);
418
+ console.log(
419
+ `Cost: ${
420
+ meta?.totalCostUsd != null
421
+ ? "$" + (meta.totalCostUsd as number).toFixed(4)
422
+ : "?"
423
+ }`
424
+ );
425
+ console.log(
426
+ `Started: ${
427
+ meta?.startTime ? new Date(meta.startTime).toLocaleString() : "?"
428
+ }`
429
+ );
430
+ console.log(
431
+ `Last update:${
432
+ meta?.lastUpdate ? " " + new Date(meta.lastUpdate).toLocaleString() : " ?"
433
+ }`
434
+ );
318
435
  console.log(`Elapsed: ${elapsedStr(meta?.startTime)}`);
319
436
 
320
437
  const threads: any[][] = meta?.threads ?? [];
@@ -342,7 +459,10 @@ async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: stri
342
459
  console.log("─".repeat(60));
343
460
  }
344
461
 
345
- async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number; raw: boolean; follow: boolean; index?: string }): Promise<void> {
462
+ async function cmdTail(
463
+ taskIdOrIndex: string | undefined,
464
+ opts: { count: number; raw: boolean; follow: boolean; index?: string }
465
+ ): Promise<void> {
346
466
  const value = opts.index ?? taskIdOrIndex;
347
467
  if (!value) {
348
468
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -373,7 +493,9 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
373
493
 
374
494
  const threads: any[][] = meta.threads ?? [];
375
495
  if (threads.length === 0) {
376
- console.log("No thread data found in metadata.json (agent may not have started yet).");
496
+ console.log(
497
+ "No thread data found in metadata.json (agent may not have started yet)."
498
+ );
377
499
  return;
378
500
  }
379
501
 
@@ -389,7 +511,11 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
389
511
  const startIndex = lastThread.length - slice.length;
390
512
 
391
513
  console.log(`\n📜 Last ${slice.length} messages for task: ${resolvedId}`);
392
- console.log(` Agent: ${meta.agentName} | Status: ${meta.status ?? loadStatus(resolvedId)} | Total messages: ${lastThread.length}`);
514
+ console.log(
515
+ ` Agent: ${meta.agentName} | Status: ${
516
+ meta.status ?? loadStatus(resolvedId)
517
+ } | Total messages: ${lastThread.length}`
518
+ );
393
519
  console.log("─".repeat(80));
394
520
 
395
521
  for (let i = 0; i < slice.length; i++) {
@@ -398,12 +524,20 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
398
524
 
399
525
  console.log("─".repeat(80));
400
526
 
401
- const cost = meta.totalCostUsd != null ? `$${(meta.totalCostUsd as number).toFixed(4)}` : "?";
527
+ const cost =
528
+ meta.totalCostUsd != null
529
+ ? `$${(meta.totalCostUsd as number).toFixed(4)}`
530
+ : "?";
402
531
  const elapsed = elapsedStr(meta.startTime);
403
532
  const status = getStatus(resolvedId, meta);
404
- console.log(`Cost: ${cost} | Elapsed: ${elapsed} | Last update: ${meta.lastUpdate ? new Date(meta.lastUpdate).toLocaleString() : "?"}`);
533
+ console.log(
534
+ `Cost: ${cost} | Elapsed: ${elapsed} | Last update: ${
535
+ meta.lastUpdate ? new Date(meta.lastUpdate).toLocaleString() : "?"
536
+ }`
537
+ );
405
538
 
406
- const isDone = status === "completed" || status === "failed" || status === "killed";
539
+ const isDone =
540
+ status === "completed" || status === "failed" || status === "killed";
407
541
 
408
542
  if (opts.follow) {
409
543
  if (isDone) {
@@ -423,13 +557,20 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
423
557
  * Poll metadata.json every second, printing any new messages as they arrive.
424
558
  * Exits when the agent status becomes completed/failed/killed.
425
559
  */
426
- async function followTask(resolvedId: string, seenCount: number): Promise<void> {
560
+ async function followTask(
561
+ resolvedId: string,
562
+ seenCount: number
563
+ ): Promise<void> {
427
564
  console.log(`\n👁 Following agent output… (Ctrl+C to stop)\n`);
428
565
 
429
566
  // Check upfront — if already done, exit immediately
430
567
  const initialMeta = loadMeta(resolvedId);
431
568
  const initialStatus = getStatus(resolvedId, initialMeta);
432
- if (initialStatus === "completed" || initialStatus === "failed" || initialStatus === "killed") {
569
+ if (
570
+ initialStatus === "completed" ||
571
+ initialStatus === "failed" ||
572
+ initialStatus === "killed"
573
+ ) {
433
574
  console.log(`✅ Agent already ${initialStatus}. Nothing to follow.\n`);
434
575
  return;
435
576
  }
@@ -452,9 +593,19 @@ async function followTask(resolvedId: string, seenCount: number): Promise<void>
452
593
  }
453
594
  const status = getStatus(resolvedId, meta);
454
595
  if (status === "completed" || status === "failed" || status === "killed") {
455
- const cost = meta.totalCostUsd != null ? `$${(meta.totalCostUsd as number).toFixed(4)}` : "?";
456
- console.log(`\n─────────────────────────────────────────────────────────────────────────────────`);
457
- console.log(`✅ Agent ${status}. Cost: ${cost} | Elapsed: ${elapsedStr(meta.startTime, meta.lastUpdate)}`);
596
+ const cost =
597
+ meta.totalCostUsd != null
598
+ ? `$${(meta.totalCostUsd as number).toFixed(4)}`
599
+ : "?";
600
+ console.log(
601
+ `\n─────────────────────────────────────────────────────────────────────────────────`
602
+ );
603
+ console.log(
604
+ `✅ Agent ${status}. Cost: ${cost} | Elapsed: ${elapsedStr(
605
+ meta.startTime,
606
+ meta.lastUpdate
607
+ )}`
608
+ );
458
609
  clearInterval(interval);
459
610
  process.exit(0);
460
611
  }
@@ -464,7 +615,10 @@ async function followTask(resolvedId: string, seenCount: number): Promise<void>
464
615
  await new Promise<void>(() => {});
465
616
  }
466
617
 
467
- async function cmdAttach(taskIdOrIndex: string | undefined, opts: { index?: string }): Promise<void> {
618
+ async function cmdAttach(
619
+ taskIdOrIndex: string | undefined,
620
+ opts: { index?: string }
621
+ ): Promise<void> {
468
622
  const value = opts.index ?? taskIdOrIndex;
469
623
  if (!value) {
470
624
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -551,28 +705,189 @@ async function cmdAnswer(
551
705
  console.log(`\n📭 No final answer available for task: ${resolvedId}`);
552
706
  console.log(` Status: ${status}`);
553
707
  if (status !== "completed") {
554
- console.log(` The agent hasn't finished yet — attach with 'knowhow agents attach' to follow it.`);
708
+ console.log(
709
+ ` The agent hasn't finished yet — attach with 'knowhow agents attach' to follow it.`
710
+ );
555
711
  }
556
712
  return;
557
713
  }
558
714
 
559
715
  console.log(`\n💬 Final answer for task: ${resolvedId}`);
560
- console.log(` Agent: ${meta.agentName ?? "?"} | Status: ${status} | Cost: ${meta.totalCostUsd != null ? "$" + (meta.totalCostUsd as number).toFixed(4) : "?"}`);
716
+ console.log(
717
+ ` Agent: ${meta.agentName ?? "?"} | Status: ${status} | Cost: ${
718
+ meta.totalCostUsd != null
719
+ ? "$" + (meta.totalCostUsd as number).toFixed(4)
720
+ : "?"
721
+ }`
722
+ );
561
723
  console.log("─".repeat(80));
562
724
  console.log(answer);
563
725
  console.log("─".repeat(80) + "\n");
564
726
  }
565
727
 
728
+ interface HistoryRunOptions {
729
+ rollback: string;
730
+ input?: string;
731
+ taskId?: string;
732
+ messageId?: string;
733
+ syncFs?: boolean;
734
+ provider?: string;
735
+ model?: string;
736
+ agentName?: string;
737
+ reasoningEffort?: string;
738
+ renderer?: string;
739
+ index?: string;
740
+ headless?: boolean;
741
+ }
566
742
 
743
+ async function runFromHistory(
744
+ sourceTaskId: string,
745
+ options: HistoryRunOptions,
746
+ fork: boolean,
747
+ getChatService: () => any
748
+ ): Promise<void> {
749
+ const rollback = Number(options.rollback);
750
+ if (!Number.isInteger(rollback) || rollback < 0) {
751
+ throw new Error("--rollback must be a non-negative integer");
752
+ }
753
+ const hasHeadlessOverrides = !!(
754
+ options.input || options.messageId || options.syncFs || options.provider ||
755
+ options.model || options.agentName || options.reasoningEffort ||
756
+ options.renderer || options.taskId
757
+ );
758
+ if (process.stdin.isTTY && !options.headless && !hasHeadlessOverrides) {
759
+ const command = fork ? "/fork" : "/resume";
760
+ const { setupServices } = await import("./services");
761
+ await setupServices();
762
+ await startChat(`${command} ${sourceTaskId} --rollback ${rollback}`);
763
+ return;
764
+ }
765
+ const requestedReasoningEffortValue = options.reasoningEffort;
766
+ if (
767
+ requestedReasoningEffortValue !== undefined &&
768
+ !isReasoningEffort(requestedReasoningEffortValue)
769
+ ) {
770
+ throw new Error(
771
+ `Invalid reasoning effort "${requestedReasoningEffortValue}". Expected one of: ${REASONING_EFFORTS.join(
772
+ ", "
773
+ )}.`
774
+ );
775
+ }
776
+ const requestedReasoningEffort =
777
+ requestedReasoningEffortValue as CompletionOptions["reasoning_effort"];
778
+
779
+ const { setupServices } = await import("./services");
780
+ await setupServices();
781
+ const chatService = getChatService();
782
+ let config: any = {};
783
+ try {
784
+ config = await getConfig();
785
+ } catch (_) {}
786
+ const rendererSpecifier =
787
+ options.renderer ?? config.chat?.renderer ?? "basic";
788
+ const resolvedRenderer =
789
+ rendererSpecifier === "basic" && !process.stdout.isTTY
790
+ ? "plain"
791
+ : rendererSpecifier;
792
+ try {
793
+ chatService.setContext({ renderer: await loadRenderer(resolvedRenderer) });
794
+ } catch (err: any) {
795
+ console.warn(
796
+ `⚠ Could not load renderer "${resolvedRenderer}": ${err.message}`
797
+ );
798
+ chatService.setContext({
799
+ renderer: !process.stdout.isTTY
800
+ ? new PlainRenderer()
801
+ : await loadRenderer("basic"),
802
+ });
803
+ }
804
+
805
+ const agentModule = new AgentModule();
806
+ const threads = await agentModule.loadThreadsForTask(
807
+ sourceTaskId,
808
+ options.messageId
809
+ );
810
+ const savedSettings = await agentModule.loadTaskSettings(
811
+ sourceTaskId,
812
+ options.messageId
813
+ );
814
+ await agentModule.initialize(chatService);
815
+ const { taskCompleted } = await agentModule.resumeFromMessages({
816
+ agentName: options.agentName || savedSettings.agentName || "Patcher",
817
+ input: options.input || "Please continue from where you left off.",
818
+ threads,
819
+ messageId: fork ? undefined : options.messageId,
820
+ taskId: fork ? options.taskId : sourceTaskId,
821
+ // sourceTaskId may itself be a remote UUID when there is no local metadata;
822
+ // AgentModule validates it and rejects filesystem slugs.
823
+ remoteTaskId: fork ? undefined : savedSettings.remoteTaskId || sourceTaskId,
824
+ syncFs: options.syncFs,
825
+ rollback,
826
+ fork,
827
+ model: options.model || savedSettings.model,
828
+ provider: options.provider || savedSettings.provider,
829
+ reasoningEffort: requestedReasoningEffort || savedSettings.reasoningEffort,
830
+ summarizeReasoning: savedSettings.summarizeReasoning,
831
+ enabledTools: savedSettings.enabledTools,
832
+ });
833
+ await taskCompleted;
834
+ }
835
+
836
+ function addHistoryRunOptions(
837
+ command: Command,
838
+ includeDestinationTaskId: boolean
839
+ ): Command {
840
+ command
841
+ .option(
842
+ "-i, --index <number>",
843
+ "Select task by row number from 'agents list'"
844
+ )
845
+ .option(
846
+ "--rollback <count>",
847
+ "Discard the newest N agent interactions before continuing",
848
+ "0"
849
+ )
850
+ .option("--input <text>", "Message to send after restoring history")
851
+ .option(
852
+ "--message-id <messageId>",
853
+ "Knowhow message ID for remote task lookup"
854
+ )
855
+ .option("--sync-fs", "Enable filesystem-based synchronization")
856
+ .option("--provider <provider>", "Override the saved AI provider")
857
+ .option("--model <model>", "Override the saved model")
858
+ .option("--agent-name <name>", "Override the saved agent")
859
+ .option(
860
+ "--reasoning-effort <effort>",
861
+ `Reasoning effort: ${REASONING_EFFORTS.join(", ")}`
862
+ )
863
+ .option(
864
+ "--renderer <name>",
865
+ "Renderer to use (default: from config or basic)"
866
+ )
867
+ .option(
868
+ "--headless",
869
+ "Run to completion without opening the attached chat interface"
870
+ );
871
+ if (includeDestinationTaskId) {
872
+ command.option(
873
+ "--task-id <taskId>",
874
+ "Explicit task ID for the newly forked task"
875
+ );
876
+ }
877
+ return command;
878
+ }
567
879
 
568
880
  // ---------------------------------------------------------------------------
569
881
  // Command registration
570
882
  // ---------------------------------------------------------------------------
571
883
 
572
- export function addAgentsCommand(program: Command): void {
884
+ export function addAgentsCommand(
885
+ program: Command,
886
+ getChatService: () => any
887
+ ): void {
573
888
  const agents = program
574
889
  .command("agents")
575
- .description("Inspect running and recent agent tasks");
890
+ .description("Manage running, recent, and historical agent tasks");
576
891
 
577
892
  agents
578
893
  .command("list")
@@ -592,7 +907,10 @@ export function addAgentsCommand(program: Command): void {
592
907
  "Show a summary for a specific agent task: status, last tool, cost, elapsed. " +
593
908
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
594
909
  )
595
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
910
+ .option(
911
+ "-i, --index <number>",
912
+ "Select task by row number from 'agents list'"
913
+ )
596
914
  .action(async (taskId: string | undefined, opts: { index?: string }) => {
597
915
  await cmdStatus(taskId, opts);
598
916
  });
@@ -604,13 +922,26 @@ export function addAgentsCommand(program: Command): void {
604
922
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'. " +
605
923
  "Use -f to follow live output."
606
924
  )
607
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
608
- .option("-n, --count <number>", "Number of messages to show", (v) => parseInt(v, 10), 20)
925
+ .option(
926
+ "-i, --index <number>",
927
+ "Select task by row number from 'agents list'"
928
+ )
929
+ .option(
930
+ "-n, --count <number>",
931
+ "Number of messages to show",
932
+ (v) => parseInt(v, 10),
933
+ 20
934
+ )
609
935
  .option("--raw", "Output raw JSON of messages", false)
610
936
  .option("-f, --follow", "Follow live output (poll for new messages)", false)
611
- .action(async (taskId: string | undefined, opts: { count: number; raw: boolean; follow: boolean; index?: string }) => {
612
- await cmdTail(taskId, opts);
613
- });
937
+ .action(
938
+ async (
939
+ taskId: string | undefined,
940
+ opts: { count: number; raw: boolean; follow: boolean; index?: string }
941
+ ) => {
942
+ await cmdTail(taskId, opts);
943
+ }
944
+ );
614
945
 
615
946
  agents
616
947
  .command("attach [taskId]")
@@ -619,7 +950,10 @@ export function addAgentsCommand(program: Command): void {
619
950
  "Shows recent history then streams new messages as they arrive. " +
620
951
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
621
952
  )
622
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
953
+ .option(
954
+ "-i, --index <number>",
955
+ "Select task by row number from 'agents list'"
956
+ )
623
957
  .action(async (taskId: string | undefined, opts: { index?: string }) => {
624
958
  await cmdAttach(taskId, opts);
625
959
  });
@@ -631,9 +965,70 @@ export function addAgentsCommand(program: Command): void {
631
965
  "Reads metadata.result, falling back to the last assistant message. " +
632
966
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
633
967
  )
634
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
635
- .option("--raw", "Print only the raw answer text (no header/formatting)", false)
636
- .action(async (taskId: string | undefined, opts: { index?: string; raw: boolean }) => {
637
- await cmdAnswer(taskId, opts);
638
- });
968
+ .option(
969
+ "-i, --index <number>",
970
+ "Select task by row number from 'agents list'"
971
+ )
972
+ .option(
973
+ "--raw",
974
+ "Print only the raw answer text (no header/formatting)",
975
+ false
976
+ )
977
+ .action(
978
+ async (
979
+ taskId: string | undefined,
980
+ opts: { index?: string; raw: boolean }
981
+ ) => {
982
+ await cmdAnswer(taskId, opts);
983
+ }
984
+ );
985
+ addHistoryRunOptions(
986
+ agents
987
+ .command("resume [taskId]")
988
+ .description(
989
+ "Resume an existing agent task. Accepts a task ID, partial substring, or -i <number> for row index from 'agents list'."
990
+ ),
991
+ false
992
+ ).action(async (taskId: string | undefined, opts: HistoryRunOptions) => {
993
+ try {
994
+ const value = opts.index ?? taskId;
995
+ if (!value) {
996
+ throw new Error("Provide a task ID or use -i <number> to select by index.");
997
+ }
998
+ const resolvedId = resolveTaskId(value);
999
+ if (!resolvedId) {
1000
+ throw new Error(`Task not found: ${value}`);
1001
+ }
1002
+ await runFromHistory(resolvedId, opts, false, getChatService);
1003
+ } catch (error) {
1004
+ console.error("Error resuming agent:", error);
1005
+ process.exitCode = 1;
1006
+ }
1007
+ });
1008
+
1009
+ addHistoryRunOptions(
1010
+ agents
1011
+ .command("fork [sourceTaskId]")
1012
+ .description(
1013
+ "Fork an existing agent task into a new task. Accepts a task ID, partial substring, or -i <number> for row index from 'agents list'."
1014
+ ),
1015
+ true
1016
+ ).action(async (sourceTaskId: string | undefined, opts: HistoryRunOptions) => {
1017
+ try {
1018
+ const value = opts.index ?? sourceTaskId;
1019
+ if (!value) {
1020
+ throw new Error(
1021
+ "Provide a task ID or use -i <number> to select by index."
1022
+ );
1023
+ }
1024
+ const resolvedId = resolveTaskId(value);
1025
+ if (!resolvedId) {
1026
+ throw new Error(`Task not found: ${value}`);
1027
+ }
1028
+ await runFromHistory(resolvedId, opts, true, getChatService);
1029
+ } catch (error) {
1030
+ console.error("Error forking agent:", error);
1031
+ process.exitCode = 1;
1032
+ }
1033
+ });
639
1034
  }