@tyvm/knowhow 0.0.103 → 0.0.105

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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/clients/index.ts +100 -6
  3. package/src/clients/pricing/index.ts +2 -0
  4. package/src/clients/pricing/models.ts +252 -0
  5. package/src/clients/pricing/types.ts +29 -0
  6. package/src/clients/pricing/xai.ts +7 -6
  7. package/src/clients/xai.ts +2 -2
  8. package/src/fileSync.ts +15 -2
  9. package/src/hashes.ts +33 -0
  10. package/src/services/AgentSyncFs.ts +20 -12
  11. package/src/services/SyncedAgentWatcher.ts +13 -298
  12. package/src/services/index.ts +1 -0
  13. package/src/services/watchers/FsSyncer.ts +155 -0
  14. package/src/services/watchers/RemoteSyncer.ts +153 -0
  15. package/src/services/watchers/index.ts +2 -0
  16. package/src/types.ts +2 -2
  17. package/ts_build/package.json +1 -1
  18. package/ts_build/src/clients/index.d.ts +8 -0
  19. package/ts_build/src/clients/index.js +57 -2
  20. package/ts_build/src/clients/index.js.map +1 -1
  21. package/ts_build/src/clients/pricing/index.d.ts +2 -0
  22. package/ts_build/src/clients/pricing/index.js +8 -1
  23. package/ts_build/src/clients/pricing/index.js.map +1 -1
  24. package/ts_build/src/clients/pricing/models.d.ts +8 -0
  25. package/ts_build/src/clients/pricing/models.js +173 -0
  26. package/ts_build/src/clients/pricing/models.js.map +1 -0
  27. package/ts_build/src/clients/pricing/types.d.ts +21 -0
  28. package/ts_build/src/clients/pricing/types.js +3 -0
  29. package/ts_build/src/clients/pricing/types.js.map +1 -0
  30. package/ts_build/src/clients/pricing/xai.d.ts +3 -8
  31. package/ts_build/src/clients/pricing/xai.js +4 -4
  32. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  33. package/ts_build/src/clients/xai.d.ts +9 -5
  34. package/ts_build/src/clients/xai.js +2 -2
  35. package/ts_build/src/clients/xai.js.map +1 -1
  36. package/ts_build/src/fileSync.js +8 -1
  37. package/ts_build/src/fileSync.js.map +1 -1
  38. package/ts_build/src/hashes.d.ts +2 -0
  39. package/ts_build/src/hashes.js +23 -0
  40. package/ts_build/src/hashes.js.map +1 -1
  41. package/ts_build/src/services/AgentSyncFs.d.ts +5 -3
  42. package/ts_build/src/services/AgentSyncFs.js +17 -11
  43. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  44. package/ts_build/src/services/SyncedAgentWatcher.d.ts +0 -51
  45. package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
  46. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  47. package/ts_build/src/services/index.d.ts +1 -0
  48. package/ts_build/src/services/index.js +1 -0
  49. package/ts_build/src/services/index.js.map +1 -1
  50. package/ts_build/src/services/watchers/FsSyncer.d.ts +27 -0
  51. package/ts_build/src/services/watchers/FsSyncer.js +135 -0
  52. package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
  53. package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
  54. package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
  55. package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
  56. package/ts_build/src/services/watchers/index.d.ts +2 -0
  57. package/ts_build/src/services/watchers/index.js +19 -0
  58. package/ts_build/src/services/watchers/index.js.map +1 -0
  59. package/ts_build/src/types.d.ts +2 -2
  60. package/ts_build/src/types.js +2 -2
  61. package/ts_build/src/types.js.map +1 -1
@@ -5,11 +5,6 @@
5
5
 
6
6
  import { Message } from "../clients/types";
7
7
  import { EventService } from "./EventService";
8
- import * as fs from "fs";
9
- import * as fsPromises from "fs/promises";
10
- import * as path from "path";
11
- import { messagesToRenderEvents } from "../chat/renderer/messagesToRenderEvents";
12
- import { KnowhowSimpleClient } from "./KnowhowClient";
13
8
 
14
9
  export interface SyncedAgentWatcher {
15
10
  /** Start watching for changes, emitting agent events */
@@ -27,7 +22,13 @@ export interface SyncedAgentWatcher {
27
22
  /** EventService that emits agent lifecycle events (toolCall, toolUsed, agentSay, threadUpdate, done) */
28
23
  agentEvents: EventService;
29
24
  /** Event type constants mirroring BaseAgent.eventTypes */
30
- eventTypes: { done: string; toolCall: string; toolUsed: string; agentSay: string; threadUpdate: string };
25
+ eventTypes: {
26
+ done: string;
27
+ toolCall: string;
28
+ toolUsed: string;
29
+ agentSay: string;
30
+ threadUpdate: string;
31
+ };
31
32
  /** Pause the remote agent */
32
33
  pause(): Promise<void>;
33
34
  /** Unpause/resume the remote agent */
@@ -44,7 +45,12 @@ export interface SyncedAgentWatcher {
44
45
  export interface AttachableAgent {
45
46
  name: string;
46
47
  agentEvents: EventService;
47
- eventTypes: { done: string; toolCall?: string; toolUsed?: string; agentSay?: string };
48
+ eventTypes: {
49
+ done: string;
50
+ toolCall?: string;
51
+ toolUsed?: string;
52
+ agentSay?: string;
53
+ };
48
54
  getTotalCostUsd(): number;
49
55
  pause(): void | Promise<void>;
50
56
  unpause(): void | Promise<void>;
@@ -104,294 +110,3 @@ export class WatcherBackedAgent implements AttachableAgent {
104
110
  });
105
111
  }
106
112
  }
107
-
108
- /**
109
- * Watches an agent running in another process via the filesystem.
110
- * Reads .knowhow/processes/agents/<taskId>/metadata.json for changes.
111
- * Sends messages by writing to .knowhow/processes/agents/<taskId>/input.txt
112
- */
113
- export class FsSyncedAgentWatcher implements SyncedAgentWatcher {
114
- public taskId: string = "";
115
- private taskPath: string = "";
116
- private watcher: fs.FSWatcher | null = null;
117
- private lastThreadLength: number = 0;
118
- public agentName: string = "unknown";
119
- private debounceTimer: NodeJS.Timeout | null = null;
120
- public agentEvents = new EventService();
121
- public eventTypes = {
122
- done: "done",
123
- toolCall: "tool:pre_call",
124
- toolUsed: "tool:post_call",
125
- agentSay: "agent:say",
126
- threadUpdate: "thread_update",
127
- };
128
-
129
- async startWatching(taskId: string): Promise<void> {
130
- this.taskId = taskId;
131
- this.taskPath = path.join(".knowhow/processes/agents", taskId);
132
-
133
- // Load initial state to track current thread length (for delta rendering)
134
- const metadata = await this.readMetadata();
135
- if (metadata) {
136
- const threads: any[][] = metadata.threads || [];
137
- const lastThread = threads[threads.length - 1] || [];
138
- this.agentName = metadata.agentName || taskId;
139
- this.lastThreadLength = lastThread.length;
140
- }
141
-
142
- // Watch the directory for metadata.json changes
143
- try {
144
- this.watcher = fs.watch(this.taskPath, (event, filename) => {
145
- if (filename === "metadata.json" || filename === null) {
146
- // Debounce rapid file writes
147
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
148
- this.debounceTimer = setTimeout(() => {
149
- this.onMetadataChanged().catch(() => {});
150
- }, 200);
151
- }
152
- });
153
- } catch (err: any) {
154
- console.warn(`⚠️ Could not watch ${this.taskPath}: ${err.message}`);
155
- }
156
-
157
- console.log(`👁️ Watching fs-synced agent: ${taskId} (${this.agentName})`);
158
- console.log(
159
- ` Type /logs 20 to see recent messages, or type to send a message`
160
- );
161
- }
162
-
163
- private async onMetadataChanged(): Promise<void> {
164
- const metadata = await this.readMetadata();
165
- if (!metadata?.threads) return;
166
-
167
- const threads: any[][] = metadata.threads;
168
- const lastThread = threads[threads.length - 1] || [];
169
-
170
- // Only render NEW messages since last check
171
- const newMessages = lastThread.slice(this.lastThreadLength);
172
- if (newMessages.length > 0) {
173
- const renderEvents = messagesToRenderEvents(
174
- newMessages,
175
- this.taskId,
176
- this.agentName
177
- );
178
- for (const event of renderEvents) {
179
- if (event.type === "toolCall") {
180
- this.agentEvents.emit(this.eventTypes.toolCall, {
181
- toolCall: (event as any).toolCall,
182
- });
183
- } else if (event.type === "toolResult") {
184
- this.agentEvents.emit(this.eventTypes.toolUsed, {
185
- toolCall: (event as any).toolCall,
186
- functionResp: (event as any).result,
187
- });
188
- } else if (event.type === "agentMessage") {
189
- this.agentEvents.emit(this.eventTypes.agentSay, {
190
- message: (event as any).message,
191
- });
192
- }
193
- }
194
- this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
195
- this.lastThreadLength = lastThread.length;
196
- }
197
-
198
- // Emit done if the agent has completed and has a result
199
- const status = metadata.status;
200
- const result = metadata.result;
201
- if ((status === "completed" || status === "killed") && result != null) {
202
- this.stopWatching();
203
- this.agentEvents.emit(this.eventTypes.done, result);
204
- }
205
- }
206
-
207
- async sendMessage(message: string): Promise<void> {
208
- const inputPath = path.join(this.taskPath, "input.txt");
209
- await fsPromises.writeFile(inputPath, message, "utf8");
210
- }
211
-
212
- async getThreads(): Promise<any[][]> {
213
- const metadata = await this.readMetadata();
214
- return metadata?.threads || [];
215
- }
216
-
217
- stopWatching(): void {
218
- if (this.debounceTimer) {
219
- clearTimeout(this.debounceTimer);
220
- this.debounceTimer = null;
221
- }
222
- this.watcher?.close();
223
- this.watcher = null;
224
- console.log(`🔌 Stopped watching agent: ${this.taskId}`);
225
- }
226
-
227
- async pause(): Promise<void> {
228
- const statusPath = path.join(this.taskPath, "status.txt");
229
- await fsPromises.writeFile(statusPath, "paused", "utf8");
230
- console.log(`⏸️ Paused remote agent: ${this.taskId}`);
231
- }
232
-
233
- async unpause(): Promise<void> {
234
- const statusPath = path.join(this.taskPath, "status.txt");
235
- await fsPromises.writeFile(statusPath, "running", "utf8");
236
- console.log(`▶️ Unpaused remote agent: ${this.taskId}`);
237
- }
238
-
239
- async kill(): Promise<void> {
240
- const statusPath = path.join(this.taskPath, "status.txt");
241
- await fsPromises.writeFile(statusPath, "killed", "utf8");
242
- console.log(`🛑 Killed remote agent: ${this.taskId}`);
243
- }
244
-
245
- private async readMetadata(): Promise<any> {
246
- try {
247
- const metaPath = path.join(this.taskPath, "metadata.json");
248
- const content = await fsPromises.readFile(metaPath, "utf8");
249
- return JSON.parse(content);
250
- } catch {
251
- return null;
252
- }
253
- }
254
- }
255
-
256
- /**
257
- * Watches an agent running on Knowhow Web via polling the API.
258
- * Polls GET /tasks/<taskId> every 3 seconds for thread updates.
259
- * Sends messages via the client's sendMessageToAgent method.
260
- */
261
- export class WebSyncedAgentWatcher implements SyncedAgentWatcher {
262
- public taskId: string = "";
263
- private client: KnowhowSimpleClient;
264
- private pollInterval: NodeJS.Timeout | null = null;
265
- private lastThreadLength: number = 0;
266
- public agentName: string = "remote-agent";
267
- private stopped: boolean = false;
268
- public agentEvents = new EventService();
269
- public eventTypes = {
270
- done: "done",
271
- toolCall: "tool:pre_call",
272
- toolUsed: "tool:post_call",
273
- agentSay: "agent:say",
274
- threadUpdate: "thread_update",
275
- };
276
-
277
- constructor(client?: KnowhowSimpleClient) {
278
- this.client = client || new KnowhowSimpleClient();
279
- }
280
-
281
- async startWatching(taskId: string): Promise<void> {
282
- this.taskId = taskId;
283
- this.stopped = false;
284
-
285
- // Load initial state to track current thread length
286
- try {
287
- const details = await this.client.getTaskDetails(taskId);
288
- const threads: any[][] = details?.data?.threads || [];
289
- const lastThread = threads[threads.length - 1] || [];
290
- this.agentName = "remote-agent";
291
- this.lastThreadLength = lastThread.length;
292
- } catch (err: any) {
293
- console.warn(
294
- `⚠️ Could not load initial state for task ${taskId}: ${err.message}`
295
- );
296
- }
297
-
298
- // Poll every 3 seconds for updates
299
- this.pollInterval = setInterval(async () => {
300
- if (!this.stopped) {
301
- await this.onPoll().catch(() => {});
302
- }
303
- }, 3000);
304
-
305
- console.log(`🌐 Watching web-synced agent: ${taskId} (${this.agentName})`);
306
- console.log(
307
- ` Type /logs 20 to see recent messages, or type to send a message`
308
- );
309
- }
310
-
311
- private async onPoll(): Promise<void> {
312
- if (this.stopped) return;
313
- try {
314
- const details = await this.client.getTaskDetails(this.taskId);
315
- const threads: any[][] = details?.data?.threads || [];
316
- const lastThread = threads[threads.length - 1] || [];
317
-
318
- const newMessages = lastThread.slice(this.lastThreadLength);
319
- if (newMessages.length > 0) {
320
- const renderEvents = messagesToRenderEvents(
321
- newMessages,
322
- this.taskId,
323
- this.agentName
324
- );
325
- for (const event of renderEvents) {
326
- if (event.type === "toolCall") {
327
- this.agentEvents.emit(this.eventTypes.toolCall, {
328
- toolCall: (event as any).toolCall,
329
- });
330
- } else if (event.type === "toolResult") {
331
- this.agentEvents.emit(this.eventTypes.toolUsed, {
332
- toolCall: (event as any).toolCall,
333
- functionResp: (event as any).result,
334
- });
335
- } else if (event.type === "agentMessage") {
336
- this.agentEvents.emit(this.eventTypes.agentSay, {
337
- message: (event as any).message,
338
- });
339
- }
340
- }
341
- this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
342
- this.lastThreadLength = lastThread.length;
343
- }
344
-
345
- // Stop polling and emit done if task is complete with a result
346
- const status = details?.data?.status;
347
- const result = details?.data?.result;
348
- if (status === "completed" || status === "killed") {
349
- this.stopWatching();
350
- if (result != null) {
351
- this.agentEvents.emit(this.eventTypes.done, result);
352
- } else {
353
- console.log(`\n✅ Remote agent ${this.taskId} status: ${status} (no result)`);
354
- }
355
- }
356
- } catch {
357
- // Silently continue on poll errors
358
- }
359
- }
360
-
361
- async sendMessage(message: string): Promise<void> {
362
- await this.client.sendMessageToAgent(this.taskId, message);
363
- }
364
-
365
- async getThreads(): Promise<any[][]> {
366
- try {
367
- const details = await this.client.getTaskDetails(this.taskId);
368
- return details?.data?.threads || [];
369
- } catch {
370
- return [];
371
- }
372
- }
373
-
374
- stopWatching(): void {
375
- this.stopped = true;
376
- if (this.pollInterval) {
377
- clearInterval(this.pollInterval);
378
- this.pollInterval = null;
379
- }
380
- console.log(`🔌 Stopped watching web agent: ${this.taskId}`);
381
- }
382
-
383
- async pause(): Promise<void> {
384
- await this.client.pauseAgent(this.taskId);
385
- console.log(`⏸️ Paused remote web agent: ${this.taskId}`);
386
- }
387
-
388
- async unpause(): Promise<void> {
389
- await this.client.resumeAgent(this.taskId);
390
- console.log(`▶️ Unpaused remote web agent: ${this.taskId}`);
391
- }
392
-
393
- async kill(): Promise<void> {
394
- await this.client.killAgent(this.taskId);
395
- console.log(`🛑 Killed remote web agent: ${this.taskId}`);
396
- }
397
- }
@@ -32,6 +32,7 @@ export * from "./SessionManager";
32
32
  export * from "./TaskRegistry";
33
33
  export * from "./SyncedAgentWatcher";
34
34
  export * from "./SyncerService";
35
+ export * from "./watchers";
35
36
  export { Clients } from "../clients";
36
37
 
37
38
  let Singletons = {} as {
@@ -0,0 +1,155 @@
1
+ import fs from "fs";
2
+ import fsPromises from "fs/promises";
3
+ import path from "path";
4
+
5
+ import { SyncedAgentWatcher } from "../SyncedAgentWatcher";
6
+ import { messagesToRenderEvents } from "../../chat/renderer/messagesToRenderEvents";
7
+ import { EventService } from "../EventService";
8
+
9
+ /**
10
+ * Watches an agent running in another process via the filesystem.
11
+ * Reads .knowhow/processes/agents/<taskId>/metadata.json for changes.
12
+ * Sends messages by writing to .knowhow/processes/agents/<taskId>/input.txt
13
+ */
14
+ export class FsSyncedAgentWatcher implements SyncedAgentWatcher {
15
+ public taskId: string = "";
16
+ private taskPath: string = "";
17
+ private watcher: fs.FSWatcher | null = null;
18
+ private lastThreadLength: number = 0;
19
+ public agentName: string = "unknown";
20
+ private debounceTimer: NodeJS.Timeout | null = null;
21
+ public agentEvents = new EventService();
22
+ public eventTypes = {
23
+ done: "done",
24
+ toolCall: "tool:pre_call",
25
+ toolUsed: "tool:post_call",
26
+ agentSay: "agent:say",
27
+ threadUpdate: "thread_update",
28
+ };
29
+
30
+ async startWatching(taskId: string): Promise<void> {
31
+ this.taskId = taskId;
32
+ this.taskPath = path.join(".knowhow/processes/agents", taskId);
33
+
34
+ // Load initial state to track current thread length (for delta rendering)
35
+ const metadata = await this.readMetadata();
36
+ if (metadata) {
37
+ const threads: any[][] = metadata.threads || [];
38
+ const lastThread = threads[threads.length - 1] || [];
39
+ this.agentName = metadata.agentName || taskId;
40
+ this.lastThreadLength = lastThread.length;
41
+ }
42
+
43
+ // Watch the directory for metadata.json changes
44
+ try {
45
+ this.watcher = fs.watch(this.taskPath, (event, filename) => {
46
+ if (filename === "metadata.json" || filename === null) {
47
+ // Debounce rapid file writes
48
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
49
+ this.debounceTimer = setTimeout(() => {
50
+ this.onMetadataChanged().catch(() => {});
51
+ }, 200);
52
+ }
53
+ });
54
+ } catch (err: any) {
55
+ console.warn(`⚠️ Could not watch ${this.taskPath}: ${err.message}`);
56
+ }
57
+
58
+ console.log(`👁️ Watching fs-synced agent: ${taskId} (${this.agentName})`);
59
+ console.log(
60
+ ` Type /logs 20 to see recent messages, or type to send a message`
61
+ );
62
+ }
63
+
64
+ private async onMetadataChanged(): Promise<void> {
65
+ const metadata = await this.readMetadata();
66
+ if (!metadata?.threads) return;
67
+
68
+ const threads: any[][] = metadata.threads;
69
+ const lastThread = threads[threads.length - 1] || [];
70
+
71
+ // Only render NEW messages since last check
72
+ const newMessages = lastThread.slice(this.lastThreadLength);
73
+ if (newMessages.length > 0) {
74
+ const renderEvents = messagesToRenderEvents(
75
+ newMessages,
76
+ this.taskId,
77
+ this.agentName
78
+ );
79
+ for (const event of renderEvents) {
80
+ if (event.type === "toolCall") {
81
+ this.agentEvents.emit(this.eventTypes.toolCall, {
82
+ toolCall: (event as any).toolCall,
83
+ });
84
+ } else if (event.type === "toolResult") {
85
+ this.agentEvents.emit(this.eventTypes.toolUsed, {
86
+ toolCall: (event as any).toolCall,
87
+ functionResp: (event as any).result,
88
+ });
89
+ } else if (event.type === "agentMessage") {
90
+ this.agentEvents.emit(this.eventTypes.agentSay, {
91
+ message: (event as any).message,
92
+ });
93
+ }
94
+ }
95
+ this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
96
+ this.lastThreadLength = lastThread.length;
97
+ }
98
+
99
+ // Emit done if the agent has completed and has a result
100
+ const status = metadata.status;
101
+ const result = metadata.result;
102
+ if ((status === "completed" || status === "killed") && result != null) {
103
+ this.stopWatching();
104
+ this.agentEvents.emit(this.eventTypes.done, result);
105
+ }
106
+ }
107
+
108
+ async sendMessage(message: string): Promise<void> {
109
+ const inputPath = path.join(this.taskPath, "input.txt");
110
+ await fsPromises.writeFile(inputPath, message, "utf8");
111
+ }
112
+
113
+ async getThreads(): Promise<any[][]> {
114
+ const metadata = await this.readMetadata();
115
+ return metadata?.threads || [];
116
+ }
117
+
118
+ stopWatching(): void {
119
+ if (this.debounceTimer) {
120
+ clearTimeout(this.debounceTimer);
121
+ this.debounceTimer = null;
122
+ }
123
+ this.watcher?.close();
124
+ this.watcher = null;
125
+ console.log(`🔌 Stopped watching agent: ${this.taskId}`);
126
+ }
127
+
128
+ async pause(): Promise<void> {
129
+ const statusPath = path.join(this.taskPath, "status.txt");
130
+ await fsPromises.writeFile(statusPath, "paused", "utf8");
131
+ console.log(`⏸️ Paused remote agent: ${this.taskId}`);
132
+ }
133
+
134
+ async unpause(): Promise<void> {
135
+ const statusPath = path.join(this.taskPath, "status.txt");
136
+ await fsPromises.writeFile(statusPath, "running", "utf8");
137
+ console.log(`▶️ Unpaused remote agent: ${this.taskId}`);
138
+ }
139
+
140
+ async kill(): Promise<void> {
141
+ const statusPath = path.join(this.taskPath, "status.txt");
142
+ await fsPromises.writeFile(statusPath, "killed", "utf8");
143
+ console.log(`🛑 Killed remote agent: ${this.taskId}`);
144
+ }
145
+
146
+ private async readMetadata(): Promise<any> {
147
+ try {
148
+ const metaPath = path.join(this.taskPath, "metadata.json");
149
+ const content = await fsPromises.readFile(metaPath, "utf8");
150
+ return JSON.parse(content);
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,153 @@
1
+ import { Message } from "../../clients/types";
2
+ import { EventService } from "../EventService";
3
+ import * as fs from "fs";
4
+ import * as fsPromises from "fs/promises";
5
+ import * as path from "path";
6
+ import { messagesToRenderEvents } from "../../chat/renderer/messagesToRenderEvents";
7
+ import { KnowhowSimpleClient } from "../KnowhowClient";
8
+ import { SyncedAgentWatcher } from "../SyncedAgentWatcher";
9
+
10
+ /**
11
+ * Watches an agent running on Knowhow Web via polling the API.
12
+ * Polls GET /tasks/<taskId> every 3 seconds for thread updates.
13
+ * Sends messages via the client's sendMessageToAgent method.
14
+ */
15
+ export class WebSyncedAgentWatcher implements SyncedAgentWatcher {
16
+ public taskId: string = "";
17
+ private client: KnowhowSimpleClient;
18
+ private pollInterval: NodeJS.Timeout | null = null;
19
+ private lastThreadLength: number = 0;
20
+ public agentName: string = "remote-agent";
21
+ private stopped: boolean = false;
22
+ public agentEvents = new EventService();
23
+ public eventTypes = {
24
+ done: "done",
25
+ toolCall: "tool:pre_call",
26
+ toolUsed: "tool:post_call",
27
+ agentSay: "agent:say",
28
+ threadUpdate: "thread_update",
29
+ };
30
+
31
+ constructor(client?: KnowhowSimpleClient) {
32
+ this.client = client || new KnowhowSimpleClient();
33
+ }
34
+
35
+ async startWatching(taskId: string): Promise<void> {
36
+ this.taskId = taskId;
37
+ this.stopped = false;
38
+
39
+ // Load initial state to track current thread length
40
+ try {
41
+ const details = await this.client.getTaskDetails(taskId);
42
+ const threads: any[][] = details?.data?.threads || [];
43
+ const lastThread = threads[threads.length - 1] || [];
44
+ this.agentName = "remote-agent";
45
+ this.lastThreadLength = lastThread.length;
46
+ } catch (err: any) {
47
+ console.warn(
48
+ `⚠️ Could not load initial state for task ${taskId}: ${err.message}`
49
+ );
50
+ }
51
+
52
+ // Poll every 3 seconds for updates
53
+ this.pollInterval = setInterval(async () => {
54
+ if (!this.stopped) {
55
+ await this.onPoll().catch(() => {});
56
+ }
57
+ }, 3000);
58
+
59
+ console.log(`🌐 Watching web-synced agent: ${taskId} (${this.agentName})`);
60
+ console.log(
61
+ ` Type /logs 20 to see recent messages, or type to send a message`
62
+ );
63
+ }
64
+
65
+ private async onPoll(): Promise<void> {
66
+ if (this.stopped) return;
67
+ try {
68
+ const details = await this.client.getTaskDetails(this.taskId);
69
+ const threads: any[][] = details?.data?.threads || [];
70
+ const lastThread = threads[threads.length - 1] || [];
71
+
72
+ const newMessages = lastThread.slice(this.lastThreadLength);
73
+ if (newMessages.length > 0) {
74
+ const renderEvents = messagesToRenderEvents(
75
+ newMessages,
76
+ this.taskId,
77
+ this.agentName
78
+ );
79
+ for (const event of renderEvents) {
80
+ if (event.type === "toolCall") {
81
+ this.agentEvents.emit(this.eventTypes.toolCall, {
82
+ toolCall: (event as any).toolCall,
83
+ });
84
+ } else if (event.type === "toolResult") {
85
+ this.agentEvents.emit(this.eventTypes.toolUsed, {
86
+ toolCall: (event as any).toolCall,
87
+ functionResp: (event as any).result,
88
+ });
89
+ } else if (event.type === "agentMessage") {
90
+ this.agentEvents.emit(this.eventTypes.agentSay, {
91
+ message: (event as any).message,
92
+ });
93
+ }
94
+ }
95
+ this.agentEvents.emit(this.eventTypes.threadUpdate, lastThread);
96
+ this.lastThreadLength = lastThread.length;
97
+ }
98
+
99
+ // Stop polling and emit done if task is complete with a result
100
+ const status = details?.data?.status;
101
+ const result = details?.data?.result;
102
+ if (status === "completed" || status === "killed") {
103
+ this.stopWatching();
104
+ if (result != null) {
105
+ this.agentEvents.emit(this.eventTypes.done, result);
106
+ } else {
107
+ console.log(
108
+ `\n✅ Remote agent ${this.taskId} status: ${status} (no result)`
109
+ );
110
+ }
111
+ }
112
+ } catch {
113
+ // Silently continue on poll errors
114
+ }
115
+ }
116
+
117
+ async sendMessage(message: string): Promise<void> {
118
+ await this.client.sendMessageToAgent(this.taskId, message);
119
+ }
120
+
121
+ async getThreads(): Promise<any[][]> {
122
+ try {
123
+ const details = await this.client.getTaskDetails(this.taskId);
124
+ return details?.data?.threads || [];
125
+ } catch {
126
+ return [];
127
+ }
128
+ }
129
+
130
+ stopWatching(): void {
131
+ this.stopped = true;
132
+ if (this.pollInterval) {
133
+ clearInterval(this.pollInterval);
134
+ this.pollInterval = null;
135
+ }
136
+ console.log(`🔌 Stopped watching web agent: ${this.taskId}`);
137
+ }
138
+
139
+ async pause(): Promise<void> {
140
+ await this.client.pauseAgent(this.taskId);
141
+ console.log(`⏸️ Paused remote web agent: ${this.taskId}`);
142
+ }
143
+
144
+ async unpause(): Promise<void> {
145
+ await this.client.resumeAgent(this.taskId);
146
+ console.log(`▶️ Unpaused remote web agent: ${this.taskId}`);
147
+ }
148
+
149
+ async kill(): Promise<void> {
150
+ await this.client.killAgent(this.taskId);
151
+ console.log(`🛑 Killed remote web agent: ${this.taskId}`);
152
+ }
153
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./FsSyncer";
2
+ export * from "./RemoteSyncer";
package/src/types.ts CHANGED
@@ -218,14 +218,14 @@ export const Models = {
218
218
  GrokImagineVideo: "grok-imagine-video",
219
219
  },
220
220
  openai: {
221
- GPT_5_2: "gpt-5.2",
222
- GPT_5_1: "gpt-5.1",
223
221
  GPT_54: "gpt-5.4",
224
222
  GPT_54_Mini: "gpt-5.4-mini",
225
223
  GPT_54_Nano: "gpt-5.4-nano",
226
224
  GPT_54_Pro: "gpt-5.4-pro",
227
225
  GPT_53_Chat: "gpt-5.3-chat-latest",
228
226
  GPT_53_Codex: "gpt-5.3-codex",
227
+ GPT_5_2: "gpt-5.2",
228
+ GPT_5_1: "gpt-5.1",
229
229
  GPT_5: "gpt-5",
230
230
  GPT_5_Mini: "gpt-5-mini",
231
231
  GPT_5_Nano: "gpt-5-nano",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.103",
3
+ "version": "0.0.105",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {