@tyvm/knowhow 0.0.79 → 0.0.81
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/base/base.ts +27 -2
- package/src/agents/tools/startAgentTask.ts +24 -3
- package/src/chat/modules/AgentModule.ts +59 -0
- package/src/cli.ts +53 -0
- package/src/clients/http.ts +108 -67
- package/src/index.ts +11 -0
- package/src/login.ts +5 -5
- package/src/services/KnowhowClient.ts +35 -11
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +1 -1
- package/ts_build/src/agents/base/base.js +19 -2
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -0
- package/ts_build/src/agents/tools/startAgentTask.js +15 -2
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +9 -0
- package/ts_build/src/chat/modules/AgentModule.js +36 -0
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/cli.js +42 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/http.d.ts +1 -0
- package/ts_build/src/clients/http.js +87 -51
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/index.js +10 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/login.js +2 -2
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +8 -1
- package/ts_build/src/services/KnowhowClient.js +10 -1
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
package/package.json
CHANGED
package/src/agents/base/base.ts
CHANGED
|
@@ -480,7 +480,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
480
480
|
} as Message);
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
async call(userInput: string | MessageContent[], _messages?: Message[]) {
|
|
483
|
+
async call(userInput: string | MessageContent[], _messages?: Message[], retryCount = 0) {
|
|
484
484
|
if (this.status === this.eventTypes.notStarted) {
|
|
485
485
|
this.status = this.eventTypes.inProgress;
|
|
486
486
|
}
|
|
@@ -704,7 +704,32 @@ export abstract class BaseAgent implements IAgent {
|
|
|
704
704
|
} catch (e) {
|
|
705
705
|
if (e.toString().includes("429")) {
|
|
706
706
|
this.setNotHealthy();
|
|
707
|
-
return this.call(userInput, _messages);
|
|
707
|
+
return this.call(userInput, _messages, retryCount);
|
|
708
|
+
const errorStr = e.toString();
|
|
709
|
+
const isNonRetriable =
|
|
710
|
+
errorStr.includes("401") ||
|
|
711
|
+
errorStr.includes("403") ||
|
|
712
|
+
errorStr.includes("404");
|
|
713
|
+
|
|
714
|
+
const isRetriable =
|
|
715
|
+
!isNonRetriable &&
|
|
716
|
+
(errorStr.match(/5\d\d/) ||
|
|
717
|
+
errorStr.includes("Failed to get models") ||
|
|
718
|
+
errorStr.includes("timeout") ||
|
|
719
|
+
errorStr.includes("ECONNRESET") ||
|
|
720
|
+
errorStr.includes("ETIMEDOUT") ||
|
|
721
|
+
errorStr.includes("Invalid response format from MCP"));
|
|
722
|
+
|
|
723
|
+
if (isRetriable && retryCount < 3) {
|
|
724
|
+
const delay = 1000 * Math.pow(2, retryCount);
|
|
725
|
+
console.warn(
|
|
726
|
+
`Agent request failed (attempt ${retryCount + 1}/3), retrying in ${delay}ms...`,
|
|
727
|
+
e.message
|
|
728
|
+
);
|
|
729
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
730
|
+
return this.call(userInput, _messages, retryCount + 1);
|
|
731
|
+
}
|
|
732
|
+
|
|
708
733
|
}
|
|
709
734
|
|
|
710
735
|
console.error("Agent failed", e);
|
|
@@ -6,6 +6,8 @@ import { spawn } from "child_process";
|
|
|
6
6
|
interface StartAgentTaskParams {
|
|
7
7
|
messageId?: string;
|
|
8
8
|
syncFs?: boolean;
|
|
9
|
+
taskId?: string;
|
|
10
|
+
resume?: boolean;
|
|
9
11
|
prompt: string;
|
|
10
12
|
provider?: string;
|
|
11
13
|
model?: string;
|
|
@@ -51,6 +53,8 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
51
53
|
const {
|
|
52
54
|
messageId,
|
|
53
55
|
prompt,
|
|
56
|
+
taskId: providedTaskId,
|
|
57
|
+
resume,
|
|
54
58
|
syncFs,
|
|
55
59
|
provider,
|
|
56
60
|
model,
|
|
@@ -62,8 +66,8 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
62
66
|
throw new Error("prompt is required to create a chat task");
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
//
|
|
66
|
-
const taskId = generateTaskId(prompt);
|
|
69
|
+
// Use provided taskId if given, otherwise generate one from the prompt
|
|
70
|
+
const taskId = providedTaskId ?? generateTaskId(prompt);
|
|
67
71
|
const agentTaskDir = path.join(AGENTS_DIR, taskId);
|
|
68
72
|
|
|
69
73
|
// Build args array (no shell escaping needed - args are passed directly)
|
|
@@ -73,7 +77,10 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
73
77
|
args.push("--message-id", messageId);
|
|
74
78
|
} else if (syncFs) {
|
|
75
79
|
args.push("--sync-fs");
|
|
76
|
-
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (syncFs || providedTaskId) {
|
|
83
|
+
// Pass --task-id whenever we have a known taskId (syncFs or explicit taskId)
|
|
77
84
|
args.push("--task-id", taskId);
|
|
78
85
|
}
|
|
79
86
|
|
|
@@ -96,6 +103,10 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
|
|
|
96
103
|
if (maxSpendLimit !== undefined) {
|
|
97
104
|
args.push("--max-spend-limit", String(maxSpendLimit));
|
|
98
105
|
}
|
|
106
|
+
if (resume) {
|
|
107
|
+
// --resume is a boolean flag; task ID is already passed via --task-id above
|
|
108
|
+
args.push("--resume");
|
|
109
|
+
}
|
|
99
110
|
|
|
100
111
|
const timeoutMs = maxTimeLimit ? maxTimeLimit * 60 * 1000 : 60 * 60 * 1000;
|
|
101
112
|
|
|
@@ -211,6 +222,16 @@ export const startAgentTaskDefinition: Tool = {
|
|
|
211
222
|
type: "number",
|
|
212
223
|
description: "Cost limit for agent execution in dollars. Default: 10",
|
|
213
224
|
},
|
|
225
|
+
taskId: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description:
|
|
228
|
+
"Pre-generated task ID to use for this agent run. When provided with syncFs, the agent directory will use this ID for a predictable path. Required when using resume.",
|
|
229
|
+
},
|
|
230
|
+
resume: {
|
|
231
|
+
type: "boolean",
|
|
232
|
+
description:
|
|
233
|
+
"Resume a previously started task from where it left off. Must be used together with taskId which identifies the task to resume.",
|
|
234
|
+
},
|
|
214
235
|
},
|
|
215
236
|
required: ["prompt"],
|
|
216
237
|
},
|
|
@@ -732,6 +732,65 @@ Please continue from where you left off and complete the original request.
|
|
|
732
732
|
}
|
|
733
733
|
}
|
|
734
734
|
|
|
735
|
+
/**
|
|
736
|
+
* Resume an agent from a set of existing message threads
|
|
737
|
+
* Used by the CLI --resume flag to continue crashed/failed tasks
|
|
738
|
+
*/
|
|
739
|
+
public async resumeFromMessages(options: {
|
|
740
|
+
agentName: string;
|
|
741
|
+
input: string;
|
|
742
|
+
threads: any[][];
|
|
743
|
+
messageId?: string;
|
|
744
|
+
taskId?: string;
|
|
745
|
+
}): Promise<{ taskCompleted: Promise<string> }> {
|
|
746
|
+
const { agentName, input, threads, messageId, taskId } = options;
|
|
747
|
+
|
|
748
|
+
// Try to extract the original request from the first user message in threads
|
|
749
|
+
let originalRequest = "";
|
|
750
|
+
if (threads && threads.length > 0) {
|
|
751
|
+
const firstThread = threads[0];
|
|
752
|
+
if (Array.isArray(firstThread)) {
|
|
753
|
+
const firstUserMsg = firstThread.find(
|
|
754
|
+
(m: any) => m.role === "user" && m.content
|
|
755
|
+
);
|
|
756
|
+
if (firstUserMsg) {
|
|
757
|
+
originalRequest =
|
|
758
|
+
typeof firstUserMsg.content === "string"
|
|
759
|
+
? firstUserMsg.content
|
|
760
|
+
: JSON.stringify(firstUserMsg.content);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Build the resume prompt
|
|
766
|
+
const resumePrompt = [
|
|
767
|
+
"You are resuming a previously started task.",
|
|
768
|
+
originalRequest
|
|
769
|
+
? `ORIGINAL REQUEST: ${originalRequest}`
|
|
770
|
+
: "",
|
|
771
|
+
"Please continue from where you left off.",
|
|
772
|
+
input ? input : "",
|
|
773
|
+
]
|
|
774
|
+
.filter(Boolean)
|
|
775
|
+
.join("\n");
|
|
776
|
+
|
|
777
|
+
// Flatten threads into a single messages array for the agent
|
|
778
|
+
const flattenedMessages = threads.flat();
|
|
779
|
+
|
|
780
|
+
const result = await this.setupAgent({
|
|
781
|
+
agentName,
|
|
782
|
+
input: resumePrompt,
|
|
783
|
+
messageId,
|
|
784
|
+
existingKnowhowTaskId: taskId,
|
|
785
|
+
run: false,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// Start agent with prior messages as context
|
|
789
|
+
result.agent.call(resumePrompt, flattenedMessages as any);
|
|
790
|
+
|
|
791
|
+
return { taskCompleted: result.taskCompleted };
|
|
792
|
+
}
|
|
793
|
+
|
|
735
794
|
/**
|
|
736
795
|
* Get list of active agent tasks
|
|
737
796
|
*/
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node --no-node-snapshot
|
|
2
2
|
import "source-map-support/register";
|
|
3
3
|
import * as fs from "fs";
|
|
4
|
+
import * as fsPromises from "fs/promises";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import * as os from "os";
|
|
6
7
|
import { Command } from "commander";
|
|
@@ -209,9 +210,61 @@ async function main() {
|
|
|
209
210
|
.option("--task-id <taskId>", "Pre-generated task ID (used with --sync-fs for predictable agent directory path)")
|
|
210
211
|
.option("--prompt-file <path>", "Custom prompt template file with {text}")
|
|
211
212
|
.option("--input <text>", "Task input (fallback to stdin if not provided)")
|
|
213
|
+
.option("--resume", "Resume a previously started task using the --task-id (local FS or remote)")
|
|
212
214
|
.action(async (options) => {
|
|
213
215
|
try {
|
|
214
216
|
await setupServices();
|
|
217
|
+
|
|
218
|
+
// Handle --resume flag: load threads from local FS or remote using --task-id
|
|
219
|
+
if (options.resume && options.taskId) {
|
|
220
|
+
const resumeTaskId: string = options.taskId;
|
|
221
|
+
const localMetadataPath = path.join(
|
|
222
|
+
".knowhow",
|
|
223
|
+
"processes",
|
|
224
|
+
"agents",
|
|
225
|
+
resumeTaskId,
|
|
226
|
+
"metadata.json"
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
let threads: any[][] = [];
|
|
230
|
+
|
|
231
|
+
// Try local FS first
|
|
232
|
+
if (fs.existsSync(localMetadataPath)) {
|
|
233
|
+
try {
|
|
234
|
+
const raw = await fsPromises.readFile(localMetadataPath, "utf-8");
|
|
235
|
+
const metadata = JSON.parse(raw);
|
|
236
|
+
threads = metadata.threads || [];
|
|
237
|
+
console.log(`📁 Loaded threads from local FS: ${localMetadataPath}`);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.warn(`⚠️ Failed to parse local metadata: ${e.message}`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
// Try remote via KnowhowSimpleClient
|
|
243
|
+
try {
|
|
244
|
+
const client = new KnowhowSimpleClient();
|
|
245
|
+
threads = await client.getTaskThreads(resumeTaskId);
|
|
246
|
+
console.log(`🌐 Loaded threads from remote for task: ${resumeTaskId}`);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
console.warn(`⚠️ Could not load threads from remote: ${e.message}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const resumeInput =
|
|
253
|
+
options.input || "Please continue from where you left off.";
|
|
254
|
+
|
|
255
|
+
const agentModule = new AgentModule();
|
|
256
|
+
await agentModule.initialize(chatService);
|
|
257
|
+
const { taskCompleted } = await agentModule.resumeFromMessages({
|
|
258
|
+
agentName: options.agentName || "Patcher",
|
|
259
|
+
input: resumeInput,
|
|
260
|
+
threads,
|
|
261
|
+
messageId: options.messageId,
|
|
262
|
+
taskId: resumeTaskId,
|
|
263
|
+
});
|
|
264
|
+
await taskCompleted;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
215
268
|
let input = options.input;
|
|
216
269
|
|
|
217
270
|
// Only read from stdin if we don't have input and don't have a standalone prompt file
|
package/src/clients/http.ts
CHANGED
|
@@ -12,6 +12,41 @@ import path from "path";
|
|
|
12
12
|
export class HttpClient implements GenericClient {
|
|
13
13
|
constructor(private baseUrl: string, private headers = {}) {}
|
|
14
14
|
|
|
15
|
+
private async withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
|
|
16
|
+
let lastError: any;
|
|
17
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
return await fn();
|
|
20
|
+
} catch (e: any) {
|
|
21
|
+
lastError = e;
|
|
22
|
+
const errorStr = e.toString();
|
|
23
|
+
const isNonRetriable =
|
|
24
|
+
errorStr.includes("401") ||
|
|
25
|
+
errorStr.includes("403") ||
|
|
26
|
+
errorStr.includes("404") ||
|
|
27
|
+
errorStr.includes("429");
|
|
28
|
+
const isRetriable =
|
|
29
|
+
!isNonRetriable &&
|
|
30
|
+
(errorStr.match(/5\d\d/) ||
|
|
31
|
+
errorStr.includes("timeout") ||
|
|
32
|
+
errorStr.includes("ECONNRESET") ||
|
|
33
|
+
errorStr.includes("ETIMEDOUT") ||
|
|
34
|
+
errorStr.includes("Invalid response format from MCP") ||
|
|
35
|
+
errorStr.includes("Failed to get models"));
|
|
36
|
+
if (!isRetriable || attempt >= retries) {
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
const delay = 1000 * Math.pow(2, attempt);
|
|
40
|
+
console.warn(
|
|
41
|
+
`HTTP request failed (attempt ${attempt + 1}/${retries}), retrying in ${delay}ms...`,
|
|
42
|
+
e.message
|
|
43
|
+
);
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw lastError;
|
|
48
|
+
}
|
|
49
|
+
|
|
15
50
|
setJwt(jwt: string) {
|
|
16
51
|
this.headers = {
|
|
17
52
|
...this.headers,
|
|
@@ -43,85 +78,91 @@ export class HttpClient implements GenericClient {
|
|
|
43
78
|
async createChatCompletion(
|
|
44
79
|
options: CompletionOptions
|
|
45
80
|
): Promise<CompletionResponse> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
return this.withRetry(async () => {
|
|
82
|
+
const body = {
|
|
83
|
+
...options,
|
|
84
|
+
model: options.model,
|
|
85
|
+
messages: options.messages,
|
|
86
|
+
max_tokens: options.max_tokens || 3000,
|
|
87
|
+
|
|
88
|
+
...(options.tools && {
|
|
89
|
+
tools: options.tools,
|
|
90
|
+
tool_choice: "auto",
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const response = await axios.post(
|
|
95
|
+
`${this.baseUrl}/v1/chat/completions`,
|
|
96
|
+
body,
|
|
97
|
+
{
|
|
98
|
+
headers: this.headers,
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const data = response.data;
|
|
103
|
+
|
|
104
|
+
// Since this uses a keepalive, we need to detect 200 with error in body
|
|
105
|
+
if (data.error) {
|
|
106
|
+
throw new Error(JSON.stringify(data.error, null, 2));
|
|
63
107
|
}
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const data = response.data;
|
|
67
|
-
|
|
68
|
-
// Since this uses a keepalive, we need to detect 200 with error in body
|
|
69
|
-
if (data.error) {
|
|
70
|
-
throw new Error(JSON.stringify(data.error, null, 2));
|
|
71
|
-
}
|
|
72
108
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
return {
|
|
110
|
+
choices: data.choices.map((choice: any) => ({
|
|
111
|
+
message: {
|
|
112
|
+
role: choice.message.role,
|
|
113
|
+
content: choice.message.content,
|
|
114
|
+
tool_calls: choice.message.tool_calls,
|
|
115
|
+
},
|
|
116
|
+
})),
|
|
117
|
+
model: data.model,
|
|
118
|
+
usage: data.usage,
|
|
119
|
+
usd_cost: data.usd_cost,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
85
122
|
}
|
|
86
123
|
|
|
87
124
|
async createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse> {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
return this.withRetry(async () => {
|
|
126
|
+
const response = await axios.post(
|
|
127
|
+
`${this.baseUrl}/v1/embeddings`,
|
|
128
|
+
{
|
|
129
|
+
model: options.model,
|
|
130
|
+
input: options.input,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
headers: this.headers,
|
|
134
|
+
}
|
|
135
|
+
);
|
|
98
136
|
|
|
99
|
-
|
|
137
|
+
const data = response.data;
|
|
100
138
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
// Since this uses a keepalive, we need to detect 200 with error in body
|
|
140
|
+
if (data.error) {
|
|
141
|
+
throw new Error(JSON.stringify(data.error, null, 2));
|
|
142
|
+
}
|
|
105
143
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
144
|
+
return {
|
|
145
|
+
data: data.data,
|
|
146
|
+
model: options.model,
|
|
147
|
+
usage: data.usage,
|
|
148
|
+
usd_cost: data.usd_cost,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
112
151
|
}
|
|
113
152
|
|
|
114
153
|
async getModels() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
154
|
+
return this.withRetry(async () => {
|
|
155
|
+
const response = await axios.get(`${this.baseUrl}/v1/models`, {
|
|
156
|
+
headers: this.headers,
|
|
157
|
+
});
|
|
118
158
|
|
|
119
|
-
|
|
159
|
+
const data = response.data?.data;
|
|
120
160
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
return data.map((model: any) => ({
|
|
162
|
+
id: model.id,
|
|
163
|
+
object: model.object,
|
|
164
|
+
owned_by: model.owned_by,
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
126
167
|
}
|
|
127
168
|
}
|
package/src/index.ts
CHANGED
|
@@ -135,6 +135,14 @@ export async function upload() {
|
|
|
135
135
|
const url = await knowhowApiClient.getPresignedUploadUrl(source);
|
|
136
136
|
console.log("Uploading to", url);
|
|
137
137
|
await AwsS3.uploadToPresignedUrl(url, source.output);
|
|
138
|
+
// Sync config metadata back to the backend DB
|
|
139
|
+
await knowhowApiClient.updateEmbeddingMetadata(source.remoteId, {
|
|
140
|
+
inputGlob: source.input,
|
|
141
|
+
outputPath: source.output,
|
|
142
|
+
chunkSize: source.chunkSize,
|
|
143
|
+
remoteType: source.remoteType,
|
|
144
|
+
});
|
|
145
|
+
console.log("Synced metadata for", source.remoteId);
|
|
138
146
|
} else {
|
|
139
147
|
console.log(
|
|
140
148
|
"Skipping upload to",
|
|
@@ -376,6 +384,9 @@ export async function download() {
|
|
|
376
384
|
const preSignedUrl = await knowhowApiClient.getPresignedDownloadUrl(
|
|
377
385
|
source
|
|
378
386
|
);
|
|
387
|
+
// Ensure output directory exists
|
|
388
|
+
const outputDir = path.dirname(destinationPath);
|
|
389
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
379
390
|
await AwsS3.downloadFromPresignedUrl(preSignedUrl, destinationPath);
|
|
380
391
|
} else {
|
|
381
392
|
console.log("Unsupported remote type for", source.output);
|
package/src/login.ts
CHANGED
|
@@ -42,7 +42,11 @@ export async function login(jwtFlag?: boolean): Promise<void> {
|
|
|
42
42
|
// Get current user/org information
|
|
43
43
|
try {
|
|
44
44
|
const storedJwt = await loadJwt();
|
|
45
|
-
await checkJwt(storedJwt);
|
|
45
|
+
const { user, currentOrg } = await checkJwt(storedJwt);
|
|
46
|
+
|
|
47
|
+
console.log(
|
|
48
|
+
`Current user: ${user.email}, \nOrganization: ${currentOrg?.organization?.name} - ${currentOrg?.organization?.id}`
|
|
49
|
+
);
|
|
46
50
|
|
|
47
51
|
const config = await getConfig();
|
|
48
52
|
const proxyUrl = KNOWHOW_API_URL + "/api/proxy";
|
|
@@ -107,9 +111,5 @@ export async function checkJwt(storedJwt: string) {
|
|
|
107
111
|
return org.organizationId === orgId;
|
|
108
112
|
});
|
|
109
113
|
|
|
110
|
-
console.log(
|
|
111
|
-
`Current user: ${user.email}, \nOrganization: ${currentOrg?.organization?.name} - ${orgId}`
|
|
112
|
-
);
|
|
113
|
-
|
|
114
114
|
return { user, currentOrg };
|
|
115
115
|
}
|
|
@@ -53,7 +53,7 @@ export interface TaskDetailsResponse {
|
|
|
53
53
|
inProgress: boolean;
|
|
54
54
|
status: "running" | "paused" | "killed" | "completed";
|
|
55
55
|
totalUsdCost: number;
|
|
56
|
-
threads: any;
|
|
56
|
+
threads: any[][];
|
|
57
57
|
createdAt: string;
|
|
58
58
|
updatedAt: string;
|
|
59
59
|
messageId?: string;
|
|
@@ -144,9 +144,6 @@ export class KnowhowSimpleClient {
|
|
|
144
144
|
return org.organizationId === orgId;
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
console.log(
|
|
148
|
-
`Current user: ${user.email}, \nOrganization: ${currentOrg?.organization?.name} - ${orgId}`
|
|
149
|
-
);
|
|
150
147
|
} catch (error) {
|
|
151
148
|
throw new Error("Invalid JWT. Please login again.");
|
|
152
149
|
}
|
|
@@ -195,6 +192,25 @@ export class KnowhowSimpleClient {
|
|
|
195
192
|
return presignedUrl;
|
|
196
193
|
}
|
|
197
194
|
|
|
195
|
+
async updateEmbeddingMetadata(
|
|
196
|
+
id: string,
|
|
197
|
+
data: {
|
|
198
|
+
inputGlob?: string;
|
|
199
|
+
outputPath?: string;
|
|
200
|
+
chunkSize?: number;
|
|
201
|
+
remoteType?: string;
|
|
202
|
+
}
|
|
203
|
+
) {
|
|
204
|
+
await this.checkJwt();
|
|
205
|
+
return axios.put(
|
|
206
|
+
`${this.baseUrl}/api/org-embeddings/${id}`,
|
|
207
|
+
data,
|
|
208
|
+
{
|
|
209
|
+
headers: this.headers,
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
198
214
|
async createChatCompletion(options: CompletionOptions) {
|
|
199
215
|
await this.checkJwt();
|
|
200
216
|
return axios.post<CompletionResponse>(
|
|
@@ -434,6 +450,14 @@ export class KnowhowSimpleClient {
|
|
|
434
450
|
);
|
|
435
451
|
}
|
|
436
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Get threads from a task by task ID
|
|
455
|
+
*/
|
|
456
|
+
async getTaskThreads(taskId: string): Promise<any[][]> {
|
|
457
|
+
const response = await this.getTaskDetails(taskId);
|
|
458
|
+
return response.data.threads || [];
|
|
459
|
+
}
|
|
460
|
+
|
|
437
461
|
/**
|
|
438
462
|
* Kill/cancel a running or paused agent task
|
|
439
463
|
*/
|
|
@@ -557,13 +581,13 @@ export class KnowhowSimpleClient {
|
|
|
557
581
|
*/
|
|
558
582
|
async getOrgFilePresignedDownloadUrl(filePath: string): Promise<string> {
|
|
559
583
|
await this.checkJwt();
|
|
560
|
-
|
|
584
|
+
|
|
561
585
|
// Find the file by path
|
|
562
586
|
const file = await this.findOrgFileByPath(filePath);
|
|
563
587
|
if (!file) {
|
|
564
588
|
throw new Error(`File not found: ${filePath}`);
|
|
565
589
|
}
|
|
566
|
-
|
|
590
|
+
|
|
567
591
|
// Get download URL using the file ID
|
|
568
592
|
const response = await axios.post<{ downloadUrl: string }>(
|
|
569
593
|
`${this.baseUrl}/api/org-files/download/${file.id}`,
|
|
@@ -578,12 +602,12 @@ export class KnowhowSimpleClient {
|
|
|
578
602
|
*/
|
|
579
603
|
async markOrgFileUploadComplete(filePath: string): Promise<void> {
|
|
580
604
|
await this.checkJwt();
|
|
581
|
-
|
|
605
|
+
|
|
582
606
|
const file = await this.findOrgFileByPath(filePath);
|
|
583
607
|
if (!file) {
|
|
584
608
|
throw new Error(`File not found: ${filePath}`);
|
|
585
609
|
}
|
|
586
|
-
|
|
610
|
+
|
|
587
611
|
await axios.post(`${this.baseUrl}/api/org-files/upload/${file.id}/complete`, {}, { headers: this.headers });
|
|
588
612
|
}
|
|
589
613
|
|
|
@@ -593,14 +617,14 @@ export class KnowhowSimpleClient {
|
|
|
593
617
|
*/
|
|
594
618
|
async getOrgFilePresignedUploadUrl(filePath: string): Promise<string> {
|
|
595
619
|
await this.checkJwt();
|
|
596
|
-
|
|
620
|
+
|
|
597
621
|
// Find or create the file by path
|
|
598
622
|
const file = await this.findOrCreateOrgFileByPath(filePath);
|
|
599
|
-
|
|
623
|
+
|
|
600
624
|
// Extract just the filename from the path
|
|
601
625
|
const lastSlash = filePath.lastIndexOf("/");
|
|
602
626
|
const fileName = lastSlash >= 0 ? filePath.substring(lastSlash + 1) : filePath;
|
|
603
|
-
|
|
627
|
+
|
|
604
628
|
// Get upload URL using the file ID
|
|
605
629
|
const response = await axios.post<{ uploadUrl: string }>(
|
|
606
630
|
`${this.baseUrl}/api/org-files/upload/${file.id}`,
|
package/ts_build/package.json
CHANGED
|
@@ -115,7 +115,7 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
115
115
|
unpause(): void;
|
|
116
116
|
unpaused(): Promise<unknown>;
|
|
117
117
|
kill(): Promise<void>;
|
|
118
|
-
call(userInput: string | MessageContent[], _messages?: Message[]): any;
|
|
118
|
+
call(userInput: string | MessageContent[], _messages?: Message[], retryCount?: number): any;
|
|
119
119
|
getStatusMessage(): string;
|
|
120
120
|
logStatus(): void;
|
|
121
121
|
addPendingMessage(message: Message): void;
|
|
@@ -345,7 +345,7 @@ class BaseAgent {
|
|
|
345
345
|
content: `<Workflow>The user has requested the task to end, please call ${this.requiredToolNames} with a report of your ending state</Workflow>`,
|
|
346
346
|
});
|
|
347
347
|
}
|
|
348
|
-
async call(userInput, _messages) {
|
|
348
|
+
async call(userInput, _messages, retryCount = 0) {
|
|
349
349
|
if (this.status === this.eventTypes.notStarted) {
|
|
350
350
|
this.status = this.eventTypes.inProgress;
|
|
351
351
|
}
|
|
@@ -475,7 +475,24 @@ class BaseAgent {
|
|
|
475
475
|
catch (e) {
|
|
476
476
|
if (e.toString().includes("429")) {
|
|
477
477
|
this.setNotHealthy();
|
|
478
|
-
return this.call(userInput, _messages);
|
|
478
|
+
return this.call(userInput, _messages, retryCount);
|
|
479
|
+
const errorStr = e.toString();
|
|
480
|
+
const isNonRetriable = errorStr.includes("401") ||
|
|
481
|
+
errorStr.includes("403") ||
|
|
482
|
+
errorStr.includes("404");
|
|
483
|
+
const isRetriable = !isNonRetriable &&
|
|
484
|
+
(errorStr.match(/5\d\d/) ||
|
|
485
|
+
errorStr.includes("Failed to get models") ||
|
|
486
|
+
errorStr.includes("timeout") ||
|
|
487
|
+
errorStr.includes("ECONNRESET") ||
|
|
488
|
+
errorStr.includes("ETIMEDOUT") ||
|
|
489
|
+
errorStr.includes("Invalid response format from MCP"));
|
|
490
|
+
if (isRetriable && retryCount < 3) {
|
|
491
|
+
const delay = 1000 * Math.pow(2, retryCount);
|
|
492
|
+
console.warn(`Agent request failed (attempt ${retryCount + 1}/3), retrying in ${delay}ms...`, e.message);
|
|
493
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
494
|
+
return this.call(userInput, _messages, retryCount + 1);
|
|
495
|
+
}
|
|
479
496
|
}
|
|
480
497
|
console.error("Agent failed", e);
|
|
481
498
|
if ("response" in e && "data" in e.response) {
|