@tyvm/knowhow 0.0.80 → 0.0.82
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 +118 -1
- package/src/cli.ts +43 -6
- package/src/clients/http.ts +108 -67
- package/src/index.ts +11 -0
- package/src/login.ts +0 -4
- package/src/services/KnowhowClient.ts +28 -1
- package/src/services/McpWebsocketTransport.ts +7 -1
- 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 +11 -0
- package/ts_build/src/chat/modules/AgentModule.js +103 -0
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/cli.js +20 -2
- 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 +0 -1
- 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 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/McpWebsocketTransport.js +3 -1
- package/ts_build/src/services/McpWebsocketTransport.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
|
},
|
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
TaskRegistry,
|
|
9
9
|
} from "../../services/index";
|
|
10
10
|
import * as fs from "fs";
|
|
11
|
+
import * as fsPromises from "fs/promises";
|
|
11
12
|
import * as path from "path";
|
|
12
13
|
|
|
13
14
|
import { BaseChatModule } from "./BaseChatModule";
|
|
14
15
|
import { services } from "../../services/index";
|
|
15
16
|
import { BaseAgent } from "../../agents/index";
|
|
16
17
|
import { ChatCommand, ChatMode, ChatContext, ChatService } from "../types";
|
|
18
|
+
import { Message } from "../../clients/types";
|
|
17
19
|
import { ChatInteraction } from "../../types";
|
|
18
20
|
import { Marked } from "../../utils/index";
|
|
19
21
|
import { TokenCompressor } from "../../processors/TokenCompressor";
|
|
@@ -28,6 +30,7 @@ import { TaskInfo, ChatSession } from "../types";
|
|
|
28
30
|
import { agents } from "../../agents";
|
|
29
31
|
import { ToolCallEvent } from "../../agents/base/base";
|
|
30
32
|
import { $Command } from "@aws-sdk/client-s3";
|
|
33
|
+
import { KnowhowSimpleClient } from "../../services/KnowhowClient";
|
|
31
34
|
|
|
32
35
|
export class AgentModule extends BaseChatModule {
|
|
33
36
|
name = "agent";
|
|
@@ -668,7 +671,10 @@ Please continue from where you left off and complete the original request.
|
|
|
668
671
|
done = true;
|
|
669
672
|
output = doneMsg || "No response from the AI";
|
|
670
673
|
// Remove threadUpdate listener to prevent cost sharing across tasks
|
|
671
|
-
agent.agentEvents.removeListener(
|
|
674
|
+
agent.agentEvents.removeListener(
|
|
675
|
+
agent.eventTypes.threadUpdate,
|
|
676
|
+
threadUpdateHandler
|
|
677
|
+
);
|
|
672
678
|
// Update task info
|
|
673
679
|
taskInfo = this.taskRegistry.get(taskId);
|
|
674
680
|
|
|
@@ -732,6 +738,117 @@ Please continue from where you left off and complete the original request.
|
|
|
732
738
|
}
|
|
733
739
|
}
|
|
734
740
|
|
|
741
|
+
public async loadThreadsForTask(taskId: string, messageId?: string) {
|
|
742
|
+
const resumeTaskId: string = taskId;
|
|
743
|
+
const localMetadataPath = path.join(
|
|
744
|
+
".knowhow",
|
|
745
|
+
"processes",
|
|
746
|
+
"agents",
|
|
747
|
+
resumeTaskId,
|
|
748
|
+
"metadata.json"
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
let threads: Message[][] = [];
|
|
752
|
+
|
|
753
|
+
// Try local FS first
|
|
754
|
+
if (!messageId && fs.existsSync(localMetadataPath)) {
|
|
755
|
+
try {
|
|
756
|
+
const raw = await fsPromises.readFile(localMetadataPath, "utf-8");
|
|
757
|
+
const metadata = JSON.parse(raw);
|
|
758
|
+
threads = metadata.threads || [];
|
|
759
|
+
console.log(`📁 Loaded threads from local FS: ${localMetadataPath}`);
|
|
760
|
+
} catch (e) {
|
|
761
|
+
console.warn(`⚠️ Failed to parse local metadata: ${e.message}`);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
// Try remote via KnowhowSimpleClient
|
|
765
|
+
try {
|
|
766
|
+
const client = new KnowhowSimpleClient();
|
|
767
|
+
threads = await client.getTaskThreads(resumeTaskId);
|
|
768
|
+
console.log(`🌐 Loaded threads from remote for task: ${resumeTaskId}`);
|
|
769
|
+
} catch (e) {
|
|
770
|
+
console.warn(`⚠️ Could not load threads from remote: ${e.message}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return threads;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Resume an agent from a set of existing message threads
|
|
779
|
+
* Used by the CLI --resume flag to continue crashed/failed tasks
|
|
780
|
+
*/
|
|
781
|
+
public async resumeFromMessages(options: {
|
|
782
|
+
agentName: string;
|
|
783
|
+
input: string;
|
|
784
|
+
threads: Message[][];
|
|
785
|
+
messageId?: string;
|
|
786
|
+
taskId?: string;
|
|
787
|
+
}): Promise<{ taskCompleted: Promise<string> }> {
|
|
788
|
+
const { agentName, input, threads, messageId, taskId } = options;
|
|
789
|
+
|
|
790
|
+
// Try to extract the original request from the first user message in threads
|
|
791
|
+
let originalRequest = "";
|
|
792
|
+
if (threads && threads.length > 0) {
|
|
793
|
+
const firstThread = threads[0];
|
|
794
|
+
if (Array.isArray(firstThread)) {
|
|
795
|
+
const firstUserMsg = firstThread.find(
|
|
796
|
+
(m: any) => m.role === "user" && m.content
|
|
797
|
+
);
|
|
798
|
+
if (firstUserMsg) {
|
|
799
|
+
originalRequest =
|
|
800
|
+
typeof firstUserMsg.content === "string"
|
|
801
|
+
? firstUserMsg.content
|
|
802
|
+
: JSON.stringify(firstUserMsg.content);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Build the resume prompt
|
|
808
|
+
const resumePrompt = [
|
|
809
|
+
"You are resuming a previously started task.",
|
|
810
|
+
originalRequest ? `ORIGINAL REQUEST: ${originalRequest}` : "",
|
|
811
|
+
"Please continue from where you left off.",
|
|
812
|
+
input ? input : "",
|
|
813
|
+
]
|
|
814
|
+
.filter(Boolean)
|
|
815
|
+
.join("\n");
|
|
816
|
+
|
|
817
|
+
// Flatten threads into a single messages array for the agent
|
|
818
|
+
const lastThread =
|
|
819
|
+
threads && threads.length > 0 ? threads[threads.length - 1] : [];
|
|
820
|
+
const resumeMessages = [...lastThread];
|
|
821
|
+
|
|
822
|
+
// find last user message index
|
|
823
|
+
const resumeIndex = lastThread
|
|
824
|
+
.reverse()
|
|
825
|
+
.findIndex((e) => e.role === "user" && typeof e.content === "string");
|
|
826
|
+
|
|
827
|
+
if (resumeIndex === -1) {
|
|
828
|
+
resumeMessages.push({
|
|
829
|
+
role: "user",
|
|
830
|
+
content: resumePrompt,
|
|
831
|
+
});
|
|
832
|
+
} else {
|
|
833
|
+
const actualIndex = lastThread.length - 1 - resumeIndex;
|
|
834
|
+
const lastUserMessage = resumeMessages[actualIndex];
|
|
835
|
+
lastUserMessage.content += `\n\n<Workflow>[RESUME CONTEXT]: ${resumePrompt}</Workflow>`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const result = await this.setupAgent({
|
|
839
|
+
agentName,
|
|
840
|
+
input: resumePrompt,
|
|
841
|
+
messageId,
|
|
842
|
+
existingKnowhowTaskId: taskId,
|
|
843
|
+
run: false,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Start agent with prior messages as context
|
|
847
|
+
result.agent.call(resumePrompt, resumeMessages);
|
|
848
|
+
|
|
849
|
+
return { taskCompleted: result.taskCompleted };
|
|
850
|
+
}
|
|
851
|
+
|
|
735
852
|
/**
|
|
736
853
|
* Get list of active agent tasks
|
|
737
854
|
*/
|
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";
|
|
@@ -39,7 +40,6 @@ import { SetupModule } from "./chat/modules/SetupModule";
|
|
|
39
40
|
import { CliChatService } from "./chat/CliChatService";
|
|
40
41
|
|
|
41
42
|
async function setupServices() {
|
|
42
|
-
|
|
43
43
|
const { Agents, Mcp, Clients, Tools: OldTools } = services();
|
|
44
44
|
const Tools = new LazyToolsService();
|
|
45
45
|
|
|
@@ -206,12 +206,42 @@ async function main() {
|
|
|
206
206
|
)
|
|
207
207
|
.option("--message-id <messageId>", "Knowhow message ID for task tracking")
|
|
208
208
|
.option("--sync-fs", "Enable filesystem-based synchronization")
|
|
209
|
-
.option(
|
|
209
|
+
.option(
|
|
210
|
+
"--task-id <taskId>",
|
|
211
|
+
"Pre-generated task ID (used with --sync-fs for predictable agent directory path)"
|
|
212
|
+
)
|
|
210
213
|
.option("--prompt-file <path>", "Custom prompt template file with {text}")
|
|
211
214
|
.option("--input <text>", "Task input (fallback to stdin if not provided)")
|
|
215
|
+
.option(
|
|
216
|
+
"--resume",
|
|
217
|
+
"Resume a previously started task using the --task-id (local FS or remote)"
|
|
218
|
+
)
|
|
212
219
|
.action(async (options) => {
|
|
213
220
|
try {
|
|
214
221
|
await setupServices();
|
|
222
|
+
const agentModule = new AgentModule();
|
|
223
|
+
|
|
224
|
+
// Handle --resume flag: load threads from local FS or remote using --task-id
|
|
225
|
+
if (options.resume) {
|
|
226
|
+
const threads = await agentModule.loadThreadsForTask(
|
|
227
|
+
options.taskId,
|
|
228
|
+
options.messageId
|
|
229
|
+
);
|
|
230
|
+
const resumeInput =
|
|
231
|
+
options.input || "Please continue from where you left off.";
|
|
232
|
+
|
|
233
|
+
await agentModule.initialize(chatService);
|
|
234
|
+
const { taskCompleted } = await agentModule.resumeFromMessages({
|
|
235
|
+
agentName: options.agentName || "Patcher",
|
|
236
|
+
input: resumeInput,
|
|
237
|
+
threads,
|
|
238
|
+
messageId: options.messageId,
|
|
239
|
+
taskId: options.taskId,
|
|
240
|
+
});
|
|
241
|
+
await taskCompleted;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
215
245
|
let input = options.input;
|
|
216
246
|
|
|
217
247
|
// Only read from stdin if we don't have input and don't have a standalone prompt file
|
|
@@ -230,7 +260,6 @@ async function main() {
|
|
|
230
260
|
process.exit(1);
|
|
231
261
|
}
|
|
232
262
|
|
|
233
|
-
const agentModule = new AgentModule();
|
|
234
263
|
await agentModule.initialize(chatService);
|
|
235
264
|
const { taskCompleted } = await agentModule.setupAgent({
|
|
236
265
|
...options,
|
|
@@ -372,7 +401,9 @@ async function main() {
|
|
|
372
401
|
|
|
373
402
|
program
|
|
374
403
|
.command("files")
|
|
375
|
-
.description(
|
|
404
|
+
.description(
|
|
405
|
+
"Sync files between local filesystem and Knowhow FS (uses fileMounts config)"
|
|
406
|
+
)
|
|
376
407
|
.option("--upload", "Force upload direction for all mounts")
|
|
377
408
|
.option("--download", "Force download direction for all mounts")
|
|
378
409
|
.option("--config <path>", "Path to knowhow.json", "./knowhow.json")
|
|
@@ -434,7 +465,10 @@ async function main() {
|
|
|
434
465
|
.description(
|
|
435
466
|
"Git credential helper for GitHub. Use as: git config credential.helper 'knowhow github-credentials'"
|
|
436
467
|
)
|
|
437
|
-
.option(
|
|
468
|
+
.option(
|
|
469
|
+
"--repo <repo>",
|
|
470
|
+
"Repository in owner/repo format (e.g. myorg/myrepo)"
|
|
471
|
+
)
|
|
438
472
|
.action(async (action: string | undefined, options: { repo?: string }) => {
|
|
439
473
|
const client = new KnowhowSimpleClient();
|
|
440
474
|
|
|
@@ -447,7 +481,10 @@ async function main() {
|
|
|
447
481
|
// Read from stdin (git sends protocol/host/username)
|
|
448
482
|
const lines: string[] = [];
|
|
449
483
|
const readline = await import("readline");
|
|
450
|
-
const rl = readline.createInterface({
|
|
484
|
+
const rl = readline.createInterface({
|
|
485
|
+
input: process.stdin,
|
|
486
|
+
terminal: false,
|
|
487
|
+
});
|
|
451
488
|
await new Promise<void>((resolve) => {
|
|
452
489
|
rl.on("line", (line) => {
|
|
453
490
|
if (line.trim()) lines.push(line.trim());
|
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
|
@@ -111,9 +111,5 @@ export async function checkJwt(storedJwt: string) {
|
|
|
111
111
|
return org.organizationId === orgId;
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
console.log(
|
|
115
|
-
`Current user: ${user.email}, \nOrganization: ${currentOrg?.organization?.name} - ${orgId}`
|
|
116
|
-
);
|
|
117
|
-
|
|
118
114
|
return { user, currentOrg };
|
|
119
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;
|
|
@@ -192,6 +192,25 @@ export class KnowhowSimpleClient {
|
|
|
192
192
|
return presignedUrl;
|
|
193
193
|
}
|
|
194
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
|
+
|
|
195
214
|
async createChatCompletion(options: CompletionOptions) {
|
|
196
215
|
await this.checkJwt();
|
|
197
216
|
return axios.post<CompletionResponse>(
|
|
@@ -431,6 +450,14 @@ export class KnowhowSimpleClient {
|
|
|
431
450
|
);
|
|
432
451
|
}
|
|
433
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
|
+
|
|
434
461
|
/**
|
|
435
462
|
* Kill/cancel a running or paused agent task
|
|
436
463
|
*/
|
|
@@ -29,7 +29,13 @@ export class MCPWebSocketTransport implements Transport {
|
|
|
29
29
|
}
|
|
30
30
|
console.log("MCPW Message received", JSON.stringify(parsed));
|
|
31
31
|
const message = JSONRPCMessageSchema.parse(parsed);
|
|
32
|
-
|
|
32
|
+
// Process message asynchronously to avoid blocking the WebSocket
|
|
33
|
+
// event loop while a long-running tool call is in progress.
|
|
34
|
+
// Without this, all subsequent messages are queued until the
|
|
35
|
+
// current tool call completes.
|
|
36
|
+
setImmediate(() => {
|
|
37
|
+
this.onmessage?.(message);
|
|
38
|
+
});
|
|
33
39
|
} catch (error) {
|
|
34
40
|
this.onerror?.(error as Error);
|
|
35
41
|
}
|