@tyvm/knowhow 0.0.83 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/package.json +4 -2
  2. package/src/agents/base/base.ts +72 -62
  3. package/src/agents/index.ts +30 -14
  4. package/src/agents/tools/startAgentTask.ts +3 -1
  5. package/src/chat/CliChatService.ts +20 -4
  6. package/src/chat/modules/AgentModule.ts +399 -357
  7. package/src/chat/modules/CustomCommandsModule.ts +0 -1
  8. package/src/chat/modules/InternalChatModule.ts +18 -2
  9. package/src/chat/modules/RendererModule.ts +109 -0
  10. package/src/chat/modules/SessionsModule.ts +854 -0
  11. package/src/chat/modules/SetupModule.ts +6 -8
  12. package/src/chat/modules/index.ts +1 -0
  13. package/src/chat/renderer/CompactRenderer.ts +209 -0
  14. package/src/chat/renderer/ConsoleRenderer.ts +141 -0
  15. package/src/chat/renderer/FancyRenderer.ts +421 -0
  16. package/src/chat/renderer/index.ts +5 -0
  17. package/src/chat/renderer/loadRenderer.ts +314 -0
  18. package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
  19. package/src/chat/renderer/types.ts +88 -0
  20. package/src/chat/types.ts +5 -0
  21. package/src/chat.ts +69 -5
  22. package/src/cli.ts +24 -5
  23. package/src/config.ts +15 -0
  24. package/src/plugins/AgentsMdPlugin.ts +1 -1
  25. package/src/plugins/GitPlugin.ts +20 -20
  26. package/src/plugins/PluginBase.ts +11 -0
  27. package/src/plugins/SkillsPlugin.ts +150 -0
  28. package/src/plugins/asana.ts +4 -4
  29. package/src/plugins/embedding.ts +3 -5
  30. package/src/plugins/exec.ts +3 -3
  31. package/src/plugins/figma.ts +3 -7
  32. package/src/plugins/github.ts +18 -29
  33. package/src/plugins/jira.ts +2 -2
  34. package/src/plugins/language.ts +4 -4
  35. package/src/plugins/linear.ts +4 -4
  36. package/src/plugins/notion.ts +6 -8
  37. package/src/plugins/plugins.ts +29 -3
  38. package/src/plugins/url.ts +2 -2
  39. package/src/plugins/vim.ts +4 -3
  40. package/src/services/AgentService.ts +17 -0
  41. package/src/services/AgentSyncFs.ts +3 -0
  42. package/src/services/EventService.ts +168 -27
  43. package/src/services/KnowhowClient.ts +1 -0
  44. package/src/services/SessionManager.ts +51 -1
  45. package/src/services/SyncedAgentWatcher.ts +397 -0
  46. package/src/services/SyncerService.ts +147 -0
  47. package/src/services/index.ts +2 -0
  48. package/src/services/modules/index.ts +14 -3
  49. package/src/types.ts +25 -0
  50. package/src/worker.ts +80 -2
  51. package/src/workers/auth/PasskeySetup.ts +185 -0
  52. package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
  53. package/src/workers/auth/types.ts +58 -0
  54. package/src/workers/tools/getChallenge.ts +33 -0
  55. package/src/workers/tools/index.ts +8 -0
  56. package/src/workers/tools/lock.ts +31 -0
  57. package/src/workers/tools/unlock.ts +116 -0
  58. package/tests/unit/modules/moduleLoading.test.ts +226 -0
  59. package/tests/unit/plugins/pluginLoading.test.ts +151 -0
  60. package/ts_build/package.json +4 -2
  61. package/ts_build/src/agents/base/base.d.ts +4 -3
  62. package/ts_build/src/agents/base/base.js +54 -30
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/index.d.ts +3 -0
  65. package/ts_build/src/agents/index.js +21 -11
  66. package/ts_build/src/agents/index.js.map +1 -1
  67. package/ts_build/src/agents/tools/startAgentTask.js +2 -1
  68. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  69. package/ts_build/src/chat/CliChatService.js +16 -5
  70. package/ts_build/src/chat/CliChatService.js.map +1 -1
  71. package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
  72. package/ts_build/src/chat/modules/AgentModule.js +248 -258
  73. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  74. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  75. package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
  76. package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
  77. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  78. package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
  79. package/ts_build/src/chat/modules/RendererModule.js +76 -0
  80. package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
  81. package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
  82. package/ts_build/src/chat/modules/SessionsModule.js +582 -0
  83. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
  84. package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
  85. package/ts_build/src/chat/modules/SetupModule.js +4 -6
  86. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  87. package/ts_build/src/chat/modules/index.d.ts +1 -0
  88. package/ts_build/src/chat/modules/index.js +3 -1
  89. package/ts_build/src/chat/modules/index.js.map +1 -1
  90. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
  91. package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
  92. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
  93. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
  94. package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
  95. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
  96. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
  97. package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
  98. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
  99. package/ts_build/src/chat/renderer/index.d.ts +5 -0
  100. package/ts_build/src/chat/renderer/index.js +29 -0
  101. package/ts_build/src/chat/renderer/index.js.map +1 -0
  102. package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
  103. package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
  104. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
  105. package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
  106. package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
  107. package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
  108. package/ts_build/src/chat/renderer/types.d.ts +75 -0
  109. package/ts_build/src/chat/renderer/types.js +3 -0
  110. package/ts_build/src/chat/renderer/types.js.map +1 -0
  111. package/ts_build/src/chat/types.d.ts +5 -0
  112. package/ts_build/src/chat.js +46 -4
  113. package/ts_build/src/chat.js.map +1 -1
  114. package/ts_build/src/cli.js +18 -5
  115. package/ts_build/src/cli.js.map +1 -1
  116. package/ts_build/src/config.d.ts +1 -0
  117. package/ts_build/src/config.js +17 -1
  118. package/ts_build/src/config.js.map +1 -1
  119. package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
  120. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
  121. package/ts_build/src/plugins/GitPlugin.js +20 -20
  122. package/ts_build/src/plugins/GitPlugin.js.map +1 -1
  123. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  124. package/ts_build/src/plugins/PluginBase.js +13 -0
  125. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  126. package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
  127. package/ts_build/src/plugins/SkillsPlugin.js +149 -0
  128. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
  129. package/ts_build/src/plugins/asana.js +4 -4
  130. package/ts_build/src/plugins/asana.js.map +1 -1
  131. package/ts_build/src/plugins/embedding.js +3 -3
  132. package/ts_build/src/plugins/embedding.js.map +1 -1
  133. package/ts_build/src/plugins/exec.js +3 -3
  134. package/ts_build/src/plugins/exec.js.map +1 -1
  135. package/ts_build/src/plugins/figma.js +3 -3
  136. package/ts_build/src/plugins/figma.js.map +1 -1
  137. package/ts_build/src/plugins/github.js +18 -18
  138. package/ts_build/src/plugins/github.js.map +1 -1
  139. package/ts_build/src/plugins/jira.js +2 -2
  140. package/ts_build/src/plugins/jira.js.map +1 -1
  141. package/ts_build/src/plugins/language.js +4 -4
  142. package/ts_build/src/plugins/language.js.map +1 -1
  143. package/ts_build/src/plugins/linear.js +4 -4
  144. package/ts_build/src/plugins/linear.js.map +1 -1
  145. package/ts_build/src/plugins/notion.js +6 -6
  146. package/ts_build/src/plugins/notion.js.map +1 -1
  147. package/ts_build/src/plugins/plugins.d.ts +3 -0
  148. package/ts_build/src/plugins/plugins.js +18 -3
  149. package/ts_build/src/plugins/plugins.js.map +1 -1
  150. package/ts_build/src/plugins/url.js +2 -2
  151. package/ts_build/src/plugins/url.js.map +1 -1
  152. package/ts_build/src/plugins/vim.js +2 -2
  153. package/ts_build/src/plugins/vim.js.map +1 -1
  154. package/ts_build/src/services/AgentService.d.ts +3 -0
  155. package/ts_build/src/services/AgentService.js +7 -0
  156. package/ts_build/src/services/AgentService.js.map +1 -1
  157. package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
  158. package/ts_build/src/services/AgentSyncFs.js +2 -0
  159. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  160. package/ts_build/src/services/EventService.d.ts +25 -2
  161. package/ts_build/src/services/EventService.js +92 -14
  162. package/ts_build/src/services/EventService.js.map +1 -1
  163. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  164. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  165. package/ts_build/src/services/SessionManager.d.ts +6 -0
  166. package/ts_build/src/services/SessionManager.js +39 -1
  167. package/ts_build/src/services/SessionManager.js.map +1 -1
  168. package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
  169. package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
  170. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
  171. package/ts_build/src/services/SyncerService.d.ts +30 -0
  172. package/ts_build/src/services/SyncerService.js +72 -0
  173. package/ts_build/src/services/SyncerService.js.map +1 -0
  174. package/ts_build/src/services/index.d.ts +2 -0
  175. package/ts_build/src/services/index.js +2 -0
  176. package/ts_build/src/services/index.js.map +1 -1
  177. package/ts_build/src/services/modules/index.js +10 -2
  178. package/ts_build/src/services/modules/index.js.map +1 -1
  179. package/ts_build/src/types.d.ts +19 -0
  180. package/ts_build/src/types.js.map +1 -1
  181. package/ts_build/src/worker.d.ts +2 -0
  182. package/ts_build/src/worker.js +59 -4
  183. package/ts_build/src/worker.js.map +1 -1
  184. package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
  185. package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
  186. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
  187. package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
  188. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
  189. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
  190. package/ts_build/src/workers/auth/types.d.ts +36 -0
  191. package/ts_build/src/workers/auth/types.js +3 -0
  192. package/ts_build/src/workers/auth/types.js.map +1 -0
  193. package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
  194. package/ts_build/src/workers/tools/getChallenge.js +27 -0
  195. package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
  196. package/ts_build/src/workers/tools/index.d.ts +6 -0
  197. package/ts_build/src/workers/tools/index.js +10 -0
  198. package/ts_build/src/workers/tools/index.js.map +1 -1
  199. package/ts_build/src/workers/tools/lock.d.ts +9 -0
  200. package/ts_build/src/workers/tools/lock.js +27 -0
  201. package/ts_build/src/workers/tools/lock.js.map +1 -0
  202. package/ts_build/src/workers/tools/unlock.d.ts +18 -0
  203. package/ts_build/src/workers/tools/unlock.js +78 -0
  204. package/ts_build/src/workers/tools/unlock.js.map +1 -0
  205. package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
  206. package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
  207. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
  208. package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
  209. package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
  210. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Agent Chat Module - Handles agent interactions
3
3
  */
4
+ import { EventService } from "../../services/EventService";
5
+ import { ConsoleRenderer, AgentRenderer } from "../renderer";
4
6
  import {
5
- AgentSyncKnowhowWeb,
6
- AgentSyncFs,
7
7
  SessionManager,
8
8
  TaskRegistry,
9
+ SyncedAgentWatcher,
10
+ SyncerService,
9
11
  } from "../../services/index";
12
+ import { AttachableAgent } from "../../services/SyncedAgentWatcher";
10
13
  import * as fs from "fs";
11
14
  import * as fsPromises from "fs/promises";
12
15
  import * as path from "path";
@@ -26,8 +29,13 @@ import {
26
29
  HarmonyToolProcessor,
27
30
  Base64ImageProcessor,
28
31
  } from "../../processors/index";
29
- import { TaskInfo, ChatSession } from "../types";
30
- import { agents } from "../../agents";
32
+ import { TaskInfo } from "../types";
33
+ import {
34
+ createAgent,
35
+ agentConstructors,
36
+ AgentName,
37
+ agents,
38
+ } from "../../agents";
31
39
  import { ToolCallEvent } from "../../agents/base/base";
32
40
  import { $Command } from "@aws-sdk/client-s3";
33
41
  import { KnowhowSimpleClient } from "../../services/KnowhowClient";
@@ -36,18 +44,33 @@ export class AgentModule extends BaseChatModule {
36
44
  name = "agent";
37
45
  description = "Agent interaction functionality";
38
46
 
47
+ /** Currently attached agent (for agent:attached mode commands) */
48
+ private attachedAgent: AttachableAgent | undefined;
49
+
39
50
  // Service instances for task management, session management, and synchronization
40
51
  private taskRegistry: TaskRegistry;
41
52
  private sessionManager: SessionManager;
42
- private webSync: AgentSyncKnowhowWeb;
43
- private fsSync: AgentSyncFs;
53
+ private syncer: SyncerService;
54
+ /** Timestamp when this process started - used to filter sessions */
55
+ private processStartTime: number = Date.now();
56
+ /** Currently attached agent task ID */
57
+ private activeAgentTaskId: string | undefined;
58
+ /** Currently active synced agent watcher (for FS or Web agents) */
59
+ private activeSyncedWatcher: SyncedAgentWatcher | undefined;
60
+
61
+ /** Stored wire params for rewireAgentRendering */
62
+ private _wireAgentEvents: EventService | undefined;
63
+ private _wireTaskId: string | undefined;
64
+ private _wireAgentName: string | undefined;
65
+ private _wireEventTypes:
66
+ | { toolCall?: string; toolUsed?: string; agentSay?: string; done: string }
67
+ | undefined;
44
68
 
45
69
  constructor() {
46
70
  super();
47
71
  this.taskRegistry = new TaskRegistry();
48
72
  this.sessionManager = new SessionManager();
49
- this.webSync = new AgentSyncKnowhowWeb();
50
- this.fsSync = new AgentSyncFs();
73
+ this.syncer = new SyncerService();
51
74
  }
52
75
 
53
76
  getCommands(): ChatCommand[] {
@@ -63,30 +86,137 @@ export class AgentModule extends BaseChatModule {
63
86
  handler: this.handleAgentsCommand.bind(this),
64
87
  },
65
88
  {
66
- name: "attach",
67
- description: "Attach to a running session or resume an old session",
68
- handler: this.handleAttachCommand.bind(this),
89
+ name: "pause",
90
+ description: "Pause the currently attached agent",
91
+ modes: ["agent:attached"],
92
+ handler: async (_args: string[]): Promise<void> => {
93
+ if (this.attachedAgent) {
94
+ await this.attachedAgent.pause();
95
+ console.log("Agent paused.");
96
+ }
97
+ },
98
+ },
99
+ {
100
+ name: "unpause",
101
+ description: "Unpause the currently attached agent",
102
+ modes: ["agent:attached"],
103
+ handler: async (_args: string[]): Promise<void> => {
104
+ if (this.attachedAgent) {
105
+ await this.attachedAgent.unpause();
106
+ console.log("Agent unpaused.");
107
+ }
108
+ },
109
+ },
110
+ {
111
+ name: "kill",
112
+ description: "Kill the currently attached agent",
113
+ modes: ["agent:attached"],
114
+ handler: async (_args: string[]): Promise<void> => {
115
+ if (this.attachedAgent) {
116
+ await this.attachedAgent.kill();
117
+ console.log("Agent terminated.");
118
+ this.detachFromAgent();
119
+ }
120
+ },
121
+ },
122
+ {
123
+ name: "detach",
124
+ description: "Detach from the currently attached agent",
125
+ modes: ["agent:attached"],
126
+ handler: async (_args: string[]): Promise<void> => {
127
+ console.log("Detached from agent");
128
+ this.detachFromAgent();
129
+ },
69
130
  },
70
131
  {
71
- name: "sessions",
72
- description: "List active tasks and saved sessions",
73
- handler: this.handleSessionsCommand.bind(this),
132
+ name: "done",
133
+ description: "Exit the current agent interaction",
134
+ modes: ["agent:attached"],
135
+ handler: async (_args: string[]): Promise<void> => {
136
+ this.detachFromAgent();
137
+ },
74
138
  },
75
139
  ];
76
140
  }
77
141
 
142
+ /**
143
+ * Clean up attached agent state and restore default mode/prompt.
144
+ */
145
+ private detachFromAgent(): void {
146
+ this.attachedAgent = undefined;
147
+
148
+ // Unregister rendering event listeners
149
+ this.unwireAgentRendering();
150
+
151
+ if (this.chatService) {
152
+ this.chatService.setMode("default");
153
+ }
154
+
155
+ // Stop any active synced watcher
156
+ if (this.activeSyncedWatcher) {
157
+ this.activeSyncedWatcher.stopWatching();
158
+ this.activeSyncedWatcher = undefined;
159
+ }
160
+
161
+ const context = this.chatService?.getContext();
162
+ if (context) context.activeAgentTaskId = undefined;
163
+ }
164
+
78
165
  getModes(): ChatMode[] {
79
166
  return [
80
167
  {
81
168
  name: "agent",
82
169
  description: "Agent interaction mode",
83
170
  active: true,
171
+ promptText: () => {
172
+ const ctx = this.chatService?.getContext();
173
+ return ctx?.currentAgent
174
+ ? `\nAsk knowhow ${ctx.currentAgent}: `
175
+ : `\nAsk knowhow: `;
176
+ },
177
+ },
178
+ {
179
+ name: "agent:attached",
180
+ description: "Attached to a running agent",
181
+ active: false,
182
+ promptText: () =>
183
+ this.attachedAgent
184
+ ? `Enter command or message for ${this.attachedAgent.name}: `
185
+ : `Enter command or message for agent: `,
84
186
  },
85
187
  ];
86
188
  }
87
189
 
190
+ /**
191
+ * Get the current renderer from the chat context, falling back to a ConsoleRenderer.
192
+ */
193
+ private get renderer(): AgentRenderer {
194
+ return (
195
+ (this.chatService?.getContext()?.renderer as AgentRenderer) ??
196
+ new ConsoleRenderer()
197
+ );
198
+ }
199
+
88
200
  async initialize(service: ChatService): Promise<void> {
89
201
  await super.initialize(service);
202
+
203
+ // Set up plugin log event handler - use setListener so re-init doesn't double-subscribe
204
+ const Events = services().Events;
205
+ Events.setListener(
206
+ { key: "agentModule:pluginLog", event: Events.eventTypes.pluginLog },
207
+ (logEvent: any) => {
208
+ const activeTasks = this.taskRegistry.getAll();
209
+ this.renderer.render({
210
+ type: "log",
211
+ taskId: this.activeAgentTaskId,
212
+ agentName: logEvent.source,
213
+ message: logEvent.message,
214
+ level: logEvent.level || "info",
215
+ timestamp: logEvent.timestamp || new Date().toISOString(),
216
+ });
217
+ }
218
+ );
219
+
90
220
  await this.handleAgentCommand(["Patcher"]);
91
221
  }
92
222
 
@@ -145,51 +275,165 @@ export class AgentModule extends BaseChatModule {
145
275
  }
146
276
  }
147
277
 
148
- async handleAttachCommand(args: string[]): Promise<void> {
149
- if (args.length === 0) {
150
- // Get both running tasks and saved sessions
151
- const runningTasks = this.taskRegistry.getAll();
152
- const savedSessions = this.sessionManager.listAvailableSessions();
278
+ /**
279
+ * Get the task registry for CLI access
280
+ */
281
+ public getTaskRegistry(): TaskRegistry {
282
+ return this.taskRegistry;
283
+ }
153
284
 
154
- if (runningTasks.length === 0 && savedSessions.length === 0) {
155
- console.log("No active tasks or saved sessions found to attach to.");
156
- return;
157
- }
285
+ /**
286
+ * Get the session manager
287
+ */
288
+ public getSessionManager(): SessionManager {
289
+ return this.sessionManager;
290
+ }
158
291
 
159
- await this.sessionManager.logRunningTasks(runningTasks, savedSessions);
292
+ /**
293
+ * Get the current renderer from the chat context, falling back to a ConsoleRenderer.
294
+ */
295
+ public getRenderer(): AgentRenderer {
296
+ return this.renderer;
297
+ }
160
298
 
161
- // Interactive selection for both types
162
- const allIds = [
163
- ...savedSessions.map((s) => s.sessionId),
164
- ...runningTasks.map((t) => t.taskId),
165
- ];
299
+ /**
300
+ * Get the currently active synced watcher
301
+ */
302
+ public getActiveSyncedWatcher(): SyncedAgentWatcher | undefined {
303
+ return this.activeSyncedWatcher;
304
+ }
166
305
 
167
- const selectedId = await this.chatService?.getInput(
168
- "Select a session/task to attach to (or press Enter to skip): ",
169
- allIds
170
- );
306
+ /**
307
+ * Set the currently active synced watcher
308
+ */
309
+ public setActiveSyncedWatcher(w: SyncedAgentWatcher | undefined): void {
310
+ this.activeSyncedWatcher = w;
311
+ }
171
312
 
172
- if (
173
- selectedId &&
174
- selectedId.trim() &&
175
- allIds.includes(selectedId.trim())
176
- ) {
177
- await this.handleAttachById(selectedId.trim());
178
- }
179
- return;
313
+ /**
314
+ * Get the currently active agent task ID
315
+ */
316
+ public getActiveAgentTaskId(): string | undefined {
317
+ return this.activeAgentTaskId;
318
+ }
319
+
320
+ /**
321
+ * Set the currently active agent task ID
322
+ */
323
+ public setActiveAgentTaskId(id: string | undefined): void {
324
+ this.activeAgentTaskId = id;
325
+ }
326
+
327
+ /**
328
+ * Wire up agentEvents to the renderer for a given task.
329
+ * Uses setListener with stable keys so re-calling automatically replaces old listeners.
330
+ *
331
+ * @param taskId The task ID to pass through to render events
332
+ * @param agentEvents An EventEmitter that emits toolCall / toolUsed / agentSay events
333
+ * @param eventTypes Object with event name constants (toolCall, toolUsed, agentSay, done)
334
+ * @param agentName Display name for the agent
335
+ */
336
+ public wireAgentRendering(
337
+ taskId: string,
338
+ agentEvents: EventService,
339
+ eventTypes: {
340
+ toolCall?: string;
341
+ toolUsed?: string;
342
+ agentSay?: string;
343
+ done: string;
344
+ },
345
+ agentName: string
346
+ ): void {
347
+ if (eventTypes.toolCall) {
348
+ agentEvents.setListener(
349
+ { key: "agentModule:render:toolCall", event: eventTypes.toolCall },
350
+ (data: any) =>
351
+ this.renderer.render({
352
+ type: "toolCall",
353
+ taskId,
354
+ agentName,
355
+ toolCall: data.toolCall,
356
+ })
357
+ );
180
358
  }
359
+ if (eventTypes.toolUsed) {
360
+ agentEvents.setListener(
361
+ { key: "agentModule:render:toolUsed", event: eventTypes.toolUsed },
362
+ (data: any) =>
363
+ this.renderer.render({
364
+ type: "toolResult",
365
+ taskId,
366
+ agentName,
367
+ toolCall: data.toolCall,
368
+ result: data.functionResp,
369
+ })
370
+ );
371
+ }
372
+ if (eventTypes.agentSay) {
373
+ agentEvents.setListener(
374
+ { key: "agentModule:render:agentSay", event: eventTypes.agentSay },
375
+ (data: any) =>
376
+ this.renderer.render({
377
+ type: "agentMessage",
378
+ taskId,
379
+ agentName,
380
+ message: data.message,
381
+ role: "assistant",
382
+ })
383
+ );
384
+ }
385
+
386
+ // Set active task on the renderer
387
+ this.activeAgentTaskId = taskId;
388
+ this.renderer.setActiveTaskId(taskId);
389
+
390
+ // Store agentEvents reference for unwire/rewire
391
+ this._wireAgentEvents = agentEvents;
392
+ this._wireAgentName = agentName;
393
+ this._wireEventTypes = eventTypes;
394
+ this._wireTaskId = taskId;
395
+ }
181
396
 
182
- const taskId = args[0];
183
- await this.handleAttachById(taskId);
397
+ /**
398
+ * Unwire rendering event listeners that were set up by wireAgentRendering().
399
+ * Clears the active task on the renderer.
400
+ */
401
+ public unwireAgentRendering(): void {
402
+ if (this._wireAgentEvents) {
403
+ this._wireAgentEvents.removeManagedListenersByPrefix(
404
+ "agentModule:render:"
405
+ );
406
+ this._wireAgentEvents = undefined;
407
+ }
408
+ this.activeAgentTaskId = undefined;
409
+ this.renderer.setActiveTaskId(undefined);
184
410
  }
185
411
 
186
412
  /**
187
- * Display single task details
413
+ * Rewire rendering event listeners to the current renderer.
414
+ * Call this after switching the active renderer (e.g. via /render) so that
415
+ * in-progress agents forward events to the new renderer.
416
+ * Since setListener auto-replaces, just re-call wireAgentRendering with stored params.
188
417
  */
189
- private displaySingleTask(task: TaskInfo): void {
190
- this.taskRegistry.displaySingleTask(task);
418
+ public rewireAgentRendering(): void {
419
+ if (
420
+ !this._wireAgentEvents ||
421
+ !this._wireTaskId ||
422
+ !this._wireEventTypes ||
423
+ !this._wireAgentName
424
+ )
425
+ return;
426
+ this.wireAgentRendering(
427
+ this._wireTaskId,
428
+ this._wireAgentEvents,
429
+ this._wireEventTypes,
430
+ this._wireAgentName
431
+ );
191
432
  }
192
433
 
434
+ /**
435
+ * List available agents and optionally select one interactively
436
+ */
193
437
  async handleAgentsCommand(args: string[]): Promise<void> {
194
438
  try {
195
439
  const allAgents = agents();
@@ -199,14 +443,15 @@ export class AgentModule extends BaseChatModule {
199
443
 
200
444
  console.log("\nAvailable agents:");
201
445
  Object.entries(allAgents).forEach(([name, agent]: [string, any]) => {
202
- console.log(` - ${name}: ${agent.description || "No description"}`);
446
+ console.log(
447
+ ` - ${name}: ${(agent as any).description || "No description"}`
448
+ );
203
449
  });
204
450
  console.log("─".repeat(80), "\n");
205
451
 
206
- // Interactive selection with autocomplete
207
452
  const selectedAgent = await this.chatService?.getInput(
208
453
  "Select an agent to start: ",
209
- agentNames // Pass agent names as autocomplete options
454
+ agentNames
210
455
  );
211
456
 
212
457
  if (
@@ -214,7 +459,6 @@ export class AgentModule extends BaseChatModule {
214
459
  selectedAgent.trim() &&
215
460
  agentNames.includes(selectedAgent.trim())
216
461
  ) {
217
- // Start the selected agent
218
462
  await this.handleAgentCommand([selectedAgent.trim()]);
219
463
  } else if (selectedAgent && selectedAgent.trim()) {
220
464
  console.log(`Agent "${selectedAgent.trim()}" not found.`);
@@ -228,153 +472,17 @@ export class AgentModule extends BaseChatModule {
228
472
  }
229
473
  }
230
474
 
231
- async logSessionTable() {
232
- const runningTasks = this.taskRegistry.getAll();
233
- const savedSessions = this.sessionManager.listAvailableSessions();
234
- this.sessionManager.logSessionTable(runningTasks, savedSessions);
235
- }
236
-
237
- async handleSessionsCommand(args: string[]): Promise<void> {
238
- try {
239
- // Get both running tasks and saved sessions
240
- const runningTasks = this.taskRegistry.getAll();
241
- const savedSessions = this.sessionManager.listAvailableSessions();
242
-
243
- if (runningTasks.length === 0 && savedSessions.length === 0) {
244
- console.log("No active tasks or saved sessions found.");
245
- return;
246
- }
247
-
248
- await this.logSessionTable();
249
-
250
- // Interactive selection for both types
251
- const allIds = [
252
- ...savedSessions.map((s) => s.sessionId),
253
- ...runningTasks.map((t) => t.taskId),
254
- ];
255
-
256
- if (allIds.length > 0) {
257
- const selectedId = await this.chatService?.getInput(
258
- "Select a session/task to attach to (or press Enter to skip): ",
259
- allIds
260
- );
261
-
262
- if (
263
- selectedId &&
264
- selectedId.trim() &&
265
- allIds.includes(selectedId.trim())
266
- ) {
267
- await this.handleAttachById(selectedId.trim());
268
- }
269
- }
270
- } catch (error) {
271
- console.error("Error listing sessions and tasks:", error);
272
- }
273
- }
274
-
275
- /**
276
- * Handle attachment by ID - works for both running tasks and saved sessions
277
- */
278
- private async handleAttachById(id: string): Promise<void> {
279
- // Check if it's a running task first
280
- if (this.taskRegistry.has(id)) {
281
- const taskInfo = this.taskRegistry.get(id);
282
- if (taskInfo) {
283
- // Switch to agent mode and set the selected agent
284
- const context = this.chatService?.getContext();
285
- const allAgents = agents();
286
- const selectedAgent = allAgents[taskInfo.agentName];
287
-
288
- if (context && selectedAgent) {
289
- context.selectedAgent = selectedAgent;
290
- context.agentMode = true;
291
- context.currentAgent = taskInfo.agentName;
292
- // Update context's model/provider to reflect the agent's settings
293
- // so /model and /provider commands show accurate information
294
- context.currentModel = selectedAgent.getModel();
295
- context.currentProvider = selectedAgent.getProvider();
296
- console.log(`🔄 Switched to agent mode with ${taskInfo.agentName}`);
297
- console.log(`📋 Attached to running task: ${id}`);
298
- console.log(`Task: ${taskInfo.initialInput}`);
299
- console.log(`Status: ${taskInfo.status}`);
300
- return;
301
- }
302
- }
303
- console.log(Marked.parse(`**Attached to running task: ${id}**`));
304
- return;
305
- }
306
-
307
- // Check if it's a saved session
308
- try {
309
- const session = this.sessionManager.loadSession(id);
310
- if (session) {
311
- console.log(Marked.parse(`**Resuming saved session: ${id}**`));
312
- // Read session to get agent information
313
-
314
- // Switch to agent mode and set the selected agent
315
- const context = this.chatService?.getContext();
316
- const allAgents = agents();
317
- const selectedAgent = allAgents[session.agentName];
318
-
319
- if (context && selectedAgent) {
320
- context.selectedAgent = selectedAgent;
321
- context.agentMode = true;
322
- // Update context's model/provider to reflect the agent's settings
323
- // so /model and /provider commands show accurate information
324
- context.currentModel = selectedAgent.getModel();
325
- context.currentProvider = selectedAgent.getProvider();
326
- console.log(`🔄 Switched to agent mode with ${session.agentName}`);
327
- console.log(`📋 Resuming saved session: ${id}`);
328
- console.log(`Original task: ${session.initialInput}`);
329
- console.log(`Status: ${session.status}`);
330
-
331
- const addedContext = await this.chatService.getInput(
332
- "Add any additional context for resuming this session (or press Enter to skip): "
333
- );
334
- await this.resumeSession(id);
335
- return;
336
- }
337
- }
338
- } catch (error) {
339
- // Session file doesn't exist or error reading it
340
- }
341
-
342
- console.log(Marked.parse(`**Session/Task ${id} not found.**`));
343
- }
344
-
345
- /**
346
- * List available session files
347
- */
348
- public async listAvailableSessions(): Promise<ChatSession[]> {
349
- return this.sessionManager.listAvailableSessions();
350
- }
351
-
352
- /**
353
- * List both active tasks and saved sessions for CLI usage
354
- */
355
- public async listSessionsAndTasks(): Promise<{
356
- runningTasks: TaskInfo[];
357
- savedSessions: ChatSession[];
358
- }> {
359
- const runningTasks = this.taskRegistry.getAll();
360
- const savedSessions = this.sessionManager.listAvailableSessions();
361
- return {
362
- runningTasks,
363
- savedSessions,
364
- };
365
- }
366
-
367
475
  /**
368
- * Get the task registry for CLI access
476
+ * Get the process start time
369
477
  */
370
- public getTaskRegistry(): TaskRegistry {
371
- return this.taskRegistry;
478
+ public getProcessStartTime(): number {
479
+ return this.processStartTime;
372
480
  }
373
481
 
374
482
  /**
375
483
  * Resume a session from saved state
376
484
  */
377
- private async resumeSession(
485
+ public async resumeSession(
378
486
  sessionId: string,
379
487
  resumeReason?: string
380
488
  ): Promise<void> {
@@ -439,6 +547,16 @@ Please continue from where you left off and complete the original request.
439
547
  async handleInput(input: string, context: ChatContext): Promise<boolean> {
440
548
  // If in agent mode, start agent with the input as initial task (like original chat.ts)
441
549
  if (context.agentMode && context.selectedAgent) {
550
+ // If we're attached to a running agent, forward input to it
551
+ if (this.attachedAgent) {
552
+ this.attachedAgent.addPendingUserMessage({
553
+ role: "user",
554
+ content: input,
555
+ });
556
+ return true;
557
+ }
558
+
559
+ // Otherwise start a new agent task
442
560
  // Create initial interaction for the chatHistory
443
561
  const initialInteraction: ChatInteraction = {
444
562
  input,
@@ -481,18 +599,22 @@ Please continue from where you left off and complete the original request.
481
599
  run?: boolean; // whether to run immediately
482
600
  taskId?: string; // optional pre-generated taskId
483
601
  }) {
484
- const allAgents = agents();
485
-
486
- if (!allAgents[options.agentName]) {
602
+ if (!agentConstructors[options.agentName as AgentName]) {
487
603
  throw new Error(
488
604
  `Agent "${
489
605
  options.agentName
490
- }" not found. Available agents: ${Object.keys(allAgents).join(", ")}`
606
+ }" not found. Available agents: ${Object.keys(agentConstructors).join(
607
+ ", "
608
+ )}`
491
609
  );
492
610
  }
493
611
 
494
612
  const { input, chatHistory = [], agentName } = options;
495
- const agent = allAgents[options.agentName] as BaseAgent;
613
+ const agentContext = services().Agents.getAgentContext();
614
+ const agent = createAgent(
615
+ options.agentName as AgentName,
616
+ agentContext
617
+ ) as BaseAgent;
496
618
 
497
619
  let done = false;
498
620
  let output = "Done";
@@ -532,45 +654,26 @@ Please continue from where you left off and complete the original request.
532
654
  this.saveSession(taskId, taskInfo, []);
533
655
 
534
656
  // Reset sync services before setting up new task (removes old listeners)
535
- this.webSync.reset();
536
- this.fsSync.reset();
537
-
538
- // Create Knowhow chat task if messageId provided
539
- if (
540
- options.messageId &&
541
- !options.existingKnowhowTaskId &&
542
- !options.syncFs
543
- ) {
544
- knowhowTaskId = await this.webSync.createChatTask({
545
- messageId: options.messageId,
546
- prompt: input,
547
- });
548
-
549
- if (knowhowTaskId) {
550
- // Update TaskInfo with the created knowhowTaskId
551
- taskInfo.knowhowTaskId = knowhowTaskId;
552
- this.taskRegistry.register(taskId, taskInfo);
657
+ this.syncer.reset();
553
658
 
554
- // Set up event-based synchronization with Knowhow API
555
- await this.webSync.setupAgentSync(agent, knowhowTaskId);
556
- }
557
- } else if (!options.messageId || options.syncFs) {
558
- // Use filesystem sync when no messageId is provided or syncFs is explicitly enabled
559
- console.log(
560
- `📁 Using filesystem-based synchronization for task: ${taskId}`
561
- );
562
-
563
- const fsTaskId = await this.fsSync.createTask({
564
- taskId,
565
- prompt: input,
566
- });
659
+ // Create sync task (SyncerService decides web vs fs internally)
660
+ const syncTaskId = await this.syncer.createTask({
661
+ taskId,
662
+ prompt: input,
663
+ messageId: options.messageId,
664
+ syncFs: options.syncFs,
665
+ existingKnowhowTaskId: options.existingKnowhowTaskId,
666
+ agentName,
667
+ });
567
668
 
568
- // Update TaskInfo with the fs sync info
569
- taskInfo.knowhowTaskId = fsTaskId;
570
- this.taskRegistry.register(taskId, taskInfo);
669
+ // Update TaskInfo with the sync task ID
670
+ const webTaskId = this.syncer.getCreatedWebTaskId();
671
+ knowhowTaskId = webTaskId;
672
+ taskInfo.knowhowTaskId = webTaskId || syncTaskId;
673
+ this.taskRegistry.register(taskId, taskInfo);
571
674
 
572
- await this.fsSync.setupAgentSync(agent, fsTaskId);
573
- }
675
+ // Wire up event listeners on the agent
676
+ await this.syncer.setupAgentSync(agent, syncTaskId);
574
677
 
575
678
  // Set up session update listener
576
679
  const threadUpdateHandler = async (threadState: any) => {
@@ -641,29 +744,30 @@ Please continue from where you left off and complete the original request.
641
744
  ]);
642
745
 
643
746
  // Set up event listeners
644
- if (!agent.agentEvents.listenerCount(agent.eventTypes.toolCall)) {
645
- agent.agentEvents.on(
646
- agent.eventTypes.toolCall,
647
- (responseMsg: ToolCallEvent) => {
648
- console.time(JSON.stringify(responseMsg.toolCall.function.name));
649
- console.log(
650
- ` 🔨 Tool: ${responseMsg.toolCall.function.name}\n Args: ${responseMsg.toolCall.function.arguments}\n`
651
- );
652
- }
653
- );
654
- }
655
- if (!agent.agentEvents.listenerCount(agent.eventTypes.toolUsed)) {
656
- agent.agentEvents.on(
657
- agent.eventTypes.toolUsed,
658
- (responseMsg: ToolCallEvent) => {
659
- console.timeEnd(JSON.stringify(responseMsg.toolCall.function.name));
660
- console.log(
661
- ` 🔨 Tool Response:
662
- ${JSON.stringify(responseMsg.functionResp, null, 2)}`
663
- );
664
- }
665
- );
666
- }
747
+ // Each task gets a fresh agent instance (via createAgent), so no stale listeners exist.
748
+ const agentLogHandler = (logData: any) => {
749
+ this.renderer.render({
750
+ type: "log",
751
+ taskId,
752
+ agentName: logData.agentName,
753
+ message: logData.message,
754
+ level: logData.level,
755
+ timestamp: logData.timestamp,
756
+ });
757
+ };
758
+ const agentStatusHandler = (statusData: any) => {
759
+ this.renderer.render({
760
+ type: "agentStatus",
761
+ taskId,
762
+ agentName: statusData.agentName,
763
+ statusMessage: statusData.statusMessage,
764
+ details: statusData.details,
765
+ timestamp: statusData.timestamp,
766
+ });
767
+ };
768
+
769
+ agent.agentEvents.on(agent.eventTypes.agentLog, agentLogHandler);
770
+ agent.agentEvents.on(agent.eventTypes.agentStatus, agentStatusHandler);
667
771
 
668
772
  const taskCompleted = new Promise<string>((resolve) => {
669
773
  agent.agentEvents.once(agent.eventTypes.done, async (doneMsg) => {
@@ -675,20 +779,20 @@ Please continue from where you left off and complete the original request.
675
779
  agent.eventTypes.threadUpdate,
676
780
  threadUpdateHandler
677
781
  );
782
+ // Remove task-specific listeners so they don't fire for the next task
783
+ agent.agentEvents.removeListener(
784
+ agent.eventTypes.agentLog,
785
+ agentLogHandler
786
+ );
787
+ agent.agentEvents.removeListener(
788
+ agent.eventTypes.agentStatus,
789
+ agentStatusHandler
790
+ );
678
791
  // Update task info
679
792
  taskInfo = this.taskRegistry.get(taskId);
680
793
 
681
794
  // Wait for AgentSync to finish before resolving
682
- if (knowhowTaskId) {
683
- console.log(
684
- "🎯 [AgentModule] Waiting for web sync finalization..."
685
- );
686
- await this.webSync.waitForFinalization();
687
- console.log("🎯 [AgentModule] Web sync finalization complete");
688
- }
689
- console.log("🎯 [AgentModule] Waiting for fs sync finalization...");
690
- await this.fsSync.waitForFinalization();
691
- console.log("🎯 [AgentModule] Fs sync finalization complete");
795
+ await this.syncer.waitForFinalization();
692
796
 
693
797
  if (taskInfo) {
694
798
  taskInfo.status = "completed";
@@ -923,126 +1027,64 @@ Please continue from where you left off and complete the original request.
923
1027
 
924
1028
  async attachedAgentChatLoop(
925
1029
  taskId: string,
926
- agent: BaseAgent,
1030
+ agent: AttachableAgent,
927
1031
  initialInput?: string
928
1032
  ): Promise<{ result: boolean; finalOutput?: string }> {
929
1033
  try {
930
- let done = false;
931
- let output = "Done";
932
1034
  let agentFinalOutput: string | undefined;
933
1035
 
934
- // Define available commands
935
- // Set mode to agent:attached so custom commands are available
1036
+ // Set mode to agent:attached so mode-specific commands are available
936
1037
  if (this.chatService) {
937
1038
  this.chatService.setMode("agent:attached");
938
1039
  }
939
1040
 
940
- // Get mode-specific commands for autocomplete
941
- const modeCommands =
942
- this.chatService
943
- ?.getCommandsForMode("agent:attached")
944
- .map((cmd) => `/${cmd.name}`) || [];
945
-
946
- const commands = [
947
- ...modeCommands,
948
- "/pause",
949
- "/unpause",
950
- "/kill",
951
- "/detach",
952
- "/done",
953
- ];
954
- const history: string[] = [];
1041
+ // Wire up rendering event listeners and track the active task
1042
+ this.wireAgentRendering(
1043
+ taskId,
1044
+ agent.agentEvents,
1045
+ agent.eventTypes,
1046
+ agent.name
1047
+ );
1048
+ const context = this.chatService?.getContext();
1049
+ if (context) context.activeAgentTaskId = taskId;
1050
+
1051
+ // Store the agent so the registered agent:attached commands can reference it
1052
+ this.attachedAgent = agent;
955
1053
 
956
1054
  // Set up the event listener BEFORE starting the agent to avoid race condition
957
- let finished = false;
958
1055
  const donePromise = new Promise<string>((resolve) => {
959
1056
  agent.agentEvents.once(agent.eventTypes.done, (doneMsg) => {
960
1057
  // Capture the agent's final output
961
1058
  agentFinalOutput = doneMsg || "No response from the AI";
962
1059
  console.log("Finished", taskId, `$${agent.getTotalCostUsd()}`);
963
- finished = true;
1060
+
1061
+ // Update final task status
1062
+ const finalTaskInfo = this.taskRegistry.get(taskId);
1063
+ if (finalTaskInfo) {
1064
+ if (finalTaskInfo.status === "running") {
1065
+ finalTaskInfo.status = "completed";
1066
+ finalTaskInfo.totalCost = agent.getTotalCostUsd();
1067
+ finalTaskInfo.endTime = Date.now();
1068
+ }
1069
+ }
1070
+
964
1071
  resolve("done");
1072
+ // Exit agent:attached mode so the prompt resets back to the default
1073
+ this.detachFromAgent();
965
1074
  });
966
1075
  });
967
1076
 
968
1077
  // Now start the agent if we have an initial input (this means we're starting, not just attaching)
969
1078
  if (initialInput) {
970
1079
  const taskInfo = this.taskRegistry.get(taskId);
971
- agent.call(
1080
+ (agent as BaseAgent).call(
972
1081
  taskInfo?.formattedPrompt || taskInfo?.initialInput || initialInput
973
1082
  );
974
1083
  }
975
1084
 
976
- let input =
977
- (await this.chatService?.getInput(
978
- `Enter command or message for ${agent.name}: `,
979
- commands
980
- )) || "";
981
-
982
- history.push(input);
983
-
984
- while (!done) {
985
- switch (input) {
986
- case "":
987
- if (finished) {
988
- output = "Agent has completed the task.";
989
- done = true;
990
- }
991
- break;
992
- case "/done":
993
- output = "Exited agent interaction.";
994
- done = true;
995
- break;
996
- case "/pause":
997
- await agent.pause();
998
- console.log("Agent paused.");
999
- break;
1000
- case "/unpause":
1001
- await agent.unpause();
1002
- console.log("Agent unpaused.");
1003
- break;
1004
- case "/kill":
1005
- await agent.kill();
1006
- console.log("Agent terminated.");
1007
- done = true;
1008
- break;
1009
- case "/detach":
1010
- console.log("Detached from agent");
1011
- // Reset mode back to default when detaching
1012
- if (this.chatService) {
1013
- this.chatService.setMode("default");
1014
- }
1015
- return { result: true, finalOutput: agentFinalOutput };
1016
- default:
1017
- agent.addPendingUserMessage({
1018
- role: "user",
1019
- content: input,
1020
- });
1021
- }
1022
-
1023
- if (!done) {
1024
- input = await this.chatService?.getInput(
1025
- `Enter command or message for ${agent.name}: `,
1026
- commands
1027
- );
1028
- }
1029
- }
1030
-
1031
- // Reset mode back to default when exiting loop
1032
- if (this.chatService) {
1033
- this.chatService.setMode("default");
1034
- }
1035
-
1036
- // Update final task status and save session
1037
- const finalTaskInfo = this.taskRegistry.get(taskId);
1038
- if (finalTaskInfo) {
1039
- if (finalTaskInfo.status === "running") {
1040
- finalTaskInfo.status = "completed";
1041
- // Ensure final cost is captured
1042
- finalTaskInfo.totalCost = agent.getTotalCostUsd();
1043
- finalTaskInfo.endTime = Date.now();
1044
- }
1045
- }
1085
+ // Return immediately — the main startChatLoop on CliChatService
1086
+ // now handles all user input via the registered agent:attached commands.
1087
+ // Any non-command input is forwarded to the agent via handleInput below.
1046
1088
  return { result: true, finalOutput: agentFinalOutput };
1047
1089
  } catch (error) {
1048
1090
  console.error("Agent execution failed:", error);