@tyvm/knowhow 0.0.146 → 0.0.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/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 +24 -0
  5. package/src/agents/historyRollback.ts +36 -0
  6. package/src/agents/tools/startAgentTask.ts +41 -9
  7. package/src/auth/browserLogin.ts +9 -3
  8. package/src/auth/keyAuth.ts +115 -0
  9. package/src/auth/keyManager.ts +166 -0
  10. package/src/chat/modules/AgentModule.ts +114 -33
  11. package/src/chat/modules/AskModule.ts +2 -0
  12. package/src/chat/modules/BaseChatModule.ts +0 -1
  13. package/src/chat/modules/CustomCommandsModule.ts +2 -0
  14. package/src/chat/modules/InternalChatModule.ts +2 -0
  15. package/src/chat/modules/RemoteSyncModule.ts +4 -0
  16. package/src/chat/modules/RendererModule.ts +2 -0
  17. package/src/chat/modules/SearchModule.ts +2 -0
  18. package/src/chat/modules/SessionsModule.ts +103 -34
  19. package/src/chat/modules/SetupModule.ts +2 -0
  20. package/src/chat/modules/ShellCommandModule.ts +2 -0
  21. package/src/chat/modules/SystemModule.ts +2 -0
  22. package/src/chat/types.ts +2 -0
  23. package/src/cli.ts +1 -1
  24. package/src/clients/index.ts +2 -0
  25. package/src/commands/agent.ts +1 -38
  26. package/src/commands/agents.ts +403 -53
  27. package/src/commands/misc.ts +3 -2
  28. package/src/login.ts +89 -28
  29. package/src/services/AgentService.ts +2 -0
  30. package/src/services/AgentSyncFs.ts +65 -0
  31. package/src/services/AgentSyncKnowhowWeb.ts +22 -4
  32. package/src/services/BehaviorsService.ts +2 -0
  33. package/src/services/DockerService.ts +2 -0
  34. package/src/services/EmbeddingService.ts +2 -0
  35. package/src/services/EmbeddingsService.ts +3 -0
  36. package/src/services/KnowhowClient.ts +68 -7
  37. package/src/services/LazyToolsService.ts +9 -0
  38. package/src/services/Mcp.ts +2 -0
  39. package/src/services/McpServer.ts +2 -0
  40. package/src/services/MessageProcessor.ts +2 -0
  41. package/src/services/MinimalToolsService.ts +2 -0
  42. package/src/services/SessionManager.ts +5 -0
  43. package/src/services/SkillsService.ts +2 -0
  44. package/src/services/SyncedAgentWatcher.ts +2 -0
  45. package/src/services/SyncerService.ts +23 -8
  46. package/src/services/TaskRegistry.ts +2 -0
  47. package/src/services/Tools.ts +2 -0
  48. package/src/services/index.ts +2 -0
  49. package/src/services/modules/index.ts +4 -1
  50. package/src/types.ts +2 -0
  51. package/src/util/Trace.ts +1 -1
  52. package/src/utils/http.ts +18 -2
  53. package/tests/unit/agents/historyRollback.test.ts +53 -0
  54. package/tests/unit/auth/keyAuth.test.ts +102 -0
  55. package/tests/unit/auth/keyManager.test.ts +67 -0
  56. package/tests/unit/chat/historyCommandArgs.test.ts +37 -0
  57. package/tests/unit/commands/agents.test.ts +68 -0
  58. package/tests/unit/login.test.ts +119 -0
  59. package/tests/unit/services/AgentSyncKnowhowWeb.test.ts +38 -0
  60. package/tests/unit/services/LazyToolsService.test.ts +29 -0
  61. package/tests/unit/services/SessionManager.test.ts +43 -0
  62. package/tests/unit/services/SyncerService.test.ts +45 -0
  63. package/tests/unit/util/Trace.test.ts +119 -0
  64. package/tests/unit/utils/http.test.ts +43 -0
  65. package/ts_build/package.json +1 -1
  66. package/ts_build/src/agents/base/base.d.ts +1 -0
  67. package/ts_build/src/agents/base/base.js +31 -2
  68. package/ts_build/src/agents/base/base.js.map +1 -1
  69. package/ts_build/src/agents/historyRollback.d.ts +2 -0
  70. package/ts_build/src/agents/historyRollback.js +22 -0
  71. package/ts_build/src/agents/historyRollback.js.map +1 -0
  72. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -0
  73. package/ts_build/src/agents/tools/startAgentTask.js +36 -9
  74. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  75. package/ts_build/src/auth/browserLogin.d.ts +1 -0
  76. package/ts_build/src/auth/browserLogin.js +5 -2
  77. package/ts_build/src/auth/browserLogin.js.map +1 -1
  78. package/ts_build/src/auth/keyAuth.d.ts +5 -0
  79. package/ts_build/src/auth/keyAuth.js +79 -0
  80. package/ts_build/src/auth/keyAuth.js.map +1 -0
  81. package/ts_build/src/auth/keyManager.d.ts +14 -0
  82. package/ts_build/src/auth/keyManager.js +145 -0
  83. package/ts_build/src/auth/keyManager.js.map +1 -0
  84. package/ts_build/src/chat/modules/AgentModule.d.ts +13 -1
  85. package/ts_build/src/chat/modules/AgentModule.js +83 -24
  86. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  87. package/ts_build/src/chat/modules/AskModule.js +12 -2
  88. package/ts_build/src/chat/modules/AskModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/BaseChatModule.js.map +1 -1
  90. package/ts_build/src/chat/modules/CustomCommandsModule.js +12 -2
  91. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  92. package/ts_build/src/chat/modules/InternalChatModule.js +16 -2
  93. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  94. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -2
  95. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  96. package/ts_build/src/chat/modules/RendererModule.js +17 -2
  97. package/ts_build/src/chat/modules/RendererModule.js.map +1 -1
  98. package/ts_build/src/chat/modules/SearchModule.js +12 -2
  99. package/ts_build/src/chat/modules/SearchModule.js.map +1 -1
  100. package/ts_build/src/chat/modules/SessionsModule.d.ts +6 -0
  101. package/ts_build/src/chat/modules/SessionsModule.js +113 -18
  102. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  103. package/ts_build/src/chat/modules/SetupModule.js +17 -2
  104. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  105. package/ts_build/src/chat/modules/ShellCommandModule.js +12 -2
  106. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -1
  107. package/ts_build/src/chat/modules/SystemModule.js +12 -2
  108. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  109. package/ts_build/src/chat/types.d.ts +2 -0
  110. package/ts_build/src/cli.js +1 -1
  111. package/ts_build/src/cli.js.map +1 -1
  112. package/ts_build/src/clients/index.js +19 -4
  113. package/ts_build/src/clients/index.js.map +1 -1
  114. package/ts_build/src/commands/agent.js +1 -21
  115. package/ts_build/src/commands/agent.js.map +1 -1
  116. package/ts_build/src/commands/agents.d.ts +1 -1
  117. package/ts_build/src/commands/agents.js +174 -16
  118. package/ts_build/src/commands/agents.js.map +1 -1
  119. package/ts_build/src/commands/misc.js +3 -2
  120. package/ts_build/src/commands/misc.js.map +1 -1
  121. package/ts_build/src/login.d.ts +1 -1
  122. package/ts_build/src/login.js +53 -15
  123. package/ts_build/src/login.js.map +1 -1
  124. package/ts_build/src/services/AgentService.js +18 -2
  125. package/ts_build/src/services/AgentService.js.map +1 -1
  126. package/ts_build/src/services/AgentSyncFs.d.ts +3 -0
  127. package/ts_build/src/services/AgentSyncFs.js +62 -15
  128. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  129. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +2 -0
  130. package/ts_build/src/services/AgentSyncKnowhowWeb.js +28 -6
  131. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
  132. package/ts_build/src/services/BehaviorsService.js +12 -2
  133. package/ts_build/src/services/BehaviorsService.js.map +1 -1
  134. package/ts_build/src/services/DockerService.js +20 -8
  135. package/ts_build/src/services/DockerService.js.map +1 -1
  136. package/ts_build/src/services/EmbeddingService.js +16 -2
  137. package/ts_build/src/services/EmbeddingService.js.map +1 -1
  138. package/ts_build/src/services/EmbeddingsService.js +12 -2
  139. package/ts_build/src/services/EmbeddingsService.js.map +1 -1
  140. package/ts_build/src/services/KnowhowClient.d.ts +7 -2
  141. package/ts_build/src/services/KnowhowClient.js +108 -10
  142. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  143. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  144. package/ts_build/src/services/LazyToolsService.js +21 -2
  145. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  146. package/ts_build/src/services/Mcp.js +12 -2
  147. package/ts_build/src/services/Mcp.js.map +1 -1
  148. package/ts_build/src/services/McpServer.js +17 -2
  149. package/ts_build/src/services/McpServer.js.map +1 -1
  150. package/ts_build/src/services/MessageProcessor.js +16 -2
  151. package/ts_build/src/services/MessageProcessor.js.map +1 -1
  152. package/ts_build/src/services/MinimalToolsService.js +16 -2
  153. package/ts_build/src/services/MinimalToolsService.js.map +1 -1
  154. package/ts_build/src/services/SessionManager.js +19 -2
  155. package/ts_build/src/services/SessionManager.js.map +1 -1
  156. package/ts_build/src/services/SkillsService.js +16 -2
  157. package/ts_build/src/services/SkillsService.js.map +1 -1
  158. package/ts_build/src/services/SyncedAgentWatcher.js +16 -2
  159. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  160. package/ts_build/src/services/SyncerService.d.ts +1 -0
  161. package/ts_build/src/services/SyncerService.js +27 -5
  162. package/ts_build/src/services/SyncerService.js.map +1 -1
  163. package/ts_build/src/services/TaskRegistry.js +12 -2
  164. package/ts_build/src/services/TaskRegistry.js.map +1 -1
  165. package/ts_build/src/services/Tools.js +16 -2
  166. package/ts_build/src/services/Tools.js.map +1 -1
  167. package/ts_build/src/services/index.d.ts +2 -0
  168. package/ts_build/src/services/index.js +4 -2
  169. package/ts_build/src/services/index.js.map +1 -1
  170. package/ts_build/src/services/modules/index.d.ts +2 -1
  171. package/ts_build/src/services/modules/index.js +1 -1
  172. package/ts_build/src/services/modules/index.js.map +1 -1
  173. package/ts_build/src/types.d.ts +1 -0
  174. package/ts_build/src/types.js.map +1 -1
  175. package/ts_build/src/util/Trace.d.ts +1 -3
  176. package/ts_build/src/utils/http.d.ts +6 -2
  177. package/ts_build/src/utils/http.js +8 -2
  178. package/ts_build/src/utils/http.js.map +1 -1
  179. package/ts_build/tests/unit/agents/historyRollback.test.d.ts +1 -0
  180. package/ts_build/tests/unit/agents/historyRollback.test.js +42 -0
  181. package/ts_build/tests/unit/agents/historyRollback.test.js.map +1 -0
  182. package/ts_build/tests/unit/auth/keyAuth.test.d.ts +1 -0
  183. package/ts_build/tests/unit/auth/keyAuth.test.js +77 -0
  184. package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -0
  185. package/ts_build/tests/unit/auth/keyManager.test.d.ts +1 -0
  186. package/ts_build/tests/unit/auth/keyManager.test.js +52 -0
  187. package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -0
  188. package/ts_build/tests/unit/chat/historyCommandArgs.test.d.ts +1 -0
  189. package/ts_build/tests/unit/chat/historyCommandArgs.test.js +29 -0
  190. package/ts_build/tests/unit/chat/historyCommandArgs.test.js.map +1 -0
  191. package/ts_build/tests/unit/commands/agents.test.d.ts +1 -0
  192. package/ts_build/tests/unit/commands/agents.test.js +91 -0
  193. package/ts_build/tests/unit/commands/agents.test.js.map +1 -0
  194. package/ts_build/tests/unit/login.test.d.ts +1 -0
  195. package/ts_build/tests/unit/login.test.js +97 -0
  196. package/ts_build/tests/unit/login.test.js.map +1 -0
  197. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.d.ts +1 -0
  198. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js +32 -0
  199. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js.map +1 -0
  200. package/ts_build/tests/unit/services/LazyToolsService.test.d.ts +1 -0
  201. package/ts_build/tests/unit/services/LazyToolsService.test.js +27 -0
  202. package/ts_build/tests/unit/services/LazyToolsService.test.js.map +1 -0
  203. package/ts_build/tests/unit/services/SessionManager.test.d.ts +1 -0
  204. package/ts_build/tests/unit/services/SessionManager.test.js +72 -0
  205. package/ts_build/tests/unit/services/SessionManager.test.js.map +1 -0
  206. package/ts_build/tests/unit/services/SyncerService.test.d.ts +1 -0
  207. package/ts_build/tests/unit/services/SyncerService.test.js +40 -0
  208. package/ts_build/tests/unit/services/SyncerService.test.js.map +1 -0
  209. package/ts_build/tests/unit/util/Trace.test.d.ts +1 -0
  210. package/ts_build/tests/unit/util/Trace.test.js +107 -0
  211. package/ts_build/tests/unit/util/Trace.test.js.map +1 -0
  212. package/ts_build/tests/unit/utils/http.test.d.ts +1 -0
  213. package/ts_build/tests/unit/utils/http.test.js +69 -0
  214. 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,7 +21,16 @@
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");
@@ -70,8 +81,11 @@ function elapsedStr(startIso: string | undefined, endIso?: string): string {
70
81
  const end = endIso ? new Date(endIso).getTime() : Date.now();
71
82
  const ms = end - start;
72
83
  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`;
84
+ if (ms < 3_600_000)
85
+ return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`;
86
+ return `${Math.floor(ms / 3_600_000)}h ${Math.floor(
87
+ (ms % 3_600_000) / 60_000
88
+ )}m`;
75
89
  }
76
90
 
77
91
  function shortId(taskId: string, maxLen = 52): string {
@@ -103,7 +117,11 @@ function lastAssistantMessage(thread: any[]): string | undefined {
103
117
  function lastToolCall(thread: any[]): string | undefined {
104
118
  for (let i = thread.length - 1; i >= 0; i--) {
105
119
  const msg = thread[i];
106
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
120
+ if (
121
+ msg.role === "assistant" &&
122
+ Array.isArray(msg.tool_calls) &&
123
+ msg.tool_calls.length > 0
124
+ ) {
107
125
  const last = msg.tool_calls[msg.tool_calls.length - 1];
108
126
  return last?.function?.name;
109
127
  }
@@ -126,25 +144,40 @@ function formatMessage(msg: any, index: number): string {
126
144
  .join(" | ");
127
145
  }
128
146
 
129
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
147
+ if (
148
+ msg.role === "assistant" &&
149
+ Array.isArray(msg.tool_calls) &&
150
+ msg.tool_calls.length > 0
151
+ ) {
130
152
  const tools = msg.tool_calls.map((tc: any) => {
131
153
  const name = tc?.function?.name ?? "?";
132
154
  let args = "";
133
155
  try {
134
156
  const parsed = JSON.parse(tc?.function?.arguments ?? "{}");
135
157
  const entries = Object.entries(parsed).slice(0, 2);
136
- args = entries.map(([k, v]) => `${k}=${String(v).slice(0, 40)}`).join(", ");
158
+ args = entries
159
+ .map(([k, v]) => `${k}=${String(v).slice(0, 40)}`)
160
+ .join(", ");
137
161
  } catch {
138
162
  args = (tc?.function?.arguments ?? "").slice(0, 80);
139
163
  }
140
164
  return `[TOOL] ${name}(${args})`;
141
165
  });
142
- content = content ? content + "\n " + tools.join("\n ") : tools.join("\n ");
166
+ content = content
167
+ ? content + "\n " + tools.join("\n ")
168
+ : tools.join("\n ");
143
169
  }
144
170
 
145
171
  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 ? "…" : ""}`;
172
+ const result =
173
+ typeof msg.content === "string"
174
+ ? msg.content.slice(0, 200)
175
+ : JSON.stringify(msg.content).slice(0, 200);
176
+ content = `[RESULT] ${result}${
177
+ (typeof msg.content === "string" ? msg.content : "").length > 200
178
+ ? "…"
179
+ : ""
180
+ }`;
148
181
  }
149
182
 
150
183
  return ` #${String(index).padStart(3)} [${role}] ${content}`;
@@ -155,8 +188,8 @@ function formatMessage(msg: any, index: number): string {
155
188
  // ---------------------------------------------------------------------------
156
189
 
157
190
  type Row = {
158
- taskId: string; // full task ID (not truncated)
159
- displayId: string; // truncated for display
191
+ taskId: string; // full task ID (not truncated)
192
+ displayId: string; // truncated for display
160
193
  agentName: string;
161
194
  status: string;
162
195
  cost: string;
@@ -182,9 +215,17 @@ function buildRows(includeAll: boolean): Row[] {
182
215
  }
183
216
  }
184
217
 
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() : "?";
218
+ const cost =
219
+ meta?.totalCostUsd != null
220
+ ? `$${(meta.totalCostUsd as number).toFixed(3)}`
221
+ : "";
222
+ const elapsed = elapsedStr(
223
+ meta?.startTime,
224
+ meta?.status === "completed" ? meta?.lastUpdate : undefined
225
+ );
226
+ const lastUpdate = meta?.lastUpdate
227
+ ? new Date(meta.lastUpdate).toLocaleString()
228
+ : "?";
188
229
 
189
230
  rows.push({
190
231
  taskId,
@@ -221,7 +262,6 @@ function resolveTaskId(value: string, includeAll = false): string | null {
221
262
  return all.find((id) => id.includes(value)) ?? null;
222
263
  }
223
264
 
224
-
225
265
  // ---------------------------------------------------------------------------
226
266
  // Command handlers
227
267
  // ---------------------------------------------------------------------------
@@ -233,15 +273,30 @@ async function cmdList(opts: { all: boolean; json: boolean }): Promise<void> {
233
273
  if (agentTaskIds().length === 0) {
234
274
  console.log("No agent task directories found under", AGENTS_DIR);
235
275
  } else {
236
- console.log("No active agent tasks. Use --all to include completed tasks.");
276
+ console.log(
277
+ "No active agent tasks. Use --all to include completed tasks."
278
+ );
237
279
  }
238
280
  return;
239
281
  }
240
282
 
241
283
  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));
284
+ console.log(
285
+ JSON.stringify(
286
+ rows.map(
287
+ ({ taskId, agentName, status, cost, elapsed, lastUpdate }) => ({
288
+ taskId,
289
+ agentName,
290
+ status,
291
+ cost,
292
+ elapsed,
293
+ lastUpdate,
294
+ })
295
+ ),
296
+ null,
297
+ 2
298
+ )
299
+ );
245
300
  return;
246
301
  }
247
302
 
@@ -277,12 +332,15 @@ async function cmdList(opts: { all: boolean; json: boolean }): Promise<void> {
277
332
  "\n"
278
333
  );
279
334
  });
280
- console.log("\nTip: use -i <#> with tail/status/attach to select by row number, e.g. knowhow agents attach -i 1\n");
335
+ console.log(
336
+ "\nTip: use -i <#> with tail/status/attach to select by row number, e.g. knowhow agents attach -i 1\n"
337
+ );
281
338
  }
282
339
 
283
-
284
-
285
- async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: string }): Promise<void> {
340
+ async function cmdStatus(
341
+ taskIdOrIndex: string | undefined,
342
+ opts: { index?: string }
343
+ ): Promise<void> {
286
344
  const value = opts.index ?? taskIdOrIndex;
287
345
  if (!value) {
288
346
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -312,9 +370,23 @@ async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: stri
312
370
  console.log(`Task ID: ${resolvedId}`);
313
371
  console.log(`Agent: ${meta?.agentName ?? "?"}`);
314
372
  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() : " ?"}`);
373
+ console.log(
374
+ `Cost: ${
375
+ meta?.totalCostUsd != null
376
+ ? "$" + (meta.totalCostUsd as number).toFixed(4)
377
+ : "?"
378
+ }`
379
+ );
380
+ console.log(
381
+ `Started: ${
382
+ meta?.startTime ? new Date(meta.startTime).toLocaleString() : "?"
383
+ }`
384
+ );
385
+ console.log(
386
+ `Last update:${
387
+ meta?.lastUpdate ? " " + new Date(meta.lastUpdate).toLocaleString() : " ?"
388
+ }`
389
+ );
318
390
  console.log(`Elapsed: ${elapsedStr(meta?.startTime)}`);
319
391
 
320
392
  const threads: any[][] = meta?.threads ?? [];
@@ -342,7 +414,10 @@ async function cmdStatus(taskIdOrIndex: string | undefined, opts: { index?: stri
342
414
  console.log("─".repeat(60));
343
415
  }
344
416
 
345
- async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number; raw: boolean; follow: boolean; index?: string }): Promise<void> {
417
+ async function cmdTail(
418
+ taskIdOrIndex: string | undefined,
419
+ opts: { count: number; raw: boolean; follow: boolean; index?: string }
420
+ ): Promise<void> {
346
421
  const value = opts.index ?? taskIdOrIndex;
347
422
  if (!value) {
348
423
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -373,7 +448,9 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
373
448
 
374
449
  const threads: any[][] = meta.threads ?? [];
375
450
  if (threads.length === 0) {
376
- console.log("No thread data found in metadata.json (agent may not have started yet).");
451
+ console.log(
452
+ "No thread data found in metadata.json (agent may not have started yet)."
453
+ );
377
454
  return;
378
455
  }
379
456
 
@@ -389,7 +466,11 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
389
466
  const startIndex = lastThread.length - slice.length;
390
467
 
391
468
  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}`);
469
+ console.log(
470
+ ` Agent: ${meta.agentName} | Status: ${
471
+ meta.status ?? loadStatus(resolvedId)
472
+ } | Total messages: ${lastThread.length}`
473
+ );
393
474
  console.log("─".repeat(80));
394
475
 
395
476
  for (let i = 0; i < slice.length; i++) {
@@ -398,12 +479,20 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
398
479
 
399
480
  console.log("─".repeat(80));
400
481
 
401
- const cost = meta.totalCostUsd != null ? `$${(meta.totalCostUsd as number).toFixed(4)}` : "?";
482
+ const cost =
483
+ meta.totalCostUsd != null
484
+ ? `$${(meta.totalCostUsd as number).toFixed(4)}`
485
+ : "?";
402
486
  const elapsed = elapsedStr(meta.startTime);
403
487
  const status = getStatus(resolvedId, meta);
404
- console.log(`Cost: ${cost} | Elapsed: ${elapsed} | Last update: ${meta.lastUpdate ? new Date(meta.lastUpdate).toLocaleString() : "?"}`);
488
+ console.log(
489
+ `Cost: ${cost} | Elapsed: ${elapsed} | Last update: ${
490
+ meta.lastUpdate ? new Date(meta.lastUpdate).toLocaleString() : "?"
491
+ }`
492
+ );
405
493
 
406
- const isDone = status === "completed" || status === "failed" || status === "killed";
494
+ const isDone =
495
+ status === "completed" || status === "failed" || status === "killed";
407
496
 
408
497
  if (opts.follow) {
409
498
  if (isDone) {
@@ -423,13 +512,20 @@ async function cmdTail(taskIdOrIndex: string | undefined, opts: { count: number;
423
512
  * Poll metadata.json every second, printing any new messages as they arrive.
424
513
  * Exits when the agent status becomes completed/failed/killed.
425
514
  */
426
- async function followTask(resolvedId: string, seenCount: number): Promise<void> {
515
+ async function followTask(
516
+ resolvedId: string,
517
+ seenCount: number
518
+ ): Promise<void> {
427
519
  console.log(`\n👁 Following agent output… (Ctrl+C to stop)\n`);
428
520
 
429
521
  // Check upfront — if already done, exit immediately
430
522
  const initialMeta = loadMeta(resolvedId);
431
523
  const initialStatus = getStatus(resolvedId, initialMeta);
432
- if (initialStatus === "completed" || initialStatus === "failed" || initialStatus === "killed") {
524
+ if (
525
+ initialStatus === "completed" ||
526
+ initialStatus === "failed" ||
527
+ initialStatus === "killed"
528
+ ) {
433
529
  console.log(`✅ Agent already ${initialStatus}. Nothing to follow.\n`);
434
530
  return;
435
531
  }
@@ -452,9 +548,19 @@ async function followTask(resolvedId: string, seenCount: number): Promise<void>
452
548
  }
453
549
  const status = getStatus(resolvedId, meta);
454
550
  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)}`);
551
+ const cost =
552
+ meta.totalCostUsd != null
553
+ ? `$${(meta.totalCostUsd as number).toFixed(4)}`
554
+ : "?";
555
+ console.log(
556
+ `\n─────────────────────────────────────────────────────────────────────────────────`
557
+ );
558
+ console.log(
559
+ `✅ Agent ${status}. Cost: ${cost} | Elapsed: ${elapsedStr(
560
+ meta.startTime,
561
+ meta.lastUpdate
562
+ )}`
563
+ );
458
564
  clearInterval(interval);
459
565
  process.exit(0);
460
566
  }
@@ -464,7 +570,10 @@ async function followTask(resolvedId: string, seenCount: number): Promise<void>
464
570
  await new Promise<void>(() => {});
465
571
  }
466
572
 
467
- async function cmdAttach(taskIdOrIndex: string | undefined, opts: { index?: string }): Promise<void> {
573
+ async function cmdAttach(
574
+ taskIdOrIndex: string | undefined,
575
+ opts: { index?: string }
576
+ ): Promise<void> {
468
577
  const value = opts.index ?? taskIdOrIndex;
469
578
  if (!value) {
470
579
  console.error("Provide a task ID or use -i <number> to select by index.");
@@ -551,28 +660,189 @@ async function cmdAnswer(
551
660
  console.log(`\n📭 No final answer available for task: ${resolvedId}`);
552
661
  console.log(` Status: ${status}`);
553
662
  if (status !== "completed") {
554
- console.log(` The agent hasn't finished yet — attach with 'knowhow agents attach' to follow it.`);
663
+ console.log(
664
+ ` The agent hasn't finished yet — attach with 'knowhow agents attach' to follow it.`
665
+ );
555
666
  }
556
667
  return;
557
668
  }
558
669
 
559
670
  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) : "?"}`);
671
+ console.log(
672
+ ` Agent: ${meta.agentName ?? "?"} | Status: ${status} | Cost: ${
673
+ meta.totalCostUsd != null
674
+ ? "$" + (meta.totalCostUsd as number).toFixed(4)
675
+ : "?"
676
+ }`
677
+ );
561
678
  console.log("─".repeat(80));
562
679
  console.log(answer);
563
680
  console.log("─".repeat(80) + "\n");
564
681
  }
565
682
 
683
+ interface HistoryRunOptions {
684
+ rollback: string;
685
+ input?: string;
686
+ taskId?: string;
687
+ messageId?: string;
688
+ syncFs?: boolean;
689
+ provider?: string;
690
+ model?: string;
691
+ agentName?: string;
692
+ reasoningEffort?: string;
693
+ renderer?: string;
694
+ index?: string;
695
+ headless?: boolean;
696
+ }
566
697
 
698
+ async function runFromHistory(
699
+ sourceTaskId: string,
700
+ options: HistoryRunOptions,
701
+ fork: boolean,
702
+ getChatService: () => any
703
+ ): Promise<void> {
704
+ const rollback = Number(options.rollback);
705
+ if (!Number.isInteger(rollback) || rollback < 0) {
706
+ throw new Error("--rollback must be a non-negative integer");
707
+ }
708
+ const hasHeadlessOverrides = !!(
709
+ options.input || options.messageId || options.syncFs || options.provider ||
710
+ options.model || options.agentName || options.reasoningEffort ||
711
+ options.renderer || options.taskId
712
+ );
713
+ if (process.stdin.isTTY && !options.headless && !hasHeadlessOverrides) {
714
+ const command = fork ? "/fork" : "/resume";
715
+ const { setupServices } = await import("./services");
716
+ await setupServices();
717
+ await startChat(`${command} ${sourceTaskId} --rollback ${rollback}`);
718
+ return;
719
+ }
720
+ const requestedReasoningEffortValue = options.reasoningEffort;
721
+ if (
722
+ requestedReasoningEffortValue !== undefined &&
723
+ !isReasoningEffort(requestedReasoningEffortValue)
724
+ ) {
725
+ throw new Error(
726
+ `Invalid reasoning effort "${requestedReasoningEffortValue}". Expected one of: ${REASONING_EFFORTS.join(
727
+ ", "
728
+ )}.`
729
+ );
730
+ }
731
+ const requestedReasoningEffort =
732
+ requestedReasoningEffortValue as CompletionOptions["reasoning_effort"];
733
+
734
+ const { setupServices } = await import("./services");
735
+ await setupServices();
736
+ const chatService = getChatService();
737
+ let config: any = {};
738
+ try {
739
+ config = await getConfig();
740
+ } catch (_) {}
741
+ const rendererSpecifier =
742
+ options.renderer ?? config.chat?.renderer ?? "basic";
743
+ const resolvedRenderer =
744
+ rendererSpecifier === "basic" && !process.stdout.isTTY
745
+ ? "plain"
746
+ : rendererSpecifier;
747
+ try {
748
+ chatService.setContext({ renderer: await loadRenderer(resolvedRenderer) });
749
+ } catch (err: any) {
750
+ console.warn(
751
+ `⚠ Could not load renderer "${resolvedRenderer}": ${err.message}`
752
+ );
753
+ chatService.setContext({
754
+ renderer: !process.stdout.isTTY
755
+ ? new PlainRenderer()
756
+ : await loadRenderer("basic"),
757
+ });
758
+ }
759
+
760
+ const agentModule = new AgentModule();
761
+ const threads = await agentModule.loadThreadsForTask(
762
+ sourceTaskId,
763
+ options.messageId
764
+ );
765
+ const savedSettings = await agentModule.loadTaskSettings(
766
+ sourceTaskId,
767
+ options.messageId
768
+ );
769
+ await agentModule.initialize(chatService);
770
+ const { taskCompleted } = await agentModule.resumeFromMessages({
771
+ agentName: options.agentName || savedSettings.agentName || "Patcher",
772
+ input: options.input || "Please continue from where you left off.",
773
+ threads,
774
+ messageId: fork ? undefined : options.messageId,
775
+ taskId: fork ? options.taskId : sourceTaskId,
776
+ // sourceTaskId may itself be a remote UUID when there is no local metadata;
777
+ // AgentModule validates it and rejects filesystem slugs.
778
+ remoteTaskId: fork ? undefined : savedSettings.remoteTaskId || sourceTaskId,
779
+ syncFs: options.syncFs,
780
+ rollback,
781
+ fork,
782
+ model: options.model || savedSettings.model,
783
+ provider: options.provider || savedSettings.provider,
784
+ reasoningEffort: requestedReasoningEffort || savedSettings.reasoningEffort,
785
+ summarizeReasoning: savedSettings.summarizeReasoning,
786
+ enabledTools: savedSettings.enabledTools,
787
+ });
788
+ await taskCompleted;
789
+ }
790
+
791
+ function addHistoryRunOptions(
792
+ command: Command,
793
+ includeDestinationTaskId: boolean
794
+ ): Command {
795
+ command
796
+ .option(
797
+ "-i, --index <number>",
798
+ "Select task by row number from 'agents list'"
799
+ )
800
+ .option(
801
+ "--rollback <count>",
802
+ "Discard the newest N agent interactions before continuing",
803
+ "0"
804
+ )
805
+ .option("--input <text>", "Message to send after restoring history")
806
+ .option(
807
+ "--message-id <messageId>",
808
+ "Knowhow message ID for remote task lookup"
809
+ )
810
+ .option("--sync-fs", "Enable filesystem-based synchronization")
811
+ .option("--provider <provider>", "Override the saved AI provider")
812
+ .option("--model <model>", "Override the saved model")
813
+ .option("--agent-name <name>", "Override the saved agent")
814
+ .option(
815
+ "--reasoning-effort <effort>",
816
+ `Reasoning effort: ${REASONING_EFFORTS.join(", ")}`
817
+ )
818
+ .option(
819
+ "--renderer <name>",
820
+ "Renderer to use (default: from config or basic)"
821
+ )
822
+ .option(
823
+ "--headless",
824
+ "Run to completion without opening the attached chat interface"
825
+ );
826
+ if (includeDestinationTaskId) {
827
+ command.option(
828
+ "--task-id <taskId>",
829
+ "Explicit task ID for the newly forked task"
830
+ );
831
+ }
832
+ return command;
833
+ }
567
834
 
568
835
  // ---------------------------------------------------------------------------
569
836
  // Command registration
570
837
  // ---------------------------------------------------------------------------
571
838
 
572
- export function addAgentsCommand(program: Command): void {
839
+ export function addAgentsCommand(
840
+ program: Command,
841
+ getChatService: () => any
842
+ ): void {
573
843
  const agents = program
574
844
  .command("agents")
575
- .description("Inspect running and recent agent tasks");
845
+ .description("Manage running, recent, and historical agent tasks");
576
846
 
577
847
  agents
578
848
  .command("list")
@@ -592,7 +862,10 @@ export function addAgentsCommand(program: Command): void {
592
862
  "Show a summary for a specific agent task: status, last tool, cost, elapsed. " +
593
863
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
594
864
  )
595
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
865
+ .option(
866
+ "-i, --index <number>",
867
+ "Select task by row number from 'agents list'"
868
+ )
596
869
  .action(async (taskId: string | undefined, opts: { index?: string }) => {
597
870
  await cmdStatus(taskId, opts);
598
871
  });
@@ -604,13 +877,26 @@ export function addAgentsCommand(program: Command): void {
604
877
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'. " +
605
878
  "Use -f to follow live output."
606
879
  )
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)
880
+ .option(
881
+ "-i, --index <number>",
882
+ "Select task by row number from 'agents list'"
883
+ )
884
+ .option(
885
+ "-n, --count <number>",
886
+ "Number of messages to show",
887
+ (v) => parseInt(v, 10),
888
+ 20
889
+ )
609
890
  .option("--raw", "Output raw JSON of messages", false)
610
891
  .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
- });
892
+ .action(
893
+ async (
894
+ taskId: string | undefined,
895
+ opts: { count: number; raw: boolean; follow: boolean; index?: string }
896
+ ) => {
897
+ await cmdTail(taskId, opts);
898
+ }
899
+ );
614
900
 
615
901
  agents
616
902
  .command("attach [taskId]")
@@ -619,7 +905,10 @@ export function addAgentsCommand(program: Command): void {
619
905
  "Shows recent history then streams new messages as they arrive. " +
620
906
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
621
907
  )
622
- .option("-i, --index <number>", "Select task by row number from 'agents list'")
908
+ .option(
909
+ "-i, --index <number>",
910
+ "Select task by row number from 'agents list'"
911
+ )
623
912
  .action(async (taskId: string | undefined, opts: { index?: string }) => {
624
913
  await cmdAttach(taskId, opts);
625
914
  });
@@ -631,9 +920,70 @@ export function addAgentsCommand(program: Command): void {
631
920
  "Reads metadata.result, falling back to the last assistant message. " +
632
921
  "Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
633
922
  )
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
- });
923
+ .option(
924
+ "-i, --index <number>",
925
+ "Select task by row number from 'agents list'"
926
+ )
927
+ .option(
928
+ "--raw",
929
+ "Print only the raw answer text (no header/formatting)",
930
+ false
931
+ )
932
+ .action(
933
+ async (
934
+ taskId: string | undefined,
935
+ opts: { index?: string; raw: boolean }
936
+ ) => {
937
+ await cmdAnswer(taskId, opts);
938
+ }
939
+ );
940
+ addHistoryRunOptions(
941
+ agents
942
+ .command("resume [taskId]")
943
+ .description(
944
+ "Resume an existing agent task. Accepts a task ID, partial substring, or -i <number> for row index from 'agents list'."
945
+ ),
946
+ false
947
+ ).action(async (taskId: string | undefined, opts: HistoryRunOptions) => {
948
+ try {
949
+ const value = opts.index ?? taskId;
950
+ if (!value) {
951
+ throw new Error("Provide a task ID or use -i <number> to select by index.");
952
+ }
953
+ const resolvedId = resolveTaskId(value);
954
+ if (!resolvedId) {
955
+ throw new Error(`Task not found: ${value}`);
956
+ }
957
+ await runFromHistory(resolvedId, opts, false, getChatService);
958
+ } catch (error) {
959
+ console.error("Error resuming agent:", error);
960
+ process.exitCode = 1;
961
+ }
962
+ });
963
+
964
+ addHistoryRunOptions(
965
+ agents
966
+ .command("fork [sourceTaskId]")
967
+ .description(
968
+ "Fork an existing agent task into a new task. Accepts a task ID, partial substring, or -i <number> for row index from 'agents list'."
969
+ ),
970
+ true
971
+ ).action(async (sourceTaskId: string | undefined, opts: HistoryRunOptions) => {
972
+ try {
973
+ const value = opts.index ?? sourceTaskId;
974
+ if (!value) {
975
+ throw new Error(
976
+ "Provide a task ID or use -i <number> to select by index."
977
+ );
978
+ }
979
+ const resolvedId = resolveTaskId(value);
980
+ if (!resolvedId) {
981
+ throw new Error(`Task not found: ${value}`);
982
+ }
983
+ await runFromHistory(resolvedId, opts, true, getChatService);
984
+ } catch (error) {
985
+ console.error("Error forking agent:", error);
986
+ process.exitCode = 1;
987
+ }
988
+ });
639
989
  }
@@ -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