@xalia/agent 0.5.4 → 0.5.5

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.
Files changed (43) hide show
  1. package/dist/agent/src/agent/agent.js +16 -9
  2. package/dist/agent/src/agent/agentUtils.js +24 -4
  3. package/dist/agent/src/agent/mcpServerManager.js +19 -9
  4. package/dist/agent/src/agent/openAILLM.js +3 -1
  5. package/dist/agent/src/agent/openAILLMStreaming.js +24 -25
  6. package/dist/agent/src/agent/repeatLLM.js +43 -0
  7. package/dist/agent/src/agent/sudoMcpServerManager.js +12 -6
  8. package/dist/agent/src/chat/client.js +259 -36
  9. package/dist/agent/src/chat/conversationManager.js +243 -24
  10. package/dist/agent/src/chat/db.js +24 -1
  11. package/dist/agent/src/chat/frontendClient.js +74 -0
  12. package/dist/agent/src/chat/server.js +3 -3
  13. package/dist/agent/src/test/db.test.js +25 -2
  14. package/dist/agent/src/test/openaiStreaming.test.js +133 -0
  15. package/dist/agent/src/test/prompt.test.js +2 -2
  16. package/dist/agent/src/test/sudoMcpServerManager.test.js +1 -1
  17. package/dist/agent/src/tool/agentChat.js +7 -197
  18. package/dist/agent/src/tool/chatMain.js +18 -23
  19. package/dist/agent/src/tool/commandPrompt.js +248 -0
  20. package/dist/agent/src/tool/prompt.js +27 -31
  21. package/package.json +1 -1
  22. package/scripts/test_chat +17 -1
  23. package/src/agent/agent.ts +34 -11
  24. package/src/agent/agentUtils.ts +52 -3
  25. package/src/agent/mcpServerManager.ts +43 -13
  26. package/src/agent/openAILLM.ts +3 -1
  27. package/src/agent/openAILLMStreaming.ts +28 -27
  28. package/src/agent/repeatLLM.ts +51 -0
  29. package/src/agent/sudoMcpServerManager.ts +41 -12
  30. package/src/chat/client.ts +353 -40
  31. package/src/chat/conversationManager.ts +345 -33
  32. package/src/chat/db.ts +28 -2
  33. package/src/chat/frontendClient.ts +123 -0
  34. package/src/chat/messages.ts +146 -2
  35. package/src/chat/server.ts +3 -3
  36. package/src/test/db.test.ts +35 -2
  37. package/src/test/openaiStreaming.test.ts +142 -0
  38. package/src/test/prompt.test.ts +1 -1
  39. package/src/test/sudoMcpServerManager.test.ts +1 -1
  40. package/src/tool/agentChat.ts +13 -211
  41. package/src/tool/chatMain.ts +28 -43
  42. package/src/tool/commandPrompt.ts +252 -0
  43. package/src/tool/prompt.ts +33 -32
@@ -4,6 +4,8 @@ import {
4
4
  ChatCompletionToolMessageParam,
5
5
  ChatCompletionMessageToolCall,
6
6
  } from "openai/resources.mjs";
7
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
8
+ import { McpServerBrief } from "@xalia/xmcp/sdk";
7
9
 
8
10
  /**
9
11
  * Message from a user to the server
@@ -13,6 +15,84 @@ export type ClientUserMessage = {
13
15
  message: string;
14
16
  };
15
17
 
18
+ /**
19
+ * Message to add an MCP server to the agent
20
+ */
21
+ export type ClientAddMcpServer = {
22
+ type: "add_mcp_server";
23
+ server_name: string;
24
+ enable_all: boolean;
25
+ };
26
+
27
+ /**
28
+ * Message to remove an MCP server from the agent
29
+ */
30
+ export type ClientRemoveMcpServer = {
31
+ type: "remove_mcp_server";
32
+ server_name: string;
33
+ };
34
+
35
+ /**
36
+ * Enable a specific tool
37
+ */
38
+ export type ClientEnableMcpServerTool = {
39
+ type: "enable_mcp_server_tool";
40
+ server_name: string;
41
+ tool: string;
42
+ };
43
+
44
+ /**
45
+ * Disable a specific tool
46
+ */
47
+ export type ClientDisableMcpServerTool = {
48
+ type: "disable_mcp_server_tool";
49
+ server_name: string;
50
+ tool: string;
51
+ };
52
+
53
+ export type ClientEnableAllMcpServerTools = {
54
+ type: "enable_all_mcp_server_tools";
55
+ server_name: string;
56
+ };
57
+
58
+ export type ClientDisableAllMcpServerTools = {
59
+ type: "disable_all_mcp_server_tools";
60
+ server_name: string;
61
+ };
62
+
63
+ export type ClientSetSystemPrompt = {
64
+ type: "set_system_prompt";
65
+ system_prompt: string;
66
+ };
67
+
68
+ export type ClientSetModel = {
69
+ type: "set_model";
70
+ model: string;
71
+ };
72
+
73
+ export type ClientToServer =
74
+ | ClientUserMessage
75
+ | ClientAddMcpServer
76
+ | ClientRemoveMcpServer
77
+ | ClientEnableMcpServerTool
78
+ | ClientDisableMcpServerTool
79
+ | ClientEnableAllMcpServerTools
80
+ | ClientDisableAllMcpServerTools
81
+ | ClientSetSystemPrompt
82
+ | ClientSetModel;
83
+
84
+ ///
85
+ /// Server -> Client Messages
86
+ ///
87
+
88
+ /**
89
+ * Error in response to an invalid request from the client
90
+ */
91
+ export type ServerError = {
92
+ type: "error";
93
+ message: string;
94
+ };
95
+
16
96
  /**
17
97
  * (from server) Chat history
18
98
  */
@@ -57,6 +137,11 @@ export type ServerAgentMessageChunk = {
57
137
  end: boolean;
58
138
  };
59
139
 
140
+ export type ServerMcpServerBriefs = {
141
+ type: "mcp_server_briefs";
142
+ server_briefs: McpServerBrief[];
143
+ };
144
+
60
145
  /**
61
146
  * For information only (to keep the chat window consistent).
62
147
  */
@@ -80,12 +165,71 @@ export type ServerTyping = {
80
165
  from: string;
81
166
  };
82
167
 
83
- export type ClientToServer = ClientUserMessage;
168
+ export type ServerMcpServerAdded = {
169
+ type: "mcp_server_added";
170
+ server_name: string;
171
+ tools: Tool[];
172
+ enabled_tools: string[];
173
+ };
174
+
175
+ export type ServerMcpServerRemoved = {
176
+ type: "mcp_server_removed";
177
+ server_name: string;
178
+ };
179
+
180
+ export type ServerMcpServerToolEnabled = {
181
+ type: "mcp_server_tool_enabled";
182
+ server_name: string;
183
+ tool: string;
184
+ };
185
+
186
+ export type ServerMcpServerToolDisabled = {
187
+ type: "mcp_server_tool_disabled";
188
+ server_name: string;
189
+ tool: string;
190
+ };
191
+
192
+ export type ServerSystemPromptUpdated = {
193
+ type: "system_prompt_updated";
194
+ system_prompt: string;
195
+ };
196
+
197
+ export type ServerModelUpdated = {
198
+ type: "model_updated";
199
+ model: string;
200
+ };
201
+
202
+ export type ServerToClientStateUpdate =
203
+ | ServerMcpServerAdded
204
+ | ServerMcpServerRemoved
205
+ | ServerMcpServerToolEnabled
206
+ | ServerMcpServerToolDisabled
207
+ | ServerSystemPromptUpdated
208
+ | ServerModelUpdated;
209
+
210
+ export type ServerOpenUrl = {
211
+ type: "open_url";
212
+ url: string;
213
+ display_name: string;
214
+ };
215
+
216
+ export type ServerApproveToolCall = {
217
+ type: "approve_tool_call";
218
+ tbd: string;
219
+ };
220
+
221
+ export type ServerToClientActions = ServerOpenUrl | ServerApproveToolCall;
84
222
 
85
223
  export type ServerToClient =
224
+ | ServerError
86
225
  | ServerUserJoined
87
226
  | ServerUserLeft
88
227
  | ServerUserMessage
89
228
  | ServerAgentMessage
90
229
  | ServerAgentMessageChunk
91
- | ServerTyping;
230
+ | ServerMcpServerBriefs
231
+ | ServerToolCall
232
+ | ServerToolCallResult
233
+ | ServerTyping
234
+ | ServerToClientStateUpdate
235
+ | ServerToClientActions;
@@ -31,7 +31,7 @@ async function resolveAgentProfileId(
31
31
  }
32
32
 
33
33
  ap = await db.getSavedAgentProfileByName(
34
- userData.user_uuid,
34
+ userData.uuid,
35
35
  agentProfileIdentifier
36
36
  );
37
37
  logger.debug(`[resolveAgentProfileId]: by name: {JSON.stringify(ap)}`);
@@ -54,7 +54,7 @@ async function resolveSessionIdFromIdentifier(
54
54
  // Interpret as an id, or as a number under the current user.
55
55
  session = await db.getSessionById(compound);
56
56
  if (!session) {
57
- session = await db.getSessionByName(userData.user_uuid, compound);
57
+ session = await db.getSessionByName(userData.uuid, compound);
58
58
  }
59
59
  } else {
60
60
  session = await db.getSessionByName(compound[0], compound[1]);
@@ -114,7 +114,7 @@ async function findOrCreateSession(
114
114
  throw `no agent profile: ${agentProfileIdParam}`;
115
115
  }
116
116
 
117
- return db.createSession(userData.user_uuid, query.session_id, agentProfileId);
117
+ return db.createSession(userData.uuid, query.session_id, agentProfileId);
118
118
  }
119
119
 
120
120
  export async function runServer(
@@ -15,6 +15,12 @@ const AGENT_PROFILE: AgentProfile = {
15
15
  mcp_settings: {},
16
16
  };
17
17
 
18
+ const AGENT_PROFILE_2: AgentProfile = {
19
+ model: undefined,
20
+ system_prompt: "You are a very helpful agent.",
21
+ mcp_settings: { simplecalc: [] },
22
+ };
23
+
18
24
  async function createDummyUser(db: Database): Promise<void> {
19
25
  const apiClient = new ApiClient(LOCAL_SERVER_URL, "dummy_key");
20
26
  const user = await apiClient.getUserBrief("dummy_user");
@@ -28,12 +34,12 @@ async function createDummyUser(db: Database): Promise<void> {
28
34
  }
29
35
 
30
36
  describe("DB", () => {
31
- it("should get existing user", async function () {
37
+ it("should get data for existing user", async function () {
32
38
  const db = getLocalDB();
33
39
  await createDummyUser(db);
34
40
  const dummyUser = await db.getUserDataFromApiKey("dummy_key");
35
41
  expect(dummyUser).eql({
36
- user_uuid: "dummy_user",
42
+ uuid: "dummy_user",
37
43
  nickname: "Dummy User",
38
44
  });
39
45
  });
@@ -44,6 +50,15 @@ describe("DB", () => {
44
50
  expect(dummyUser).to.equal(undefined);
45
51
  });
46
52
 
53
+ it("should get user by uuid", async function () {
54
+ const db = getLocalDB();
55
+ await createDummyUser(db);
56
+ const dummyUser = await db.getUserFromUuid("dummy_user");
57
+ assert(dummyUser);
58
+ expect(dummyUser.uuid).eql("dummy_user");
59
+ expect(dummyUser.nickname).eql("Dummy User");
60
+ });
61
+
47
62
  it("should create and retrieve agent profiles", async function () {
48
63
  const db = getLocalDB();
49
64
 
@@ -74,6 +89,24 @@ describe("DB", () => {
74
89
  expect(agentProfile).eql(AGENT_PROFILE);
75
90
  });
76
91
 
92
+ it("should update agent profiles", async function () {
93
+ const db = getLocalDB();
94
+
95
+ await db.clearAgentProfiles();
96
+ const agentProfileId = await db.createAgentProfile(
97
+ "dummy_user",
98
+ "test_profile",
99
+ AGENT_PROFILE
100
+ );
101
+ expect(agentProfileId).to.not.equal(undefined);
102
+ assert(agentProfileId);
103
+
104
+ await db.updateAgentProfile(agentProfileId, AGENT_PROFILE_2);
105
+
106
+ const agentProfileFromDB = (await db.getAgentProfileById(agentProfileId))!;
107
+ expect(agentProfileFromDB).eql(AGENT_PROFILE_2);
108
+ });
109
+
77
110
  it("should create and retrieve sessions", async function () {
78
111
  const db = getLocalDB();
79
112
 
@@ -0,0 +1,142 @@
1
+ import { expect } from "chai";
2
+
3
+ import { OpenAI } from "openai";
4
+ import {
5
+ initializeCompletion,
6
+ updateCompletion,
7
+ } from "../agent/openAILLMStreaming";
8
+
9
+ const TEST_STANDARD: OpenAI.Chat.Completions.ChatCompletionChunk[] = [
10
+ {
11
+ id: "chatcmpl-BzBqzVs5w0kU5we3KIUN1Q4FAZXjg",
12
+ choices: [
13
+ {
14
+ delta: { content: "!" },
15
+ finish_reason: null,
16
+ index: 0,
17
+ logprobs: null,
18
+ },
19
+ ],
20
+ created: 1753923273,
21
+ model: "gpt-4o-2024-08-06",
22
+ object: "chat.completion.chunk",
23
+ service_tier: "default",
24
+ system_fingerprint: "fp_07871e2ad8",
25
+ },
26
+ {
27
+ id: "chatcmpl-BzBqzVs5w0kU5we3KIUN1Q4FAZXjg",
28
+ choices: [{ delta: {}, finish_reason: "stop", index: 0, logprobs: null }],
29
+ created: 1753923273,
30
+ model: "gpt-4o-2024-08-06",
31
+ object: "chat.completion.chunk",
32
+ service_tier: "default",
33
+ system_fingerprint: "fp_07871e2ad8",
34
+ },
35
+ ];
36
+
37
+ const TEST_TRAILING_USAGE: OpenAI.Chat.Completions.ChatCompletionChunk[] = [
38
+ {
39
+ id: "gen-1753923406-nsIKHyFRoJqkUntBnQTw",
40
+ choices: [
41
+ {
42
+ delta: { content: "test", role: "assistant" },
43
+ finish_reason: "stop",
44
+ index: 0,
45
+ logprobs: null,
46
+ },
47
+ ],
48
+ created: 1753923406,
49
+ model: "openai/gpt-4o",
50
+ object: "chat.completion.chunk",
51
+ system_fingerprint: "fp_a288987b44",
52
+ },
53
+ {
54
+ id: "gen-1753923406-nsIKHyFRoJqkUntBnQTw",
55
+ choices: [
56
+ {
57
+ delta: { content: "", role: "assistant" },
58
+ finish_reason: null,
59
+ index: 0,
60
+ logprobs: null,
61
+ },
62
+ ],
63
+ created: 1753923406,
64
+ model: "openai/gpt-4o",
65
+ object: "chat.completion.chunk",
66
+ usage: {
67
+ completion_tokens: 50,
68
+ prompt_tokens: 271,
69
+ total_tokens: 321,
70
+ completion_tokens_details: { reasoning_tokens: 0 },
71
+ prompt_tokens_details: { cached_tokens: 0 },
72
+ },
73
+ },
74
+ ];
75
+
76
+ describe("OpenAI Streaming", () => {
77
+ it("should support standard termination", async function () {
78
+ const chunks = TEST_STANDARD;
79
+ expect(chunks.length).eql(2);
80
+ const { initMessage } = initializeCompletion(chunks[0]);
81
+ updateCompletion(initMessage, chunks[1]);
82
+
83
+ expect(initMessage).eql({
84
+ id: "chatcmpl-BzBqzVs5w0kU5we3KIUN1Q4FAZXjg",
85
+ choices: [
86
+ {
87
+ message: {
88
+ content: "!",
89
+ role: "assistant",
90
+ refusal: null,
91
+ tool_calls: undefined,
92
+ },
93
+ finish_reason: "stop",
94
+ index: 0,
95
+ logprobs: null,
96
+ },
97
+ ],
98
+ created: 1753923273,
99
+ model: "gpt-4o-2024-08-06",
100
+ object: "chat.completion",
101
+ service_tier: "default",
102
+ system_fingerprint: "fp_07871e2ad8",
103
+ usage: undefined,
104
+ });
105
+ });
106
+
107
+ it("should support trailing usage", async function () {
108
+ const chunks = TEST_TRAILING_USAGE;
109
+ expect(chunks.length).eql(2);
110
+ const { initMessage } = initializeCompletion(chunks[0]);
111
+ updateCompletion(initMessage, chunks[1]);
112
+
113
+ expect(initMessage).eql({
114
+ id: "gen-1753923406-nsIKHyFRoJqkUntBnQTw",
115
+ choices: [
116
+ {
117
+ message: {
118
+ content: "test",
119
+ role: "assistant",
120
+ refusal: null,
121
+ tool_calls: undefined,
122
+ },
123
+ finish_reason: "stop",
124
+ index: 0,
125
+ logprobs: null,
126
+ },
127
+ ],
128
+ created: 1753923406,
129
+ model: "openai/gpt-4o",
130
+ object: "chat.completion",
131
+ service_tier: undefined,
132
+ system_fingerprint: "fp_a288987b44",
133
+ usage: {
134
+ completion_tokens: 50,
135
+ prompt_tokens: 271,
136
+ total_tokens: 321,
137
+ completion_tokens_details: { reasoning_tokens: 0 },
138
+ prompt_tokens_details: { cached_tokens: 0 },
139
+ },
140
+ });
141
+ });
142
+ });
@@ -1,5 +1,5 @@
1
1
  import { expect } from "chai";
2
- import { parsePrompt } from "../tool/prompt";
2
+ import { parsePrompt } from "../tool/commandPrompt";
3
3
 
4
4
  describe("Prompt", () => {
5
5
  it("should recognise commands", async function () {
@@ -47,7 +47,7 @@ describe("SudoMcpServerManager", async () => {
47
47
 
48
48
  it("should add a new MCP server", async () => {
49
49
  const [, sm] = await getServerManagers();
50
- await sm.addMcpServer("sudomcp_simplecalc");
50
+ await sm.addMcpServer("sudomcp_simplecalc", true);
51
51
  const serverBriefs = sm.getServerBriefs();
52
52
  expect(
53
53
  serverBriefs.some((brief) => brief.name === "sudomcp_simplecalc")
@@ -1,21 +1,18 @@
1
1
  import yocto from "yocto-spinner";
2
2
  import { Spinner } from "yocto-spinner";
3
- import * as fs from "fs";
4
3
  import OpenAI from "openai";
5
4
  import chalk from "chalk";
6
5
 
7
6
  import { configuration, utils } from "@xalia/xmcp/tool";
8
- import { getLogger, InvalidConfiguration } from "@xalia/xmcp/sdk";
7
+ import { getLogger } from "@xalia/xmcp/sdk";
9
8
 
10
- import { Agent, AgentProfile } from "../agent/agent";
11
- import { McpServerManager } from "../agent/mcpServerManager";
12
- import { displayToolCall } from "../agent/tools";
13
- import { SkillManager } from "../agent/sudoMcpServerManager";
9
+ import { AgentProfile } from "../agent/agent";
14
10
  import { createAgentWithSkills } from "../agent/agentUtils";
15
11
 
16
12
  import { loadImageB64OrUndefined } from "./files";
17
- import { Prompt, parsePrompt } from "./prompt";
18
13
  import { NODE_PLATFORM } from "./nodePlatform";
14
+ import { CommandPrompt } from "./commandPrompt";
15
+ import { IPrompt, Prompt } from "./prompt";
19
16
 
20
17
  const logger = getLogger();
21
18
 
@@ -66,6 +63,9 @@ export async function runChat(
66
63
  }
67
64
  };
68
65
 
66
+ const repl: IPrompt = new Prompt();
67
+ const cmdPrompt = new CommandPrompt(repl);
68
+
69
69
  let remainingApprovedToolCalls = approveToolsUpTo;
70
70
  const onToolCall = async (toolCall: OpenAI.ChatCompletionMessageToolCall) => {
71
71
  if (remainingApprovedToolCalls !== 0) {
@@ -74,7 +74,7 @@ export async function runChat(
74
74
  }
75
75
 
76
76
  spinner.stop().clear();
77
- const result = await promptToolCall(repl, toolCall);
77
+ const result = await cmdPrompt.promptToolCall(toolCall);
78
78
  spinner.start();
79
79
  return result;
80
80
  };
@@ -108,12 +108,13 @@ export async function runChat(
108
108
  console.log(`USER: ${prompt}`);
109
109
  }
110
110
 
111
- const repl = new Prompt();
112
-
113
111
  // Conversation loop
114
112
  while (true) {
115
113
  if (!prompt) {
116
- const [msg, img] = await getNextPrompt(repl, agent, sudoMcpServerManager);
114
+ const [msg, img] = await cmdPrompt.getNextPrompt(
115
+ agent,
116
+ sudoMcpServerManager
117
+ );
117
118
  if (!msg && !img) {
118
119
  break;
119
120
  }
@@ -137,7 +138,7 @@ export async function runChat(
137
138
 
138
139
  // Shutdown the agent
139
140
 
140
- repl.shutdown();
141
+ cmdPrompt.shutdown();
141
142
  await agent.shutdown();
142
143
 
143
144
  logger.debug("shutdown done");
@@ -147,202 +148,3 @@ export async function runChat(
147
148
  * Read lines from the prompt, parsing any commands, and return once there is
148
149
  * a prompt and/or image for the llm. Both undefined means exit.
149
150
  */
150
- async function getNextPrompt(
151
- repl: Prompt,
152
- agent: Agent,
153
- sudoMcpServerManager: SkillManager
154
- ): Promise<[string | undefined, string | undefined]> {
155
- while (true) {
156
- // Get a line, detecting the EOF signal.
157
- const line = await repl.run();
158
- if (typeof line === "undefined") {
159
- console.log("closing ...");
160
- return [undefined, undefined];
161
- }
162
- if (line.length === 0) {
163
- continue;
164
- }
165
-
166
- // Extract prompt or commands
167
- const { msg, cmds } = parsePrompt(line);
168
-
169
- // If there are no commands, this must be a prompt only
170
- if (!cmds) {
171
- return [msg, undefined];
172
- }
173
-
174
- // There are commands. If it's image, return [prompt, image]. If it's
175
- // quit, return [undefined, undefined], otherwise it must be a command.
176
- // Execute it and prompt again.
177
- switch (cmds[0]) {
178
- case "i": // image
179
- return [msg, cmds[1]];
180
- case "q":
181
- case "quit":
182
- case "exit":
183
- return [undefined, undefined];
184
- default:
185
- break;
186
- }
187
-
188
- try {
189
- await runCommand(
190
- agent,
191
- agent.getMcpServerManager(),
192
- sudoMcpServerManager,
193
- cmds
194
- );
195
- } catch (e) {
196
- console.log(`ERROR: ${e}`);
197
- }
198
- }
199
- }
200
-
201
- async function runCommand(
202
- agent: Agent,
203
- mcpServerManager: McpServerManager,
204
- sudoMcpServerManager: SkillManager,
205
- cmds: string[]
206
- ) {
207
- switch (cmds[0]) {
208
- case "lt":
209
- listTools(mcpServerManager);
210
- break;
211
- case "ls":
212
- listServers(sudoMcpServerManager);
213
- break;
214
- case "as":
215
- await addServer(sudoMcpServerManager, cmds[1]);
216
- break;
217
- case "rs":
218
- await mcpServerManager.removeMcpServer(cmds[1]);
219
- break;
220
- case "e":
221
- mcpServerManager.enableTool(cmds[1], cmds[2]);
222
- console.log(`Enabled tool ${cmds[2]} for server ${cmds[1]}`);
223
- break;
224
- case "d":
225
- mcpServerManager.disableTool(cmds[1], cmds[2]);
226
- console.log(`Disabled tool ${cmds[2]} for server ${cmds[1]}`);
227
- break;
228
- case "ea":
229
- mcpServerManager.enableAllTools(cmds[1]);
230
- console.log(`Enabled all tools for server ${cmds[1]}`);
231
- break;
232
- case "da":
233
- mcpServerManager.disableAllTools(cmds[1]);
234
- console.log(`Disabled all tools for server ${cmds[1]}`);
235
- break;
236
- case "wc":
237
- writeConversation(agent, cmds[1]);
238
- break;
239
- case "wa":
240
- writeAgentProfile(agent, cmds[1]);
241
- break;
242
- case "h":
243
- case "help":
244
- case "?":
245
- helpMenu();
246
- break;
247
- default:
248
- console.log(`error: Unknown command ${cmds[0]}`);
249
- }
250
- }
251
-
252
- function helpMenu() {
253
- console.log(`Tool management commands:`);
254
- console.log(` ${chalk.yellow("/lt")} List tools: `);
255
- console.log(` ${chalk.yellow("/ls")} List servers`);
256
- console.log(` ${chalk.yellow("/as <server-name>")} Add server`);
257
- console.log(
258
- ` ${chalk.yellow("/rs <server-name>")} Remove server`
259
- );
260
- console.log(` ${chalk.yellow("/e <server-name> <tool-name>")} Enable tool`);
261
- console.log(` ${chalk.yellow("/d <server-name> <tool-name>")} Disable tool`);
262
- console.log(
263
- ` ${chalk.yellow("/ea <server-name>")} Enable all tools`
264
- );
265
- console.log(
266
- ` ${chalk.yellow("/da <server-name>")} Disable all tools`
267
- );
268
- console.log(
269
- ` ${chalk.yellow("/wc <file-name>")} Write conversation file`
270
- );
271
- console.log(
272
- ` ${chalk.yellow("/wa <file-name>")} Write agent profile file`
273
- );
274
-
275
- console.log(` ${chalk.yellow("/q")} Quit`);
276
- }
277
-
278
- function listTools(mcpServerManager: McpServerManager) {
279
- console.log("Mcp servers and tools (* - enabled):");
280
-
281
- const serverNames = mcpServerManager.getMcpServerNames();
282
- for (const serverName of serverNames) {
283
- const server = mcpServerManager.getMcpServer(serverName);
284
- console.log(` ${chalk.green(serverName)}`);
285
- const tools = server.getTools();
286
- const enabled = server.getEnabledTools();
287
- for (const tool of tools) {
288
- const isEnabled = enabled[tool.name] ? "*" : " ";
289
- console.log(` [${isEnabled}] ${tool.name}`);
290
- }
291
- }
292
- }
293
-
294
- function listServers(sudoMcpServerManager: SkillManager) {
295
- console.log(`Available MCP Servers:`);
296
-
297
- const serverBriefs = sudoMcpServerManager.getServerBriefs();
298
- serverBriefs.sort((a, b) => a.name.localeCompare(b.name));
299
- for (const brief of serverBriefs) {
300
- console.log(`- ${chalk.green(brief.name)}: ${brief.description}`);
301
- }
302
- }
303
-
304
- /**
305
- * Adds server and enables all tools.
306
- */
307
- async function addServer(
308
- sudoMcpServerManager: SkillManager,
309
- serverName: string
310
- ) {
311
- try {
312
- await sudoMcpServerManager.addMcpServer(serverName);
313
- } catch (e) {
314
- if (e instanceof InvalidConfiguration) {
315
- throw `${e}. \n
316
- Have you installed server ${serverName} using the SudoMCP CLI tool?`;
317
- } else {
318
- throw e;
319
- }
320
- }
321
- sudoMcpServerManager.getMcpServerManager().enableAllTools(serverName);
322
- console.log(`Added server: ${serverName} and enabled all tools.`);
323
- console.log(`Current tool list:`);
324
- listTools(sudoMcpServerManager.getMcpServerManager());
325
- }
326
-
327
- function writeConversation(agent: Agent, fileName: string) {
328
- const conversation = agent.getConversation();
329
- fs.writeFileSync(fileName, JSON.stringify(conversation), {
330
- encoding: "utf8",
331
- });
332
- console.log(`Conversation written to ${fileName}`);
333
- }
334
-
335
- function writeAgentProfile(agent: Agent, fileName: string) {
336
- const profile = agent.getAgentProfile();
337
- fs.writeFileSync(fileName, JSON.stringify(profile));
338
- console.log(`AgentProfile written to ${fileName}`);
339
- }
340
-
341
- async function promptToolCall(
342
- repl: Prompt,
343
- toolCall: OpenAI.ChatCompletionMessageToolCall
344
- ): Promise<boolean> {
345
- displayToolCall(toolCall);
346
- const response = await repl.run("Approve tool call? (Y/n) ");
347
- return response === "y" || response === "yes" || response == "";
348
- }