@tyvm/knowhow 0.0.83 → 0.0.85
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 +4 -2
- package/src/agents/base/base.ts +72 -62
- package/src/agents/index.ts +30 -14
- package/src/agents/researcher/researcher.ts +1 -2
- package/src/agents/tools/aiClient.ts +48 -0
- package/src/agents/tools/list.ts +57 -0
- package/src/agents/tools/startAgentTask.ts +3 -1
- package/src/chat/CliChatService.ts +20 -4
- package/src/chat/modules/AgentModule.ts +399 -357
- package/src/chat/modules/CustomCommandsModule.ts +0 -1
- package/src/chat/modules/InternalChatModule.ts +18 -2
- package/src/chat/modules/RendererModule.ts +109 -0
- package/src/chat/modules/SessionsModule.ts +854 -0
- package/src/chat/modules/SetupModule.ts +6 -8
- package/src/chat/modules/index.ts +1 -0
- package/src/chat/renderer/CompactRenderer.ts +209 -0
- package/src/chat/renderer/ConsoleRenderer.ts +141 -0
- package/src/chat/renderer/FancyRenderer.ts +421 -0
- package/src/chat/renderer/index.ts +5 -0
- package/src/chat/renderer/loadRenderer.ts +314 -0
- package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
- package/src/chat/renderer/types.ts +88 -0
- package/src/chat/types.ts +5 -0
- package/src/chat.ts +69 -5
- package/src/cli.ts +24 -5
- package/src/clients/index.ts +91 -0
- package/src/clients/pricing/google.ts +81 -2
- package/src/clients/pricing/openai.ts +68 -0
- package/src/config.ts +15 -0
- package/src/plugins/AgentsMdPlugin.ts +1 -1
- package/src/plugins/GitPlugin.ts +20 -20
- package/src/plugins/PluginBase.ts +11 -0
- package/src/plugins/SkillsPlugin.ts +150 -0
- package/src/plugins/asana.ts +4 -4
- package/src/plugins/embedding.ts +3 -5
- package/src/plugins/exec.ts +3 -3
- package/src/plugins/figma.ts +3 -7
- package/src/plugins/github.ts +18 -29
- package/src/plugins/jira.ts +2 -2
- package/src/plugins/language.ts +4 -4
- package/src/plugins/linear.ts +4 -4
- package/src/plugins/notion.ts +6 -8
- package/src/plugins/plugins.ts +29 -3
- package/src/plugins/url.ts +2 -2
- package/src/plugins/vim.ts +4 -3
- package/src/services/AgentService.ts +17 -0
- package/src/services/AgentSyncFs.ts +3 -0
- package/src/services/EventService.ts +168 -27
- package/src/services/KnowhowClient.ts +1 -0
- package/src/services/SessionManager.ts +51 -1
- package/src/services/SyncedAgentWatcher.ts +397 -0
- package/src/services/SyncerService.ts +147 -0
- package/src/services/index.ts +2 -0
- package/src/services/modules/index.ts +14 -3
- package/src/types.ts +103 -5
- package/src/worker.ts +80 -2
- package/src/workers/auth/PasskeySetup.ts +185 -0
- package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
- package/src/workers/auth/types.ts +58 -0
- package/src/workers/tools/getChallenge.ts +33 -0
- package/src/workers/tools/index.ts +8 -0
- package/src/workers/tools/lock.ts +31 -0
- package/src/workers/tools/unlock.ts +116 -0
- package/tests/clients/pricing.test.ts +144 -0
- package/tests/unit/modules/moduleLoading.test.ts +226 -0
- package/tests/unit/plugins/pluginLoading.test.ts +151 -0
- package/ts_build/package.json +4 -2
- package/ts_build/src/agents/base/base.d.ts +4 -3
- package/ts_build/src/agents/base/base.js +54 -30
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/index.d.ts +3 -0
- package/ts_build/src/agents/index.js +21 -11
- package/ts_build/src/agents/index.js.map +1 -1
- package/ts_build/src/agents/researcher/researcher.js +1 -1
- package/ts_build/src/agents/researcher/researcher.js.map +1 -1
- package/ts_build/src/agents/tools/aiClient.d.ts +3 -0
- package/ts_build/src/agents/tools/aiClient.js +31 -1
- package/ts_build/src/agents/tools/aiClient.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +48 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.js +2 -1
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +16 -5
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
- package/ts_build/src/chat/modules/AgentModule.js +248 -258
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
- package/ts_build/src/chat/modules/RendererModule.js +76 -0
- package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
- package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
- package/ts_build/src/chat/modules/SessionsModule.js +582 -0
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
- package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
- package/ts_build/src/chat/modules/SetupModule.js +4 -6
- package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
- package/ts_build/src/chat/modules/index.d.ts +1 -0
- package/ts_build/src/chat/modules/index.js +3 -1
- package/ts_build/src/chat/modules/index.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/index.d.ts +5 -0
- package/ts_build/src/chat/renderer/index.js +29 -0
- package/ts_build/src/chat/renderer/index.js.map +1 -0
- package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
- package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
- package/ts_build/src/chat/renderer/types.d.ts +75 -0
- package/ts_build/src/chat/renderer/types.js +3 -0
- package/ts_build/src/chat/renderer/types.js.map +1 -0
- package/ts_build/src/chat/types.d.ts +5 -0
- package/ts_build/src/chat.js +46 -4
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +18 -5
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/gemini.d.ts +10 -10
- package/ts_build/src/clients/index.d.ts +10 -0
- package/ts_build/src/clients/index.js +58 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +10 -10
- package/ts_build/src/clients/pricing/google.js +74 -2
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +65 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +17 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
- package/ts_build/src/plugins/GitPlugin.js +20 -20
- package/ts_build/src/plugins/GitPlugin.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +1 -0
- package/ts_build/src/plugins/PluginBase.js +13 -0
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
- package/ts_build/src/plugins/SkillsPlugin.js +149 -0
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
- package/ts_build/src/plugins/asana.js +4 -4
- package/ts_build/src/plugins/asana.js.map +1 -1
- package/ts_build/src/plugins/embedding.js +3 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/exec.js +3 -3
- package/ts_build/src/plugins/exec.js.map +1 -1
- package/ts_build/src/plugins/figma.js +3 -3
- package/ts_build/src/plugins/figma.js.map +1 -1
- package/ts_build/src/plugins/github.js +18 -18
- package/ts_build/src/plugins/github.js.map +1 -1
- package/ts_build/src/plugins/jira.js +2 -2
- package/ts_build/src/plugins/jira.js.map +1 -1
- package/ts_build/src/plugins/language.js +4 -4
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/linear.js +4 -4
- package/ts_build/src/plugins/linear.js.map +1 -1
- package/ts_build/src/plugins/notion.js +6 -6
- package/ts_build/src/plugins/notion.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +3 -0
- package/ts_build/src/plugins/plugins.js +18 -3
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/url.js +2 -2
- package/ts_build/src/plugins/url.js.map +1 -1
- package/ts_build/src/plugins/vim.js +2 -2
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/services/AgentService.d.ts +3 -0
- package/ts_build/src/services/AgentService.js +7 -0
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
- package/ts_build/src/services/AgentSyncFs.js +2 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +25 -2
- package/ts_build/src/services/EventService.js +92 -14
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/SessionManager.d.ts +6 -0
- package/ts_build/src/services/SessionManager.js +39 -1
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
- package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
- package/ts_build/src/services/SyncerService.d.ts +30 -0
- package/ts_build/src/services/SyncerService.js +72 -0
- package/ts_build/src/services/SyncerService.js.map +1 -0
- package/ts_build/src/services/index.d.ts +2 -0
- package/ts_build/src/services/index.js +2 -0
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.js +10 -2
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +51 -2
- package/ts_build/src/types.js +73 -5
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +2 -0
- package/ts_build/src/worker.js +59 -4
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
- package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
- package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +36 -0
- package/ts_build/src/workers/auth/types.js +3 -0
- package/ts_build/src/workers/auth/types.js.map +1 -0
- package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
- package/ts_build/src/workers/tools/getChallenge.js +27 -0
- package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
- package/ts_build/src/workers/tools/index.d.ts +6 -0
- package/ts_build/src/workers/tools/index.js +10 -0
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/lock.d.ts +9 -0
- package/ts_build/src/workers/tools/lock.js +27 -0
- package/ts_build/src/workers/tools/lock.js.map +1 -0
- package/ts_build/src/workers/tools/unlock.d.ts +18 -0
- package/ts_build/src/workers/tools/unlock.js +78 -0
- package/ts_build/src/workers/tools/unlock.js.map +1 -0
- package/ts_build/tests/clients/pricing.test.d.ts +1 -0
- package/ts_build/tests/clients/pricing.test.js +90 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncedAgentWatcher - Watch agents running in other processes or on knowhow-web
|
|
3
|
+
* and emit agent events as messages come in, decoupled from rendering.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Message } from "../clients/types";
|
|
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
|
+
|
|
14
|
+
export interface SyncedAgentWatcher {
|
|
15
|
+
/** Start watching for changes, emitting agent events */
|
|
16
|
+
startWatching(taskId: string): Promise<void>;
|
|
17
|
+
/** Stop watching */
|
|
18
|
+
stopWatching(): void;
|
|
19
|
+
/** The task ID being watched */
|
|
20
|
+
taskId: string;
|
|
21
|
+
/** The agent name being watched */
|
|
22
|
+
agentName: string;
|
|
23
|
+
/** Send a message to the remote agent */
|
|
24
|
+
sendMessage(message: string): Promise<void>;
|
|
25
|
+
/** Get current threads (for replaying history via /logs) */
|
|
26
|
+
getThreads(): Promise<any[][]>;
|
|
27
|
+
/** EventService that emits agent lifecycle events (toolCall, toolUsed, agentSay, threadUpdate, done) */
|
|
28
|
+
agentEvents: EventService;
|
|
29
|
+
/** Event type constants mirroring BaseAgent.eventTypes */
|
|
30
|
+
eventTypes: { done: string; toolCall: string; toolUsed: string; agentSay: string; threadUpdate: string };
|
|
31
|
+
/** Pause the remote agent */
|
|
32
|
+
pause(): Promise<void>;
|
|
33
|
+
/** Unpause/resume the remote agent */
|
|
34
|
+
unpause(): Promise<void>;
|
|
35
|
+
/** Kill/terminate the remote agent */
|
|
36
|
+
kill(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A minimal agent-like interface used by attachedAgentChatLoop.
|
|
41
|
+
* Both BaseAgent and WatcherBackedAgent implement this, allowing the
|
|
42
|
+
* single attachedAgentChatLoop to handle both local and remote agents.
|
|
43
|
+
*/
|
|
44
|
+
export interface AttachableAgent {
|
|
45
|
+
name: string;
|
|
46
|
+
agentEvents: EventService;
|
|
47
|
+
eventTypes: { done: string; toolCall?: string; toolUsed?: string; agentSay?: string };
|
|
48
|
+
getTotalCostUsd(): number;
|
|
49
|
+
pause(): void | Promise<void>;
|
|
50
|
+
unpause(): void | Promise<void>;
|
|
51
|
+
kill(): void | Promise<void>;
|
|
52
|
+
addPendingUserMessage(message: Message): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adapts a SyncedAgentWatcher to the AttachableAgent interface so that
|
|
57
|
+
* attachedAgentChatLoop can drive both local agents and remote/watcher-backed
|
|
58
|
+
* agents through a single code path.
|
|
59
|
+
*
|
|
60
|
+
* - pause/unpause/kill delegate to the watcher
|
|
61
|
+
* - addPendingUserMessage calls watcher.sendMessage()
|
|
62
|
+
* - agentEvents and eventTypes are delegated to the watcher
|
|
63
|
+
* - "done" is emitted by the watcher when the remote agent completes
|
|
64
|
+
*/
|
|
65
|
+
export class WatcherBackedAgent implements AttachableAgent {
|
|
66
|
+
public name: string;
|
|
67
|
+
public agentEvents: EventService;
|
|
68
|
+
public eventTypes: { done: string };
|
|
69
|
+
|
|
70
|
+
constructor(public readonly watcher: SyncedAgentWatcher) {
|
|
71
|
+
this.name = watcher.agentName;
|
|
72
|
+
this.agentEvents = watcher.agentEvents;
|
|
73
|
+
this.eventTypes = watcher.eventTypes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getTotalCostUsd(): number {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async pause(): Promise<void> {
|
|
81
|
+
await this.watcher.pause();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async unpause(): Promise<void> {
|
|
85
|
+
await this.watcher.unpause();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async kill(): Promise<void> {
|
|
89
|
+
await this.watcher.kill();
|
|
90
|
+
// Signal the chat loop to exit
|
|
91
|
+
this.agentEvents.emit(this.eventTypes.done, "Agent killed");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
addPendingUserMessage(message: Message): void {
|
|
95
|
+
// Fire-and-forget — errors are logged but not surfaced
|
|
96
|
+
const text = typeof message.content === "string" ? message.content : "";
|
|
97
|
+
this.watcher
|
|
98
|
+
.sendMessage(text)
|
|
99
|
+
.then(() => {
|
|
100
|
+
console.log(`📨 Message sent to ${this.name}`);
|
|
101
|
+
})
|
|
102
|
+
.catch((err) => {
|
|
103
|
+
console.error(`❌ Failed to send message to ${this.name}:`, err);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncerService - Unified wrapper around AgentSyncFs and AgentSyncKnowhowWeb
|
|
3
|
+
* Hides complexity of choosing and managing sync backends.
|
|
4
|
+
*/
|
|
5
|
+
import { BaseAgent } from "../agents/base/base";
|
|
6
|
+
import { AgentSyncFs } from "./AgentSyncFs";
|
|
7
|
+
import { AgentSyncKnowhowWeb } from "./AgentSyncKnowhowWeb";
|
|
8
|
+
|
|
9
|
+
export interface SyncerOptions {
|
|
10
|
+
taskId: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
/** If provided → use web sync (unless syncFs is set) */
|
|
13
|
+
messageId?: string;
|
|
14
|
+
/** Force fs sync even if messageId is provided */
|
|
15
|
+
syncFs?: boolean;
|
|
16
|
+
/** Use an existing Knowhow task ID instead of creating a new one */
|
|
17
|
+
existingKnowhowTaskId?: string;
|
|
18
|
+
/** Agent name to persist in metadata.json */
|
|
19
|
+
agentName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AgentSyncer {
|
|
23
|
+
/** Set up sync for a new task. Returns the sync task ID */
|
|
24
|
+
createTask(options: SyncerOptions): Promise<string>;
|
|
25
|
+
/** Wire up event listeners on the agent */
|
|
26
|
+
setupAgentSync(agent: BaseAgent, taskId: string): Promise<void>;
|
|
27
|
+
/** Wait for all pending sync operations to complete */
|
|
28
|
+
waitForFinalization(): Promise<void>;
|
|
29
|
+
/** Reset state for next task */
|
|
30
|
+
reset(): void;
|
|
31
|
+
/** Whether this syncer is active (has been configured) */
|
|
32
|
+
isActive(): boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* SyncerService implements AgentSyncer by delegating to AgentSyncFs and/or AgentSyncKnowhowWeb.
|
|
37
|
+
*
|
|
38
|
+
* Decision logic:
|
|
39
|
+
* - Always sets up AgentSyncFs (primary local syncer)
|
|
40
|
+
* - If messageId is present AND syncFs is not forced AND no existingKnowhowTaskId → also sets up AgentSyncKnowhowWeb
|
|
41
|
+
*/
|
|
42
|
+
export class SyncerService implements AgentSyncer {
|
|
43
|
+
private fsSync: AgentSyncFs;
|
|
44
|
+
private webSync: AgentSyncKnowhowWeb;
|
|
45
|
+
private active: boolean = false;
|
|
46
|
+
private useWebSync: boolean = false;
|
|
47
|
+
private createdTaskId: string | undefined;
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
this.fsSync = new AgentSyncFs();
|
|
51
|
+
this.webSync = new AgentSyncKnowhowWeb();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create sync task(s) and return the primary task ID.
|
|
56
|
+
* The returned ID is the local (fs) task ID.
|
|
57
|
+
*/
|
|
58
|
+
async createTask(options: SyncerOptions): Promise<string> {
|
|
59
|
+
this.active = true;
|
|
60
|
+
this.useWebSync = false;
|
|
61
|
+
|
|
62
|
+
// Determine whether to use web sync
|
|
63
|
+
const shouldUseWebSync =
|
|
64
|
+
!!options.messageId &&
|
|
65
|
+
!options.syncFs &&
|
|
66
|
+
!options.existingKnowhowTaskId;
|
|
67
|
+
|
|
68
|
+
// Always create fs sync task first
|
|
69
|
+
console.log(
|
|
70
|
+
`📁 Using filesystem-based synchronization for task: ${options.taskId}`
|
|
71
|
+
);
|
|
72
|
+
const fsTaskId = await this.fsSync.createTask({
|
|
73
|
+
taskId: options.taskId,
|
|
74
|
+
prompt: options.prompt,
|
|
75
|
+
agentName: options.agentName,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Optionally create web sync task
|
|
79
|
+
if (shouldUseWebSync) {
|
|
80
|
+
const knowhowTaskId = await this.webSync.createChatTask({
|
|
81
|
+
messageId: options.messageId,
|
|
82
|
+
prompt: options.prompt,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (knowhowTaskId) {
|
|
86
|
+
this.useWebSync = true;
|
|
87
|
+
this.createdTaskId = knowhowTaskId;
|
|
88
|
+
console.log(`🌐 Web sync task created: ${knowhowTaskId}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return fsTaskId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Wire up event listeners for all active sync backends.
|
|
97
|
+
* @param agent - the agent to sync
|
|
98
|
+
* @param taskId - the fs task ID (returned by createTask)
|
|
99
|
+
*/
|
|
100
|
+
async setupAgentSync(agent: BaseAgent, taskId: string): Promise<void> {
|
|
101
|
+
await this.fsSync.setupAgentSync(agent, taskId);
|
|
102
|
+
|
|
103
|
+
if (this.useWebSync && this.createdTaskId) {
|
|
104
|
+
await this.webSync.setupAgentSync(agent, this.createdTaskId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Wait for finalization across all active sync backends.
|
|
110
|
+
*/
|
|
111
|
+
async waitForFinalization(): Promise<void> {
|
|
112
|
+
if (this.useWebSync) {
|
|
113
|
+
console.log("🎯 [SyncerService] Waiting for web sync finalization...");
|
|
114
|
+
await this.webSync.waitForFinalization();
|
|
115
|
+
console.log("🎯 [SyncerService] Web sync finalization complete");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log("🎯 [SyncerService] Waiting for fs sync finalization...");
|
|
119
|
+
await this.fsSync.waitForFinalization();
|
|
120
|
+
console.log("🎯 [SyncerService] Fs sync finalization complete");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reset both sync backends for the next task.
|
|
125
|
+
*/
|
|
126
|
+
reset(): void {
|
|
127
|
+
this.webSync.reset();
|
|
128
|
+
this.fsSync.reset();
|
|
129
|
+
this.active = false;
|
|
130
|
+
this.useWebSync = false;
|
|
131
|
+
this.createdTaskId = undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether this syncer has been configured for a task.
|
|
136
|
+
*/
|
|
137
|
+
isActive(): boolean {
|
|
138
|
+
return this.active;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Returns the Knowhow web task ID if one was created (for updating TaskInfo).
|
|
143
|
+
*/
|
|
144
|
+
getCreatedWebTaskId(): string | undefined {
|
|
145
|
+
return this.useWebSync ? this.createdTaskId : undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/services/index.ts
CHANGED
|
@@ -29,6 +29,8 @@ export * from "./AgentSyncKnowhowWeb";
|
|
|
29
29
|
export * from "./AgentSyncFs";
|
|
30
30
|
export * from "./SessionManager";
|
|
31
31
|
export * from "./TaskRegistry";
|
|
32
|
+
export * from "./SyncedAgentWatcher";
|
|
33
|
+
export * from "./SyncerService";
|
|
32
34
|
export { Clients } from "../clients";
|
|
33
35
|
|
|
34
36
|
let Singletons = {} as {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getConfig } from "../../config";
|
|
1
|
+
import { getConfig, getGlobalConfig } from "../../config";
|
|
2
2
|
import { KnowhowModule, ModuleContext } from "./types";
|
|
3
3
|
import { ToolsService } from "../Tools";
|
|
4
4
|
import { services } from "../";
|
|
@@ -25,9 +25,14 @@ export class ModulesService {
|
|
|
25
25
|
const pluginService = context.Plugins;
|
|
26
26
|
const clients = context.Clients;
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Load from global config (~/.knowhow/knowhow.json) first, then local config
|
|
29
|
+
const globalConfig = await getGlobalConfig();
|
|
30
|
+
const allModulePaths = [
|
|
31
|
+
...(globalConfig.modules || []),
|
|
32
|
+
...(config.modules || []),
|
|
33
|
+
];
|
|
29
34
|
|
|
30
|
-
for (const modulePath of
|
|
35
|
+
for (const modulePath of allModulePaths) {
|
|
31
36
|
const importedModule = require(modulePath) as KnowhowModule;
|
|
32
37
|
await importedModule.init({ config, cwd: process.cwd() });
|
|
33
38
|
|
|
@@ -49,5 +54,11 @@ export class ModulesService {
|
|
|
49
54
|
clients.registerModels(client.provider, client.models);
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
|
|
58
|
+
// Also load plugins directly from config's pluginPackages map
|
|
59
|
+
if (pluginService) {
|
|
60
|
+
await pluginService.loadPluginsFromConfig(config);
|
|
61
|
+
await pluginService.loadPluginsFromConfig(globalConfig);
|
|
62
|
+
}
|
|
52
63
|
}
|
|
53
64
|
}
|