@tyvm/knowhow 0.0.60 → 0.0.61
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/setup/setup.ts +9 -2
- package/src/agents/tools/textSearch.ts +4 -1
- package/src/chat/CliChatService.ts +5 -6
- package/src/chat/modules/SystemModule.ts +1 -1
- package/src/clients/anthropic.ts +12 -0
- package/src/config.ts +5 -0
- package/src/plugins/language.ts +4 -0
- package/src/services/Mcp.ts +1 -0
- package/src/services/Tools.ts +5 -3
- package/src/types.ts +1 -0
- package/src/utils/InputQueueManager.ts +119 -95
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/setup/setup.js +9 -2
- package/ts_build/src/agents/setup/setup.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.js +2 -1
- package/ts_build/src/agents/tools/textSearch.js.map +1 -1
- package/ts_build/src/chat/CliChatService.d.ts +1 -1
- package/ts_build/src/chat/CliChatService.js +6 -6
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/SystemModule.js +1 -1
- package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +12 -0
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/config.js +5 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/plugins/language.js +4 -0
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/Tools.js +1 -1
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/types.d.ts +1 -0
- package/ts_build/src/types.js +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/utils/InputQueueManager.d.ts +4 -1
- package/ts_build/src/utils/InputQueueManager.js +93 -78
- package/ts_build/src/utils/InputQueueManager.js.map +1 -1
package/package.json
CHANGED
|
@@ -38,15 +38,22 @@ export class SetupAgent extends BaseAgent {
|
|
|
38
38
|
|
|
39
39
|
Always ask the user to approve what you're going to do to the config, that way you can get feedback via askHuman before modifying the config
|
|
40
40
|
|
|
41
|
+
After using askHuman and them providing their feedback of what you'd like to do, only follow what they say. We want to make the minimum set of changes to the config.
|
|
42
|
+
|
|
43
|
+
For codebase embeddings you don't want to use prompt, as that'd embed a transformation of the code, you want to embed the actual source, so don't use prompt.
|
|
44
|
+
For embeddings prompt would only be used for generating an embedding from transformed data, like if you wanted to summarize a transcript and make embeddings from the summary, then you'd use prompt on the embeddings, otherwise you should not need it.
|
|
45
|
+
|
|
41
46
|
When setting up the language plugin for a user you should come up with phrases they're likely to say, like frontend/backend/schema etc that will signal we should load in guides or rules for that type of task. You should put any of your rules/analses in .knowhow/docs and the language plugin should reference those.
|
|
42
47
|
|
|
48
|
+
The language plugin can only read in files, not directories, so do not add entries to language plugin unless you've first written some markdown files to load in as guidance. The files loaded by the language plugin should give quick tips to any unusual things about the project, commands that should be run to rebuild any auto-generated code, quirks about codebase behavior etc.
|
|
49
|
+
|
|
43
50
|
If a user is vauge about setting up, you should give them some options of what all you could help them setup with a brief explanation of what those setups would enable.
|
|
44
51
|
|
|
45
52
|
Only suggest embeddings that include a folder path with many elements, ie src/**/*.ts, never suggest entries with one element
|
|
46
53
|
|
|
47
|
-
If a user is requesting help with setting up a coding project, you can look at their package.json to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
|
|
54
|
+
If a user is requesting help with setting up a coding project, you can look at their package.json, or language specific config to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
|
|
48
55
|
|
|
49
|
-
If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole
|
|
56
|
+
If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebase and linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
|
|
50
57
|
|
|
51
58
|
`,
|
|
52
59
|
},
|
|
@@ -3,7 +3,10 @@ import { execCommand } from "./execCommand";
|
|
|
3
3
|
|
|
4
4
|
export async function textSearch(searchTerm) {
|
|
5
5
|
try {
|
|
6
|
-
|
|
6
|
+
// Escape the search term for safe shell usage
|
|
7
|
+
// Replace single quotes with '\'' which closes quote, adds escaped quote, reopens quote
|
|
8
|
+
const escapedTerm = searchTerm.replace(/'/g, "'\\''");
|
|
9
|
+
const command = `ag -m 3 -Q '${escapedTerm}'`;
|
|
7
10
|
const output = await execCommand(command);
|
|
8
11
|
return output;
|
|
9
12
|
} catch (err) {
|
|
@@ -61,8 +61,8 @@ export class CliChatService implements ChatService {
|
|
|
61
61
|
try {
|
|
62
62
|
if (fs.existsSync(this.historyFile)) {
|
|
63
63
|
const historyData = fs.readFileSync(this.historyFile, "utf8");
|
|
64
|
-
const
|
|
65
|
-
this.inputHistory =
|
|
64
|
+
const parsedHistory: ChatHistory = JSON.parse(historyData);
|
|
65
|
+
this.inputHistory = parsedHistory.inputs || [];
|
|
66
66
|
}
|
|
67
67
|
} catch (error) {
|
|
68
68
|
console.error("Error loading input history:", error);
|
|
@@ -81,11 +81,11 @@ export class CliChatService implements ChatService {
|
|
|
81
81
|
fs.mkdirSync(dir, { recursive: true });
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const inputHistory: ChatHistory = {
|
|
85
85
|
inputs: this.inputHistory,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
fs.writeFileSync(this.historyFile, JSON.stringify(
|
|
88
|
+
fs.writeFileSync(this.historyFile, JSON.stringify(inputHistory, null, 2));
|
|
89
89
|
} catch (error) {
|
|
90
90
|
console.error("Error saving input history:", error);
|
|
91
91
|
}
|
|
@@ -154,6 +154,7 @@ export class CliChatService implements ChatService {
|
|
|
154
154
|
|
|
155
155
|
async processInput(input: string): Promise<boolean> {
|
|
156
156
|
// Note: Input is added to history via setOnNewHistoryEntry callback when user presses Enter
|
|
157
|
+
// Note: this actually sends all commands to modules, first to service takes it
|
|
157
158
|
|
|
158
159
|
// Check if input is a command
|
|
159
160
|
if (input.startsWith("/")) {
|
|
@@ -198,7 +199,6 @@ export class CliChatService implements ChatService {
|
|
|
198
199
|
async getInput(
|
|
199
200
|
prompt: string = "> ",
|
|
200
201
|
options: string[] = [],
|
|
201
|
-
chatHistory: any[] = []
|
|
202
202
|
): Promise<string> {
|
|
203
203
|
if (this.context.inputMethod) {
|
|
204
204
|
return await this.context.inputMethod.getInput(prompt);
|
|
@@ -277,7 +277,6 @@ export class CliChatService implements ChatService {
|
|
|
277
277
|
const input = await this.getInput(
|
|
278
278
|
promptText,
|
|
279
279
|
commandNames,
|
|
280
|
-
this.chatHistory
|
|
281
280
|
);
|
|
282
281
|
|
|
283
282
|
if (input.trim() === "") {
|
package/src/clients/anthropic.ts
CHANGED
|
@@ -325,12 +325,24 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
325
325
|
|
|
326
326
|
pricesPerMillion() {
|
|
327
327
|
return {
|
|
328
|
+
[Models.anthropic.Opus4_6]: {
|
|
329
|
+
input: 5.0,
|
|
330
|
+
cache_write: 6.25,
|
|
331
|
+
cache_hit: 0.5,
|
|
332
|
+
output: 25.0,
|
|
333
|
+
},
|
|
328
334
|
[Models.anthropic.Opus4_5]: {
|
|
329
335
|
input: 5.0,
|
|
330
336
|
cache_write: 6.25,
|
|
331
337
|
cache_hit: 0.5,
|
|
332
338
|
output: 25.0,
|
|
333
339
|
},
|
|
340
|
+
[Models.anthropic.Opus4_1]: {
|
|
341
|
+
input: 15.0,
|
|
342
|
+
cache_write: 18.75,
|
|
343
|
+
cache_hit: 1.5,
|
|
344
|
+
output: 75.0,
|
|
345
|
+
},
|
|
334
346
|
[Models.anthropic.Opus4]: {
|
|
335
347
|
input: 15.0,
|
|
336
348
|
cache_write: 18.75,
|
package/src/config.ts
CHANGED
|
@@ -52,6 +52,11 @@ const defaultConfig = {
|
|
|
52
52
|
prompt: "BasicEmbeddingExplainer",
|
|
53
53
|
chunkSize: 2000,
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
input: "src/**/*.ts",
|
|
57
|
+
output: ".knowhow/embeddings/code.json",
|
|
58
|
+
chunkSize: 2000,
|
|
59
|
+
},
|
|
55
60
|
],
|
|
56
61
|
embeddingModel: EmbeddingModels.openai.EmbeddingAda2,
|
|
57
62
|
|
package/src/plugins/language.ts
CHANGED
|
@@ -96,6 +96,10 @@ export class LanguagePlugin extends PluginBase implements Plugin {
|
|
|
96
96
|
if (!exists) {
|
|
97
97
|
return { filePath, content: `File ${filePath} does not exist` };
|
|
98
98
|
}
|
|
99
|
+
const stat = await fileStat(filePath);
|
|
100
|
+
if (stat.isDirectory()) {
|
|
101
|
+
throw new Error(`Cannot read directories: ${filePath}`);
|
|
102
|
+
}
|
|
99
103
|
const content = (await readFile(filePath, "utf8")).toString();
|
|
100
104
|
return { filePath, content };
|
|
101
105
|
})
|
package/src/services/Mcp.ts
CHANGED
package/src/services/Tools.ts
CHANGED
|
@@ -348,7 +348,7 @@ export class ToolsService {
|
|
|
348
348
|
|
|
349
349
|
const originalFunc = this.originalFunctions[name];
|
|
350
350
|
|
|
351
|
-
//
|
|
351
|
+
// Overrides basically replace the function entirely
|
|
352
352
|
const matchingOverride = this.findMatchingOverride(name);
|
|
353
353
|
if (matchingOverride) {
|
|
354
354
|
// Create a wrapper function that calls the override with correct arguments
|
|
@@ -361,7 +361,9 @@ export class ToolsService {
|
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
//
|
|
364
|
+
// Wrappers are like russian dolls,
|
|
365
|
+
// each wrapper gets the previous function as its "inner" function to call, and can modify arguments and return value as needed
|
|
366
|
+
// the first wrapper gets the original function, the second wrapper gets the first wrapper as its inner function, etc
|
|
365
367
|
const wrappers = this.findMatchingWrappers(name);
|
|
366
368
|
if (wrappers.length > 0) {
|
|
367
369
|
let wrappedFunction = originalFunc;
|
|
@@ -369,7 +371,7 @@ export class ToolsService {
|
|
|
369
371
|
// Apply wrappers in priority order
|
|
370
372
|
for (const wrapperReg of wrappers) {
|
|
371
373
|
const innerFunc = wrappedFunction;
|
|
372
|
-
wrappedFunction = ((args: any) => {
|
|
374
|
+
wrappedFunction = ((...args: any[]) => {
|
|
373
375
|
const toolDefinition = this.getTool(name);
|
|
374
376
|
return wrapperReg.wrapper(innerFunc, args, toolDefinition);
|
|
375
377
|
}).bind(this);
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,9 @@ type AskOptions = {
|
|
|
12
12
|
|
|
13
13
|
export class InputQueueManager {
|
|
14
14
|
private stack: AskOptions[] = [];
|
|
15
|
-
private
|
|
15
|
+
private static instance: InputQueueManager | null = null;
|
|
16
|
+
private static rl: readline.Interface | null = null;
|
|
17
|
+
private static keypressListenerSetup = false;
|
|
16
18
|
|
|
17
19
|
// We keep one "live" buffer shared across stacked questions
|
|
18
20
|
// (so typing is preserved when questions change)
|
|
@@ -31,6 +33,11 @@ export class InputQueueManager {
|
|
|
31
33
|
// This allows CliChatService to update inputHistory immediately
|
|
32
34
|
private onNewEntry?: OnNewHistoryEntry;
|
|
33
35
|
|
|
36
|
+
constructor() {
|
|
37
|
+
// Store the current instance as the singleton
|
|
38
|
+
InputQueueManager.instance = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
/**
|
|
35
42
|
* Set a callback to be notified when user enters a new history entry.
|
|
36
43
|
* This allows the caller to update their history source immediately.
|
|
@@ -40,9 +47,9 @@ export class InputQueueManager {
|
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
private ensureRl(): readline.Interface {
|
|
43
|
-
if (
|
|
50
|
+
if (InputQueueManager.rl) return InputQueueManager.rl;
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
InputQueueManager.rl = readline.createInterface({
|
|
46
53
|
input: process.stdin,
|
|
47
54
|
output: process.stdout,
|
|
48
55
|
terminal: true,
|
|
@@ -74,7 +81,7 @@ export class InputQueueManager {
|
|
|
74
81
|
});
|
|
75
82
|
|
|
76
83
|
// When user presses Enter, buffer the line for paste detection
|
|
77
|
-
|
|
84
|
+
InputQueueManager.rl.on("line", (line) => {
|
|
78
85
|
const current = this.peek();
|
|
79
86
|
if (!current) return;
|
|
80
87
|
|
|
@@ -84,25 +91,32 @@ export class InputQueueManager {
|
|
|
84
91
|
// Already in paste mode, add to buffer
|
|
85
92
|
this.pasteBuffer.push(line);
|
|
86
93
|
clearTimeout(this.pasteTimeout);
|
|
87
|
-
this.pasteTimeout = setTimeout(
|
|
94
|
+
this.pasteTimeout = setTimeout(
|
|
95
|
+
() => this.flushPasteBuffer(),
|
|
96
|
+
this.PASTE_DELAY_MS
|
|
97
|
+
);
|
|
88
98
|
return;
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
// Start paste detection mode - buffer this line and wait to see if more come
|
|
92
102
|
this.pasteBuffer.push(line);
|
|
93
|
-
this.pasteTimeout = setTimeout(
|
|
103
|
+
this.pasteTimeout = setTimeout(
|
|
104
|
+
() => this.flushPasteBuffer(),
|
|
105
|
+
this.PASTE_DELAY_MS
|
|
106
|
+
);
|
|
94
107
|
});
|
|
95
108
|
|
|
96
|
-
|
|
109
|
+
InputQueueManager.rl.on("close", () => {
|
|
97
110
|
// Flush any remaining paste buffer on close
|
|
98
111
|
if (this.pasteTimeout) {
|
|
99
112
|
clearTimeout(this.pasteTimeout);
|
|
100
113
|
this.flushPasteBuffer();
|
|
114
|
+
this.historyIndex = -1;
|
|
101
115
|
}
|
|
102
116
|
});
|
|
103
117
|
|
|
104
118
|
// Handle Ctrl+C (readline SIGINT)
|
|
105
|
-
|
|
119
|
+
InputQueueManager.rl.on("SIGINT", () => {
|
|
106
120
|
// If there's an active question, cancel it (like Esc)
|
|
107
121
|
if (this.stack.length > 0) {
|
|
108
122
|
const cancelled = this.stack.pop();
|
|
@@ -120,91 +134,98 @@ export class InputQueueManager {
|
|
|
120
134
|
|
|
121
135
|
// Capture keypresses for ESC + history nav while still using readline.
|
|
122
136
|
// Tab is handled by rl completer.
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.syncFromReadline();
|
|
143
|
-
|
|
144
|
-
// Any "real typing" should exit history mode
|
|
145
|
-
// (we'll treat left/right as not exiting; you can tweak)
|
|
146
|
-
const exitsHistoryMode =
|
|
147
|
-
key &&
|
|
148
|
-
(key.name === "return" ||
|
|
149
|
-
key.name === "enter" ||
|
|
150
|
-
key.name === "backspace" ||
|
|
151
|
-
(key.sequence &&
|
|
152
|
-
key.sequence.length === 1 &&
|
|
153
|
-
!key.ctrl &&
|
|
154
|
-
!key.meta));
|
|
155
|
-
if (exitsHistoryMode && this.historyIndex !== -1) {
|
|
156
|
-
this.historyIndex = -1;
|
|
157
|
-
this.savedLineBeforeHistory = "";
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Custom Up/Down history navigation using only passed-in history
|
|
161
|
-
if (key?.name === "up") {
|
|
162
|
-
const history = this.getHistory();
|
|
163
|
-
if (history.length === 0) return;
|
|
164
|
-
|
|
165
|
-
if (this.historyIndex === -1) {
|
|
166
|
-
// entering history mode: remember current typed text
|
|
167
|
-
this.savedLineBeforeHistory = this.currentLine;
|
|
137
|
+
// Only set up keypress listener once to avoid multiple handlers
|
|
138
|
+
if (!InputQueueManager.keypressListenerSetup) {
|
|
139
|
+
InputQueueManager.keypressListenerSetup = true;
|
|
140
|
+
readline.emitKeypressEvents(process.stdin);
|
|
141
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
142
|
+
|
|
143
|
+
process.stdin.on("keypress", (_str, key) => {
|
|
144
|
+
// Handle Ctrl+C in raw mode (some terminals deliver this here instead of SIGINT)
|
|
145
|
+
const instance = InputQueueManager.instance;
|
|
146
|
+
if (!instance) return;
|
|
147
|
+
|
|
148
|
+
if (key?.ctrl && key?.name === "c") {
|
|
149
|
+
if (instance.stack.length > 0) {
|
|
150
|
+
const cancelled = instance.stack.pop();
|
|
151
|
+
cancelled?.resolve("");
|
|
152
|
+
instance.currentLine = "";
|
|
153
|
+
}
|
|
154
|
+
instance.close();
|
|
155
|
+
process.exit(0);
|
|
168
156
|
}
|
|
169
157
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
158
|
+
// If RL is closed or nothing to ask, ignore
|
|
159
|
+
if (!InputQueueManager.rl || instance.stack.length === 0) return;
|
|
160
|
+
|
|
161
|
+
// Keep our buffer in sync with readline's live line
|
|
162
|
+
instance.syncFromReadline();
|
|
163
|
+
|
|
164
|
+
// Any "real typing" should exit history mode
|
|
165
|
+
// (we'll treat left/right as not exiting; you can tweak)
|
|
166
|
+
const exitsHistoryMode =
|
|
167
|
+
key &&
|
|
168
|
+
(key.name === "return" ||
|
|
169
|
+
key.name === "enter" ||
|
|
170
|
+
key.name === "backspace" ||
|
|
171
|
+
(key.sequence &&
|
|
172
|
+
key.sequence.length === 1 &&
|
|
173
|
+
!key.ctrl &&
|
|
174
|
+
!key.meta));
|
|
175
|
+
if (exitsHistoryMode && instance.historyIndex !== -1) {
|
|
176
|
+
instance.historyIndex = -1;
|
|
177
|
+
instance.savedLineBeforeHistory = "";
|
|
176
178
|
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (key?.name === "down") {
|
|
181
|
-
const history = this.getHistory();
|
|
182
|
-
if (history.length === 0) return;
|
|
183
179
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
180
|
+
// Custom Up/Down history navigation using only passed-in history
|
|
181
|
+
if (key?.name === "up") {
|
|
182
|
+
const history = instance.getHistory();
|
|
183
|
+
if (history.length === 0) return;
|
|
184
|
+
|
|
185
|
+
if (instance.historyIndex === -1) {
|
|
186
|
+
// entering history mode: remember current typed text
|
|
187
|
+
instance.savedLineBeforeHistory = instance.currentLine;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (instance.historyIndex < history.length - 1) {
|
|
191
|
+
instance.historyIndex++;
|
|
192
|
+
const index = history.length - 1 - instance.historyIndex;
|
|
193
|
+
const next = history[index] ?? "";
|
|
194
|
+
instance.replaceLine(next);
|
|
195
|
+
instance.currentLine = next;
|
|
196
|
+
}
|
|
190
197
|
return;
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
if (key?.name === "down") {
|
|
201
|
+
const history = instance.getHistory();
|
|
202
|
+
if (history.length === 0) return;
|
|
203
|
+
|
|
204
|
+
if (instance.historyIndex > 0) {
|
|
205
|
+
instance.historyIndex--;
|
|
206
|
+
const index = history.length - 1 - instance.historyIndex;
|
|
207
|
+
const next = history[index] ?? "";
|
|
208
|
+
instance.replaceLine(next);
|
|
209
|
+
instance.currentLine = next;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (instance.historyIndex === 0) {
|
|
214
|
+
// leave history mode, restore what user was typing
|
|
215
|
+
instance.historyIndex = -1;
|
|
216
|
+
const restore = instance.savedLineBeforeHistory ?? "";
|
|
217
|
+
instance.savedLineBeforeHistory = "";
|
|
218
|
+
instance.replaceLine(restore);
|
|
219
|
+
instance.currentLine = restore;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
return;
|
|
201
224
|
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
202
227
|
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
return this.rl;
|
|
228
|
+
return InputQueueManager.rl;
|
|
208
229
|
}
|
|
209
230
|
|
|
210
231
|
private flushPasteBuffer(): void {
|
|
@@ -238,6 +259,7 @@ export class InputQueueManager {
|
|
|
238
259
|
async ask(question: string, options: string[] = [], history: string[] = []) {
|
|
239
260
|
return new Promise<string>((resolve) => {
|
|
240
261
|
this.stack.push({ question, options, history, resolve });
|
|
262
|
+
this.historyIndex = -1; // reset history nav when a new question is asked
|
|
241
263
|
|
|
242
264
|
const rl = this.ensureRl();
|
|
243
265
|
|
|
@@ -260,8 +282,8 @@ export class InputQueueManager {
|
|
|
260
282
|
}
|
|
261
283
|
|
|
262
284
|
private syncFromReadline(): void {
|
|
263
|
-
if (!
|
|
264
|
-
this.currentLine = (
|
|
285
|
+
if (!InputQueueManager.rl) return;
|
|
286
|
+
this.currentLine = (InputQueueManager.rl as any).line ?? "";
|
|
265
287
|
}
|
|
266
288
|
|
|
267
289
|
private sanitizeHistoryEntry(value: string): string {
|
|
@@ -290,13 +312,13 @@ export class InputQueueManager {
|
|
|
290
312
|
}
|
|
291
313
|
|
|
292
314
|
private render(): void {
|
|
293
|
-
if (!
|
|
315
|
+
if (!InputQueueManager.rl) return;
|
|
294
316
|
const current = this.peek();
|
|
295
317
|
if (!current) return;
|
|
296
318
|
|
|
297
319
|
// Make prompt be the question (readline manages wrapping/cursor)
|
|
298
|
-
|
|
299
|
-
|
|
320
|
+
InputQueueManager.rl.setPrompt(current.question);
|
|
321
|
+
InputQueueManager.rl.prompt(true);
|
|
300
322
|
}
|
|
301
323
|
|
|
302
324
|
private renderTopOrClose(): void {
|
|
@@ -311,17 +333,17 @@ export class InputQueueManager {
|
|
|
311
333
|
|
|
312
334
|
this.render();
|
|
313
335
|
this.replaceLine(this.currentLine);
|
|
314
|
-
|
|
336
|
+
InputQueueManager.rl?.prompt(true);
|
|
315
337
|
}
|
|
316
338
|
|
|
317
339
|
private replaceLine(next: string): void {
|
|
318
|
-
if (!
|
|
340
|
+
if (!InputQueueManager.rl) return;
|
|
319
341
|
|
|
320
342
|
const safe = this.sanitizeHistoryEntry(next);
|
|
321
343
|
|
|
322
344
|
// Clear current line and write next input without affecting terminal scrollback
|
|
323
|
-
|
|
324
|
-
if (safe)
|
|
345
|
+
InputQueueManager.rl.write(null, { ctrl: true, name: "u" }); // Ctrl+U clears the line
|
|
346
|
+
if (safe) InputQueueManager.rl.write(safe);
|
|
325
347
|
}
|
|
326
348
|
|
|
327
349
|
/**
|
|
@@ -343,9 +365,11 @@ export class InputQueueManager {
|
|
|
343
365
|
}
|
|
344
366
|
|
|
345
367
|
private close(): void {
|
|
346
|
-
if (!
|
|
347
|
-
|
|
348
|
-
|
|
368
|
+
if (!InputQueueManager.rl) return;
|
|
369
|
+
InputQueueManager.rl.close();
|
|
370
|
+
InputQueueManager.rl = null;
|
|
371
|
+
// Note: We don't reset keypressListenerSetup because the listener stays attached to process.stdin
|
|
372
|
+
// and will continue to work for the next readline interface
|
|
349
373
|
|
|
350
374
|
if (process.stdin.isTTY) {
|
|
351
375
|
try {
|
package/ts_build/package.json
CHANGED
|
@@ -39,15 +39,22 @@ class SetupAgent extends base_1.BaseAgent {
|
|
|
39
39
|
|
|
40
40
|
Always ask the user to approve what you're going to do to the config, that way you can get feedback via askHuman before modifying the config
|
|
41
41
|
|
|
42
|
+
After using askHuman and them providing their feedback of what you'd like to do, only follow what they say. We want to make the minimum set of changes to the config.
|
|
43
|
+
|
|
44
|
+
For codebase embeddings you don't want to use prompt, as that'd embed a transformation of the code, you want to embed the actual source, so don't use prompt.
|
|
45
|
+
For embeddings prompt would only be used for generating an embedding from transformed data, like if you wanted to summarize a transcript and make embeddings from the summary, then you'd use prompt on the embeddings, otherwise you should not need it.
|
|
46
|
+
|
|
42
47
|
When setting up the language plugin for a user you should come up with phrases they're likely to say, like frontend/backend/schema etc that will signal we should load in guides or rules for that type of task. You should put any of your rules/analses in .knowhow/docs and the language plugin should reference those.
|
|
43
48
|
|
|
49
|
+
The language plugin can only read in files, not directories, so do not add entries to language plugin unless you've first written some markdown files to load in as guidance. The files loaded by the language plugin should give quick tips to any unusual things about the project, commands that should be run to rebuild any auto-generated code, quirks about codebase behavior etc.
|
|
50
|
+
|
|
44
51
|
If a user is vauge about setting up, you should give them some options of what all you could help them setup with a brief explanation of what those setups would enable.
|
|
45
52
|
|
|
46
53
|
Only suggest embeddings that include a folder path with many elements, ie src/**/*.ts, never suggest entries with one element
|
|
47
54
|
|
|
48
|
-
If a user is requesting help with setting up a coding project, you can look at their package.json to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
|
|
55
|
+
If a user is requesting help with setting up a coding project, you can look at their package.json, or language specific config to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
|
|
49
56
|
|
|
50
|
-
If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole
|
|
57
|
+
If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebase and linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
|
|
51
58
|
|
|
52
59
|
`,
|
|
53
60
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../src/agents/setup/setup.ts"],"names":[],"mappings":";;;;;;AACA,uCAAuD;AACvD,2CAA6C;AAC7C,gGAAiE;AACjE,iCAAkC;AAElC,MAAa,UAAW,SAAQ,gBAAS;IACvC,IAAI,GAAG,OAAO,CAAC;IACf,WAAW,GAAG,4CAA4C,CAAC;IAE3D,YAAY,OAAqB;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,mBAAmB,CAAC;YACvB,EAAE,KAAK,EAAE,WAAM,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;YAC1D;gBACE,KAAK,EAAE,WAAM,CAAC,MAAM,CAAC,WAAW;gBAChC,QAAQ,EAAE,QAAQ;aACnB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,oBAAW;;;;yCAIU,+BAAc
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../src/agents/setup/setup.ts"],"names":[],"mappings":";;;;;;AACA,uCAAuD;AACvD,2CAA6C;AAC7C,gGAAiE;AACjE,iCAAkC;AAElC,MAAa,UAAW,SAAQ,gBAAS;IACvC,IAAI,GAAG,OAAO,CAAC;IACf,WAAW,GAAG,4CAA4C,CAAC;IAE3D,YAAY,OAAqB;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,mBAAmB,CAAC;YACvB,EAAE,KAAK,EAAE,WAAM,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;YAC1D;gBACE,KAAK,EAAE,WAAM,CAAC,MAAM,CAAC,WAAW;gBAChC,QAAQ,EAAE,QAAQ;aACnB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,oBAAW;;;;yCAIU,+BAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2B9C;aACF;YACD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE;SACxB,CAAC;IACjB,CAAC;CACF;AAxDD,gCAwDC"}
|
|
@@ -5,7 +5,8 @@ const embeddings_1 = require("../../embeddings");
|
|
|
5
5
|
const execCommand_1 = require("./execCommand");
|
|
6
6
|
async function textSearch(searchTerm) {
|
|
7
7
|
try {
|
|
8
|
-
const
|
|
8
|
+
const escapedTerm = searchTerm.replace(/'/g, "'\\''");
|
|
9
|
+
const command = `ag -m 3 -Q '${escapedTerm}'`;
|
|
9
10
|
const output = await (0, execCommand_1.execCommand)(command);
|
|
10
11
|
return output;
|
|
11
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textSearch.js","sourceRoot":"","sources":["../../../../src/agents/tools/textSearch.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAC3D,+CAA4C;AAErC,KAAK,UAAU,UAAU,CAAC,UAAU;IACzC,IAAI;
|
|
1
|
+
{"version":3,"file":"textSearch.js","sourceRoot":"","sources":["../../../../src/agents/tools/textSearch.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAC3D,+CAA4C;AAErC,KAAK,UAAU,UAAU,CAAC,UAAU;IACzC,IAAI;QAGF,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,eAAe,WAAW,GAAG,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAW,EAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;KACf;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CACT,mEAAmE,CACpE,CAAC;QACF,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,IAAA,oCAAuB,GAAE,CAAC;QACnD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAC9C,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CACvD,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;KAChB;AACH,CAAC;AApBD,gCAoBC"}
|
|
@@ -27,7 +27,7 @@ export declare class CliChatService implements ChatService {
|
|
|
27
27
|
processInput(input: string): Promise<boolean>;
|
|
28
28
|
enableMode(name: string): void;
|
|
29
29
|
disableMode(name: string): void;
|
|
30
|
-
getInput(prompt?: string, options?: string[]
|
|
30
|
+
getInput(prompt?: string, options?: string[]): Promise<string>;
|
|
31
31
|
clearHistory(): void;
|
|
32
32
|
getChatHistory(): ChatInteraction[];
|
|
33
33
|
getInputHistory(): string[];
|
|
@@ -43,8 +43,8 @@ class CliChatService {
|
|
|
43
43
|
try {
|
|
44
44
|
if (fs_1.default.existsSync(this.historyFile)) {
|
|
45
45
|
const historyData = fs_1.default.readFileSync(this.historyFile, "utf8");
|
|
46
|
-
const
|
|
47
|
-
this.inputHistory =
|
|
46
|
+
const parsedHistory = JSON.parse(historyData);
|
|
47
|
+
this.inputHistory = parsedHistory.inputs || [];
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
@@ -58,10 +58,10 @@ class CliChatService {
|
|
|
58
58
|
if (!fs_1.default.existsSync(dir)) {
|
|
59
59
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
60
60
|
}
|
|
61
|
-
const
|
|
61
|
+
const inputHistory = {
|
|
62
62
|
inputs: this.inputHistory,
|
|
63
63
|
};
|
|
64
|
-
fs_1.default.writeFileSync(this.historyFile, JSON.stringify(
|
|
64
|
+
fs_1.default.writeFileSync(this.historyFile, JSON.stringify(inputHistory, null, 2));
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
67
|
console.error("Error saving input history:", error);
|
|
@@ -143,7 +143,7 @@ class CliChatService {
|
|
|
143
143
|
mode.active = false;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
async getInput(prompt = "> ", options = []
|
|
146
|
+
async getInput(prompt = "> ", options = []) {
|
|
147
147
|
if (this.context.inputMethod) {
|
|
148
148
|
return await this.context.inputMethod.getInput(prompt);
|
|
149
149
|
}
|
|
@@ -197,7 +197,7 @@ class CliChatService {
|
|
|
197
197
|
? `\nAsk knowhow ${this.context.currentAgent}: `
|
|
198
198
|
: `\nAsk knowhow: `;
|
|
199
199
|
try {
|
|
200
|
-
const input = await this.getInput(promptText, commandNames
|
|
200
|
+
const input = await this.getInput(promptText, commandNames);
|
|
201
201
|
if (input.trim() === "") {
|
|
202
202
|
continue;
|
|
203
203
|
}
|