@tyvm/knowhow 0.0.130 → 0.0.132
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/agents/configurable/ConfigAgent.ts +0 -1
- package/src/agents/tools/startAgentTask.ts +17 -13
- package/src/ai.ts +55 -24
- package/src/chat/CliChatService.ts +7 -1
- package/src/chat/modules/AgentModule.ts +72 -14
- package/src/chat/renderer/PlainRenderer.ts +218 -0
- package/src/chat/renderer/index.ts +1 -0
- package/src/chat/renderer/loadRenderer.ts +2 -0
- package/src/chat.ts +2 -2
- package/src/cli.ts +23 -0
- package/src/clients/anthropic.ts +9 -1
- package/src/clients/fireworks.ts +10 -1
- package/src/clients/index.ts +6 -3
- package/src/clients/meta.ts +9 -0
- package/src/clients/pricing/anthropic.ts +11 -0
- package/src/clients/pricing/fireworks.ts +12 -0
- package/src/commands/agent.ts +53 -20
- package/src/commands/agents.ts +556 -0
- package/src/commands/hash.ts +170 -0
- package/src/commands/misc.ts +157 -3
- package/src/commands/skills.ts +36 -0
- package/src/config.ts +54 -2
- package/src/generate.ts +507 -0
- package/src/index.ts +32 -216
- package/src/services/BehaviorsService.ts +32 -0
- package/src/services/SkillsService.ts +198 -0
- package/src/types.ts +36 -4
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
- package/ts_build/src/agents/tools/startAgentTask.js +13 -12
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/ai.d.ts +10 -4
- package/ts_build/src/ai.js +27 -25
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/chat/CliChatService.d.ts +1 -1
- package/ts_build/src/chat/CliChatService.js +4 -1
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +61 -4
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
- package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
- package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/index.d.ts +1 -0
- package/ts_build/src/chat/renderer/index.js +3 -1
- package/ts_build/src/chat/renderer/index.js.map +1 -1
- package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
- package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
- package/ts_build/src/chat.d.ts +1 -1
- package/ts_build/src/chat.js +2 -2
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +16 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +4 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/fireworks.js +7 -0
- package/ts_build/src/clients/fireworks.js.map +1 -1
- package/ts_build/src/clients/index.js +5 -1
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/meta.d.ts +2 -0
- package/ts_build/src/clients/meta.js +5 -0
- package/ts_build/src/clients/meta.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +3 -0
- package/ts_build/src/clients/pricing/anthropic.js +6 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
- package/ts_build/src/clients/pricing/fireworks.js +5 -1
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/commands/agent.js +44 -17
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/agents.d.ts +2 -0
- package/ts_build/src/commands/agents.js +460 -0
- package/ts_build/src/commands/agents.js.map +1 -0
- package/ts_build/src/commands/hash.d.ts +2 -0
- package/ts_build/src/commands/hash.js +150 -0
- package/ts_build/src/commands/hash.js.map +1 -0
- package/ts_build/src/commands/misc.js +108 -2
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/skills.js +24 -0
- package/ts_build/src/commands/skills.js.map +1 -1
- package/ts_build/src/config.js +37 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/generate.d.ts +16 -0
- package/ts_build/src/generate.js +304 -0
- package/ts_build/src/generate.js.map +1 -0
- package/ts_build/src/index.d.ts +1 -3
- package/ts_build/src/index.js +9 -118
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/services/BehaviorsService.d.ts +2 -0
- package/ts_build/src/services/BehaviorsService.js +24 -0
- package/ts_build/src/services/BehaviorsService.js.map +1 -1
- package/ts_build/src/services/SkillsService.d.ts +7 -0
- package/ts_build/src/services/SkillsService.js +163 -0
- package/ts_build/src/services/SkillsService.js.map +1 -1
- package/ts_build/src/types.d.ts +14 -4
- package/ts_build/src/types.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AgentContext, BaseAgent } from "../base/base";
|
|
2
2
|
import { Message } from "../../clients/types";
|
|
3
3
|
import { Assistant, Config } from "../../types";
|
|
4
|
-
import { EventService, ToolsService } from "../../services";
|
|
5
4
|
import { MessageProcessor } from "../../services/MessageProcessor";
|
|
6
5
|
|
|
7
6
|
export class ConfigAgent extends BaseAgent {
|
|
@@ -3,7 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
|
|
6
|
-
interface StartAgentTaskParams {
|
|
6
|
+
export interface StartAgentTaskParams {
|
|
7
7
|
messageId?: string;
|
|
8
8
|
syncFs?: boolean;
|
|
9
9
|
taskId?: string;
|
|
@@ -14,6 +14,8 @@ interface StartAgentTaskParams {
|
|
|
14
14
|
agentName?: string;
|
|
15
15
|
maxTimeLimit?: number;
|
|
16
16
|
maxSpendLimit?: number;
|
|
17
|
+
/** When true, wait for the agent subprocess to exit before resolving (for generate pipelines). */
|
|
18
|
+
waitForCompletion?: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const PROCESSES_DIR = path.join(process.cwd(), ".knowhow", "processes");
|
|
@@ -63,6 +65,7 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
63
65
|
agentName,
|
|
64
66
|
maxTimeLimit,
|
|
65
67
|
maxSpendLimit,
|
|
68
|
+
waitForCompletion,
|
|
66
69
|
} = params;
|
|
67
70
|
if (!prompt) {
|
|
68
71
|
throw new Error("prompt is required to create a chat task");
|
|
@@ -156,24 +159,25 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
156
159
|
`To check status, read: ${agentTaskDir}/status.txt\n`
|
|
157
160
|
: "";
|
|
158
161
|
|
|
159
|
-
// Give the agent 5 seconds to finish before detaching
|
|
160
|
-
const detachTime = 5 * 1000;
|
|
161
|
-
const tid = setTimeout(() => {
|
|
162
|
-
try { child.unref(); } catch {}
|
|
163
|
-
done(
|
|
164
|
-
`Agent started (pid=${pid}), running in background.\n` +
|
|
165
|
-
`Logs: ${logPath}\n` +
|
|
166
|
-
syncFsNote
|
|
167
|
-
);
|
|
168
|
-
}, detachTime);
|
|
169
|
-
|
|
170
162
|
child.once("exit", (code) => {
|
|
171
|
-
clearTimeout(tid);
|
|
172
163
|
done(
|
|
173
164
|
`Agent finished with exit code ${code}.\nLogs: ${logPath}\n` +
|
|
174
165
|
syncFsNote
|
|
175
166
|
);
|
|
176
167
|
});
|
|
168
|
+
|
|
169
|
+
if (!waitForCompletion) {
|
|
170
|
+
// Give the agent 5 seconds to finish before detaching (fire-and-forget mode)
|
|
171
|
+
const detachTime = 5 * 1000;
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
try { child.unref(); } catch {}
|
|
174
|
+
done(
|
|
175
|
+
`Agent started (pid=${pid}), running in background.\n` +
|
|
176
|
+
`Logs: ${logPath}\n` +
|
|
177
|
+
syncFsNote
|
|
178
|
+
);
|
|
179
|
+
}, detachTime);
|
|
180
|
+
}
|
|
177
181
|
});
|
|
178
182
|
}
|
|
179
183
|
|
package/src/ai.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import { readFile } from "./utils";
|
|
3
3
|
|
|
4
|
+
/** Options forwarded to startAgentTask when a generation source has an `agent` field */
|
|
5
|
+
export type AgentOptions = {
|
|
6
|
+
syncFs?: boolean;
|
|
7
|
+
taskId?: string;
|
|
8
|
+
maxTimeLimit?: number;
|
|
9
|
+
maxSpendLimit?: number;
|
|
10
|
+
};
|
|
4
11
|
import OpenAI from "openai";
|
|
5
12
|
import { Assistant } from "./types";
|
|
6
13
|
import { convertToText } from "./conversion";
|
|
@@ -46,17 +53,24 @@ export function readPromptFile(promptFile: string, input: string) {
|
|
|
46
53
|
return input;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
export async function singlePrompt(
|
|
50
|
-
|
|
56
|
+
export async function singlePrompt(
|
|
57
|
+
userPrompt: string,
|
|
58
|
+
model = "",
|
|
59
|
+
agent = "",
|
|
60
|
+
agentOptions?: AgentOptions
|
|
61
|
+
) {
|
|
51
62
|
if (agent) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
// Route through the full AgentModule pipeline (renderer, sync-fs, limits).
|
|
64
|
+
// This ensures generate agent runs produce readable output and are observable
|
|
65
|
+
// via `knowhow agents list/tail/status`, matching `knowhow agent --input` behaviour.
|
|
66
|
+
const { startAgentTask } = await import("./agents/tools/startAgentTask");
|
|
67
|
+
return startAgentTask({
|
|
68
|
+
agentName: agent,
|
|
69
|
+
prompt: userPrompt,
|
|
70
|
+
model: model || undefined,
|
|
71
|
+
waitForCompletion: true,
|
|
72
|
+
...(agentOptions ?? {}),
|
|
73
|
+
});
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
if (!model) {
|
|
@@ -100,6 +114,11 @@ function isContextLengthError(err: any): boolean {
|
|
|
100
114
|
* Recursively summarize an array of texts using a split-and-summarize approach.
|
|
101
115
|
* When the combined texts exceed the context window (either by estimate or actual API error),
|
|
102
116
|
* split the array in half, summarize each half recursively, then combine.
|
|
117
|
+
*
|
|
118
|
+
* NOTE: when an `agent` is provided the recursive split path is skipped — agents
|
|
119
|
+
* can handle large inputs themselves (they can read files). Splitting would create
|
|
120
|
+
* multiple isolated agent runs each writing partial output. Instead the combined
|
|
121
|
+
* content is passed directly to the agent.
|
|
103
122
|
*/
|
|
104
123
|
async function summarizeTextsRecursive(
|
|
105
124
|
texts: string[],
|
|
@@ -107,7 +126,8 @@ async function summarizeTextsRecursive(
|
|
|
107
126
|
model: string,
|
|
108
127
|
agent: string,
|
|
109
128
|
contextLimit: number,
|
|
110
|
-
depth = 0
|
|
129
|
+
depth = 0,
|
|
130
|
+
agentOptions?: AgentOptions
|
|
111
131
|
): Promise<string> {
|
|
112
132
|
const indent = " ".repeat(depth);
|
|
113
133
|
|
|
@@ -115,7 +135,7 @@ async function summarizeTextsRecursive(
|
|
|
115
135
|
if (texts.length === 1) {
|
|
116
136
|
const content = template.replaceAll("{text}", texts[0]);
|
|
117
137
|
console.log(`${indent}summarizeTexts[depth=${depth}]: single text, ~${estimateTokens(content)} tokens`);
|
|
118
|
-
return singlePrompt(content, model, agent);
|
|
138
|
+
return singlePrompt(content, model, agent, agentOptions);
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
// Check if combined fits in context window by estimate
|
|
@@ -127,7 +147,7 @@ async function summarizeTextsRecursive(
|
|
|
127
147
|
// Try single combined prompt — if context error, fall through to split
|
|
128
148
|
console.log(`${indent}summarizeTexts[depth=${depth}]: ${texts.length} texts, ~${estimatedTokens} tokens, trying combined`);
|
|
129
149
|
try {
|
|
130
|
-
return await singlePrompt(combinedContent, model, agent);
|
|
150
|
+
return await singlePrompt(combinedContent, model, agent, agentOptions);
|
|
131
151
|
} catch (err: any) {
|
|
132
152
|
if (!isContextLengthError(err)) throw err;
|
|
133
153
|
console.log(`${indent}summarizeTexts[depth=${depth}]: API rejected (context too long), splitting in half`);
|
|
@@ -136,14 +156,22 @@ async function summarizeTextsRecursive(
|
|
|
136
156
|
console.log(`${indent}summarizeTexts[depth=${depth}]: ${texts.length} texts, ~${estimatedTokens} tokens exceeds limit, splitting in half`);
|
|
137
157
|
}
|
|
138
158
|
|
|
159
|
+
// When an agent is assigned, don't split — agents can handle large inputs and
|
|
160
|
+
// splitting would create multiple isolated agent runs that each write partial output.
|
|
161
|
+
// Instead, pass the combined content as-is even if it may exceed context limit.
|
|
162
|
+
if (agent) {
|
|
163
|
+
console.log(`${indent}summarizeTexts[depth=${depth}]: agent mode — skipping split, passing combined content to agent`);
|
|
164
|
+
return singlePrompt(combinedContent, model, agent, agentOptions);
|
|
165
|
+
}
|
|
166
|
+
|
|
139
167
|
// Split texts in half and recurse
|
|
140
168
|
const mid = Math.ceil(texts.length / 2);
|
|
141
169
|
const left = texts.slice(0, mid);
|
|
142
170
|
const right = texts.slice(mid);
|
|
143
171
|
|
|
144
172
|
const [leftSummary, rightSummary] = await Promise.all([
|
|
145
|
-
summarizeTextsRecursive(left, template, model, agent, contextLimit, depth + 1),
|
|
146
|
-
summarizeTextsRecursive(right, template, model, agent, contextLimit, depth + 1),
|
|
173
|
+
summarizeTextsRecursive(left, template, model, agent, contextLimit, depth + 1, agentOptions),
|
|
174
|
+
summarizeTextsRecursive(right, template, model, agent, contextLimit, depth + 1, agentOptions),
|
|
147
175
|
]);
|
|
148
176
|
|
|
149
177
|
// Combine the two halves with a final summary prompt
|
|
@@ -153,27 +181,28 @@ async function summarizeTextsRecursive(
|
|
|
153
181
|
console.log(`${indent}summarizeTexts[depth=${depth}]: combining halves, ~${finalEstimate} tokens`);
|
|
154
182
|
|
|
155
183
|
if (finalEstimate < contextLimit) {
|
|
156
|
-
return singlePrompt(finalContent, model, agent);
|
|
184
|
+
return singlePrompt(finalContent, model, agent, agentOptions);
|
|
157
185
|
}
|
|
158
186
|
|
|
159
187
|
// If even the combined summaries are too long, recurse one more level
|
|
160
|
-
return summarizeTextsRecursive([leftSummary, rightSummary], template, model, agent, contextLimit, depth + 1);
|
|
188
|
+
return summarizeTextsRecursive([leftSummary, rightSummary], template, model, agent, contextLimit, depth + 1, agentOptions);
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
export async function summarizeTexts(
|
|
164
192
|
texts: string[],
|
|
165
193
|
template: string,
|
|
166
194
|
model = "",
|
|
167
|
-
agent = ""
|
|
195
|
+
agent = "",
|
|
196
|
+
agentOptions?: AgentOptions
|
|
168
197
|
) {
|
|
169
198
|
const effectiveModel = model || Models.openai.GPT_54_Nano;
|
|
170
199
|
const contextLimit = getModelContextLimit(effectiveModel);
|
|
171
200
|
|
|
172
201
|
console.log(
|
|
173
|
-
`summarizeTexts: ${texts.length} text(s), context limit: ${contextLimit}, model: ${effectiveModel}`
|
|
202
|
+
`summarizeTexts: ${texts.length} text(s), context limit: ${contextLimit}, model: ${effectiveModel}${agent ? `, agent: ${agent}` : ""}`
|
|
174
203
|
);
|
|
175
204
|
|
|
176
|
-
return summarizeTextsRecursive(texts, template, model, agent, contextLimit).catch((err) => {
|
|
205
|
+
return summarizeTextsRecursive(texts, template, model, agent, contextLimit, 0, agentOptions).catch((err) => {
|
|
177
206
|
return `Texts of combined length ${texts.reduce((a, t) => a + t.length, 0)} could not be summarized due to error: ${err.message}`;
|
|
178
207
|
});
|
|
179
208
|
}
|
|
@@ -193,23 +222,25 @@ export async function summarizeFiles(
|
|
|
193
222
|
files: string[],
|
|
194
223
|
template: string,
|
|
195
224
|
model = "",
|
|
196
|
-
agent = ""
|
|
225
|
+
agent = "",
|
|
226
|
+
agentOptions?: AgentOptions
|
|
197
227
|
) {
|
|
198
228
|
const texts = [];
|
|
199
229
|
for (const file of files) {
|
|
200
230
|
const text = `file: ${file}\n` + (await convertToText(file));
|
|
201
231
|
texts.push(text);
|
|
202
232
|
}
|
|
203
|
-
return summarizeTexts(texts, template, model, agent);
|
|
233
|
+
return summarizeTexts(texts, template, model, agent, agentOptions);
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
export async function summarizeFile(
|
|
207
237
|
file: string,
|
|
208
238
|
template: string,
|
|
209
239
|
model = "",
|
|
210
|
-
agent = ""
|
|
240
|
+
agent = "",
|
|
241
|
+
agentOptions?: AgentOptions
|
|
211
242
|
) {
|
|
212
|
-
return await summarizeFiles([file], template, model, agent);
|
|
243
|
+
return await summarizeFiles([file], template, model, agent, agentOptions);
|
|
213
244
|
}
|
|
214
245
|
|
|
215
246
|
/*
|
|
@@ -336,12 +336,18 @@ export class CliChatService implements ChatService {
|
|
|
336
336
|
return fullPrompt;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
async startChatLoop(): Promise<void> {
|
|
339
|
+
async startChatLoop(initialCommand?: string): Promise<void> {
|
|
340
340
|
// Display available commands like the original
|
|
341
341
|
const availableCommands = this.getCommandsForActiveModes();
|
|
342
342
|
const commandNames = availableCommands.map((cmd) => `/${cmd.name}`);
|
|
343
343
|
console.log("Commands:", commandNames.join(", "));
|
|
344
344
|
|
|
345
|
+
// If an initial command was provided (e.g. from CLI), process it before
|
|
346
|
+
// entering the interactive loop so the user lands in the right state.
|
|
347
|
+
if (initialCommand) {
|
|
348
|
+
await this.processInput(initialCommand.trim());
|
|
349
|
+
}
|
|
350
|
+
|
|
345
351
|
while (true) {
|
|
346
352
|
// Recompute available commands each iteration so mode changes are reflected in autocomplete
|
|
347
353
|
const currentCommandNames = this.getCommandsForActiveModes().map(
|
|
@@ -733,22 +733,24 @@ export class AgentModule extends BaseChatModule {
|
|
|
733
733
|
run?: boolean; // whether to run immediately
|
|
734
734
|
taskId?: string; // optional pre-generated taskId
|
|
735
735
|
}) {
|
|
736
|
-
if (!agentConstructors[options.agentName as AgentName]) {
|
|
737
|
-
throw new Error(
|
|
738
|
-
`Agent "${
|
|
739
|
-
options.agentName
|
|
740
|
-
}" not found. Available agents: ${Object.keys(agentConstructors).join(
|
|
741
|
-
", "
|
|
742
|
-
)}`
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
736
|
const { input, chatHistory = [], agentName } = options;
|
|
747
737
|
const agentContext = services().Agents.getAgentContext();
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
738
|
+
|
|
739
|
+
// Resolve agent: built-in agents first, then fall back to ConfigAgents from knowhow.json
|
|
740
|
+
let agent: BaseAgent;
|
|
741
|
+
if (agentConstructors[options.agentName as AgentName]) {
|
|
742
|
+
agent = createAgent(options.agentName as AgentName, agentContext) as BaseAgent;
|
|
743
|
+
} else {
|
|
744
|
+
// Try to find a ConfigAgent registered via loadAgentsFromConfig (from knowhow.json agents[])
|
|
745
|
+
try {
|
|
746
|
+
const configAgent = services().Agents.getAgent(options.agentName);
|
|
747
|
+
agent = configAgent as unknown as BaseAgent;
|
|
748
|
+
} catch {
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Agent "${options.agentName}" not found. Available agents: ${Object.keys(agentConstructors).join(", ")} (plus any agents defined in knowhow.json)`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
752
754
|
|
|
753
755
|
let done = false;
|
|
754
756
|
let output = "Done";
|
|
@@ -915,7 +917,63 @@ export class AgentModule extends BaseChatModule {
|
|
|
915
917
|
agent.agentEvents.on(agent.eventTypes.agentLog, agentLogHandler);
|
|
916
918
|
agent.agentEvents.on(agent.eventTypes.agentStatus, agentStatusHandler);
|
|
917
919
|
|
|
920
|
+
// Wire tool call / tool result / agent message rendering so that all
|
|
921
|
+
// renderer types (including PlainRenderer for non-TTY) receive events
|
|
922
|
+
// even when running via the CLI `knowhow agent` command (which uses
|
|
923
|
+
// run:true and does NOT go through attachedAgentChatLoop / wireAgentRendering).
|
|
924
|
+
const toolCallHandler = (data: any) => {
|
|
925
|
+
this.renderer.render({
|
|
926
|
+
type: "toolCall",
|
|
927
|
+
taskId,
|
|
928
|
+
agentName: agent.name,
|
|
929
|
+
toolCall: data.toolCall,
|
|
930
|
+
});
|
|
931
|
+
};
|
|
932
|
+
const toolResultHandler = (data: any) => {
|
|
933
|
+
this.renderer.render({
|
|
934
|
+
type: "toolResult",
|
|
935
|
+
taskId,
|
|
936
|
+
agentName: agent.name,
|
|
937
|
+
toolCall: data.toolCall,
|
|
938
|
+
result: data.functionResp,
|
|
939
|
+
});
|
|
940
|
+
};
|
|
941
|
+
const agentSayHandler = (data: any) => {
|
|
942
|
+
this.renderer.render({
|
|
943
|
+
type: "agentMessage",
|
|
944
|
+
taskId,
|
|
945
|
+
agentName: agent.name,
|
|
946
|
+
message: data.message,
|
|
947
|
+
role: "assistant",
|
|
948
|
+
});
|
|
949
|
+
};
|
|
950
|
+
if (agent.eventTypes.toolCall) {
|
|
951
|
+
agent.agentEvents.on(agent.eventTypes.toolCall, toolCallHandler);
|
|
952
|
+
}
|
|
953
|
+
if (agent.eventTypes.toolUsed) {
|
|
954
|
+
agent.agentEvents.on(agent.eventTypes.toolUsed, toolResultHandler);
|
|
955
|
+
}
|
|
956
|
+
if (agent.eventTypes.agentSay) {
|
|
957
|
+
agent.agentEvents.on(agent.eventTypes.agentSay, agentSayHandler);
|
|
958
|
+
}
|
|
959
|
+
// Track the active task on the renderer so it doesn't filter out events
|
|
960
|
+
this.renderer.setActiveTaskId(taskId);
|
|
961
|
+
this.activeAgentTaskId = taskId;
|
|
962
|
+
|
|
918
963
|
const taskCompleted = new Promise<string>((resolve) => {
|
|
964
|
+
// Remove tool rendering listeners
|
|
965
|
+
if (agent.eventTypes.toolCall) {
|
|
966
|
+
agent.agentEvents.removeListener(agent.eventTypes.toolCall, toolCallHandler);
|
|
967
|
+
}
|
|
968
|
+
if (agent.eventTypes.toolUsed) {
|
|
969
|
+
agent.agentEvents.removeListener(agent.eventTypes.toolUsed, toolResultHandler);
|
|
970
|
+
}
|
|
971
|
+
if (agent.eventTypes.agentSay) {
|
|
972
|
+
agent.agentEvents.removeListener(agent.eventTypes.agentSay, agentSayHandler);
|
|
973
|
+
}
|
|
974
|
+
// Clear active task on renderer
|
|
975
|
+
this.renderer.setActiveTaskId(undefined);
|
|
976
|
+
this.activeAgentTaskId = undefined;
|
|
919
977
|
agent.agentEvents.once(agent.eventTypes.done, async (doneMsg) => {
|
|
920
978
|
console.log("🎯 [AgentModule] Task Completed");
|
|
921
979
|
done = true;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlainRenderer - A plain-text, append-only renderer for non-TTY environments.
|
|
3
|
+
*
|
|
4
|
+
* Used automatically when stdout is not a TTY (e.g. redirected to a file,
|
|
5
|
+
* piped, or run in background). Emits simple timestamped log lines with no
|
|
6
|
+
* ANSI codes, no cursor movement, and no spinner — safe for logfiles.
|
|
7
|
+
*
|
|
8
|
+
* Activate explicitly via:
|
|
9
|
+
* knowhow agent --renderer plain ...
|
|
10
|
+
* or it is selected automatically when !process.stdout.isTTY and no renderer
|
|
11
|
+
* is configured.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
AgentRenderer,
|
|
16
|
+
LogEvent,
|
|
17
|
+
AgentStatusEvent,
|
|
18
|
+
ToolCallEvent,
|
|
19
|
+
ToolResultEvent,
|
|
20
|
+
AgentMessageEvent,
|
|
21
|
+
AgentDoneEvent,
|
|
22
|
+
RenderEvent,
|
|
23
|
+
} from "./types";
|
|
24
|
+
import { EventEmitter } from "events";
|
|
25
|
+
|
|
26
|
+
function ts(): string {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function truncate(s: string, n: number): string {
|
|
31
|
+
return s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatArgs(raw: string): string {
|
|
35
|
+
try {
|
|
36
|
+
const obj = JSON.parse(raw || "{}");
|
|
37
|
+
const entries = Object.entries(obj);
|
|
38
|
+
if (!entries.length) return "{}";
|
|
39
|
+
return entries
|
|
40
|
+
.map(([k, v]) => {
|
|
41
|
+
const val = typeof v === "string" ? v : JSON.stringify(v);
|
|
42
|
+
return `${k}=${truncate(val, 80)}`;
|
|
43
|
+
})
|
|
44
|
+
.join(", ");
|
|
45
|
+
} catch {
|
|
46
|
+
return truncate(raw || "", 120);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatResult(result: any): string {
|
|
51
|
+
try {
|
|
52
|
+
const s = typeof result === "string" ? result : JSON.stringify(result);
|
|
53
|
+
return truncate(s.replace(/\s+/g, " "), 200);
|
|
54
|
+
} catch {
|
|
55
|
+
return truncate(String(result), 200);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class PlainRenderer implements AgentRenderer {
|
|
60
|
+
private activeTaskId: string | undefined;
|
|
61
|
+
private emitter = new EventEmitter();
|
|
62
|
+
private paused = false;
|
|
63
|
+
private bufferedEvents: RenderEvent[] = [];
|
|
64
|
+
|
|
65
|
+
setActiveTaskId(taskId: string | undefined): void {
|
|
66
|
+
this.activeTaskId = taskId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getActiveTaskId(): string | undefined {
|
|
70
|
+
return this.activeTaskId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private isActiveTask(taskId: string): boolean {
|
|
74
|
+
if (!this.activeTaskId) return true;
|
|
75
|
+
return taskId === this.activeTaskId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onLog(handler: (event: LogEvent) => void): void {
|
|
79
|
+
this.emitter.on("log", handler);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onAgentStatus(handler: (event: AgentStatusEvent) => void): void {
|
|
83
|
+
this.emitter.on("agentStatus", handler);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onToolCall(handler: (event: ToolCallEvent) => void): void {
|
|
87
|
+
this.emitter.on("toolCall", handler);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
onToolResult(handler: (event: ToolResultEvent) => void): void {
|
|
91
|
+
this.emitter.on("toolResult", handler);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onAgentMessage(handler: (event: AgentMessageEvent) => void): void {
|
|
95
|
+
this.emitter.on("agentMessage", handler);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onAgentDone(handler: (event: AgentDoneEvent) => void): void {
|
|
99
|
+
this.emitter.on("agentDone", handler);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pause(): void {
|
|
103
|
+
this.paused = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resume(): void {
|
|
107
|
+
this.paused = false;
|
|
108
|
+
const buffered = this.bufferedEvents.splice(0);
|
|
109
|
+
for (const event of buffered) {
|
|
110
|
+
this.render(event);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render(event: RenderEvent): void {
|
|
115
|
+
if (!this.isActiveTask(event.taskId)) return;
|
|
116
|
+
|
|
117
|
+
if (this.paused) {
|
|
118
|
+
this.bufferedEvents.push(event);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.emitter.emit(event.type, event);
|
|
123
|
+
|
|
124
|
+
switch (event.type) {
|
|
125
|
+
case "log":
|
|
126
|
+
this._log(event);
|
|
127
|
+
break;
|
|
128
|
+
case "agentStatus":
|
|
129
|
+
this._status(event);
|
|
130
|
+
break;
|
|
131
|
+
case "toolCall":
|
|
132
|
+
this._toolCall(event);
|
|
133
|
+
break;
|
|
134
|
+
case "toolResult":
|
|
135
|
+
this._toolResult(event);
|
|
136
|
+
break;
|
|
137
|
+
case "agentMessage":
|
|
138
|
+
this._message(event);
|
|
139
|
+
break;
|
|
140
|
+
case "agentDone":
|
|
141
|
+
this._done(event);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _write(line: string): void {
|
|
147
|
+
process.stdout.write(line + "\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _log(e: LogEvent): void {
|
|
151
|
+
const level = e.level.toUpperCase().padEnd(5);
|
|
152
|
+
this._write(`${ts()} [${level}] [${e.agentName}] ${e.message}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _status(e: AgentStatusEvent): void {
|
|
156
|
+
const cost = `$${e.details.totalCostUsd.toFixed(3)}`;
|
|
157
|
+
const elapsed = `${Math.floor(e.details.elapsedMs / 1000)}s`;
|
|
158
|
+
const parts = [`cost=${cost}`, `elapsed=${elapsed}`];
|
|
159
|
+
if (e.details.remainingTurns !== undefined) {
|
|
160
|
+
parts.push(`turns_left=${e.details.remainingTurns}`);
|
|
161
|
+
}
|
|
162
|
+
if (e.details.remainingTimeMs !== undefined) {
|
|
163
|
+
parts.push(`time_left=${Math.floor(e.details.remainingTimeMs / 1000)}s`);
|
|
164
|
+
}
|
|
165
|
+
this._write(
|
|
166
|
+
`${ts()} [STATUS] [${e.agentName}] ${e.statusMessage} | ${parts.join(" | ")}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private _toolCall(e: ToolCallEvent): void {
|
|
171
|
+
const args = formatArgs(e.toolCall.function.arguments);
|
|
172
|
+
this._write(
|
|
173
|
+
`${ts()} [TOOL->] [${e.agentName}] ${e.toolCall.function.name}(${args})`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private _toolResult(e: ToolResultEvent): void {
|
|
178
|
+
const result = formatResult(e.result);
|
|
179
|
+
this._write(
|
|
180
|
+
`${ts()} [TOOL<-] [${e.agentName}] ${e.toolCall.function.name} => ${result}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private _message(e: AgentMessageEvent): void {
|
|
185
|
+
if (e.role !== "assistant") return;
|
|
186
|
+
// Print the message with line-by-line prefix
|
|
187
|
+
const lines = e.message.split("\n");
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
if (line.trim()) {
|
|
190
|
+
this._write(`${ts()} [MSG ] [${e.agentName}] ${line}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private _done(e: AgentDoneEvent): void {
|
|
196
|
+
const cost = e.totalCost ? ` | total_cost=$${e.totalCost.toFixed(4)}` : "";
|
|
197
|
+
this._write(`${ts()} [DONE ] [${e.agentName}] Agent finished${cost}`);
|
|
198
|
+
// Print final output lines
|
|
199
|
+
const lines = e.output.split("\n");
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (line.trim()) {
|
|
202
|
+
this._write(`${ts()} [OUT ] [${e.agentName}] ${line}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
logMessages(events: RenderEvent[], count: number = 20): void {
|
|
208
|
+
const recent = events.slice(-count);
|
|
209
|
+
this._write(`\n[LOGS ] Last ${recent.length} events:`);
|
|
210
|
+
this._write("-".repeat(80));
|
|
211
|
+
for (const event of recent) {
|
|
212
|
+
this.render(event);
|
|
213
|
+
}
|
|
214
|
+
this._write("-".repeat(80));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default PlainRenderer;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { ConsoleRenderer } from "./ConsoleRenderer";
|
|
3
3
|
export { FancyRenderer } from "./FancyRenderer";
|
|
4
|
+
export { PlainRenderer } from "./PlainRenderer";
|
|
4
5
|
export { CompactRenderer } from "./CompactRenderer";
|
|
5
6
|
export { loadRenderer, loadRootModule, loadChatModule } from "./loadRenderer";
|
|
@@ -12,6 +12,7 @@ import path from "path";
|
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import { AgentRenderer } from "./types";
|
|
14
14
|
import { ConsoleRenderer } from "./ConsoleRenderer";
|
|
15
|
+
import { PlainRenderer } from "./PlainRenderer";
|
|
15
16
|
import { FancyRenderer } from "./FancyRenderer";
|
|
16
17
|
import { CompactRenderer } from "./CompactRenderer";
|
|
17
18
|
|
|
@@ -19,6 +20,7 @@ import { CompactRenderer } from "./CompactRenderer";
|
|
|
19
20
|
const BUILTIN_RENDERERS: Record<string, () => AgentRenderer> = {
|
|
20
21
|
basic: () => new ConsoleRenderer(),
|
|
21
22
|
fancy: () => new FancyRenderer(),
|
|
23
|
+
plain: () => new PlainRenderer(),
|
|
22
24
|
compact: () => new CompactRenderer(),
|
|
23
25
|
};
|
|
24
26
|
|
package/src/chat.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { getConfig } from "./config.js";
|
|
|
21
21
|
import { loadRenderer, loadRootModule, loadChatModule } from "./chat/renderer/loadRenderer.js";
|
|
22
22
|
import { AgentModule } from "./chat/modules/AgentModule.js";
|
|
23
23
|
|
|
24
|
-
async function main() {
|
|
24
|
+
async function main(initialCommand?: string) {
|
|
25
25
|
try {
|
|
26
26
|
// Load configuration and plugins
|
|
27
27
|
let config;
|
|
@@ -108,7 +108,7 @@ async function main() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Start the chat loop
|
|
111
|
-
await chatService.startChatLoop();
|
|
111
|
+
await chatService.startChatLoop(initialCommand);
|
|
112
112
|
} catch (error) {
|
|
113
113
|
console.error("Error starting chat:", error);
|
|
114
114
|
process.exit(1);
|
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node --no-node-snapshot
|
|
2
|
+
// Mark this process as running the Knowhow CLI (not just the SDK)
|
|
3
|
+
process.env.KNOWHOW_CLI = "1";
|
|
4
|
+
|
|
5
|
+
// Determine which warnings to show based on the command being run.
|
|
6
|
+
// Commands that don't need AI models or a config file should not spam warnings.
|
|
7
|
+
// Individual commands can override by setting KNOWHOW_WARN_NEEDS before parsing.
|
|
8
|
+
// Format: comma-separated list of "models" | "config" (or empty string to suppress all).
|
|
9
|
+
const _rawArgs = process.argv.slice(2);
|
|
10
|
+
const _firstArg = _rawArgs.find((a) => !a.startsWith("-"));
|
|
11
|
+
const COMMANDS_NEEDING_MODELS = new Set(["chat", "agent", "ask", "generate", "embed", "replay"]);
|
|
12
|
+
const COMMANDS_NEEDING_CONFIG = new Set([
|
|
13
|
+
"chat", "agent", "ask", "generate", "embed", "upload", "download",
|
|
14
|
+
"worker", "workers", "replay", "behaviors", "skills",
|
|
15
|
+
]);
|
|
16
|
+
const _needsModels = _firstArg && COMMANDS_NEEDING_MODELS.has(_firstArg) ? "models" : "";
|
|
17
|
+
const _needsConfig = _firstArg && COMMANDS_NEEDING_CONFIG.has(_firstArg) ? "config" : "";
|
|
18
|
+
const _needs = [_needsModels, _needsConfig].filter(Boolean).join(",");
|
|
19
|
+
process.env.KNOWHOW_WARN_NEEDS = process.env.KNOWHOW_WARN_NEEDS ?? _needs;
|
|
20
|
+
|
|
2
21
|
import { Command } from "commander";
|
|
3
22
|
import { version } from "../package.json";
|
|
4
23
|
import { logger } from "./logger";
|
|
@@ -40,6 +59,8 @@ import { addConvertCommand } from "./commands/convert";
|
|
|
40
59
|
import { addReplayCommand } from "./commands/replay";
|
|
41
60
|
import { addBehaviorsCommand } from "./commands/behaviors";
|
|
42
61
|
import { addSkillsCommand } from "./commands/skills";
|
|
62
|
+
import { addAgentsCommand } from "./commands/agents";
|
|
63
|
+
import { addHashCommand } from "./commands/hash";
|
|
43
64
|
|
|
44
65
|
// Handle unhandled promise rejections gracefully — particularly from MCP SDK
|
|
45
66
|
// which fires errors via event emitters that can bypass Promise.allSettled.
|
|
@@ -106,6 +127,8 @@ async function main() {
|
|
|
106
127
|
addReplayCommand(program);
|
|
107
128
|
addBehaviorsCommand(program);
|
|
108
129
|
addSkillsCommand(program);
|
|
130
|
+
addAgentsCommand(program);
|
|
131
|
+
addHashCommand(program);
|
|
109
132
|
|
|
110
133
|
// Load global modules early (before parse) so they can register CLI subcommands.
|
|
111
134
|
// We pass only the Program in context — no services are spun up at this stage.
|