@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.
- package/package.json +1 -1
- package/src/clients/index.ts +100 -6
- package/src/clients/pricing/index.ts +2 -0
- package/src/clients/pricing/models.ts +252 -0
- package/src/clients/pricing/types.ts +29 -0
- package/src/clients/pricing/xai.ts +7 -6
- package/src/clients/xai.ts +2 -2
- package/src/fileSync.ts +15 -2
- package/src/hashes.ts +33 -0
- package/src/services/AgentSyncFs.ts +20 -12
- package/src/services/SyncedAgentWatcher.ts +13 -298
- package/src/services/index.ts +1 -0
- package/src/services/watchers/FsSyncer.ts +155 -0
- package/src/services/watchers/RemoteSyncer.ts +153 -0
- package/src/services/watchers/index.ts +2 -0
- package/src/types.ts +2 -2
- package/ts_build/package.json +1 -1
- package/ts_build/src/clients/index.d.ts +8 -0
- package/ts_build/src/clients/index.js +57 -2
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +2 -0
- package/ts_build/src/clients/pricing/index.js +8 -1
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/models.d.ts +8 -0
- package/ts_build/src/clients/pricing/models.js +173 -0
- package/ts_build/src/clients/pricing/models.js.map +1 -0
- package/ts_build/src/clients/pricing/types.d.ts +21 -0
- package/ts_build/src/clients/pricing/types.js +3 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -0
- package/ts_build/src/clients/pricing/xai.d.ts +3 -8
- package/ts_build/src/clients/pricing/xai.js +4 -4
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/xai.d.ts +9 -5
- package/ts_build/src/clients/xai.js +2 -2
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/fileSync.js +8 -1
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +2 -0
- package/ts_build/src/hashes.js +23 -0
- package/ts_build/src/hashes.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +5 -3
- package/ts_build/src/services/AgentSyncFs.js +17 -11
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +0 -51
- package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
- package/ts_build/src/services/index.d.ts +1 -0
- package/ts_build/src/services/index.js +1 -0
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/watchers/FsSyncer.d.ts +27 -0
- package/ts_build/src/services/watchers/FsSyncer.js +135 -0
- package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
- package/ts_build/src/services/watchers/index.d.ts +2 -0
- package/ts_build/src/services/watchers/index.js +19 -0
- package/ts_build/src/services/watchers/index.js.map +1 -0
- package/ts_build/src/types.d.ts +2 -2
- package/ts_build/src/types.js +2 -2
- 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: {
|
|
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: {
|
|
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
|
-
}
|
package/src/services/index.ts
CHANGED
|
@@ -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
|
+
}
|
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",
|