4runr-os 1.0.6 → 1.0.10

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/dist/index.js CHANGED
@@ -8,6 +8,8 @@ import * as fs from 'fs';
8
8
  import * as path from 'path';
9
9
  import * as os from 'os';
10
10
  import { GatewayClient } from './gateway-client.js';
11
+ import { executeLocalModel, verifyLocalModelServer } from './local-model-executor.js';
12
+ import { checkOllamaInstallation, installOllama, startOllamaServer, downloadModel, verifyLocalExecution } from './local-setup.js';
11
13
  // ANSI colors
12
14
  const colors = {
13
15
  reset: '\x1b[0m',
@@ -34,7 +36,13 @@ let gatewayUrl = process.env.GATEWAY_URL || 'http://44.222.212.152';
34
36
  let chatMode = false;
35
37
  let chatAgent = null;
36
38
  let conversationHistory = [];
39
+ // Multiline input state
40
+ let multilineMode = false;
41
+ let multilineResolver = null;
42
+ let multilineLines = [];
43
+ let multilineEndMarker = 'END';
37
44
  const customAgents = new Map();
45
+ const customTools = new Map();
38
46
  // Get config directory path
39
47
  function getConfigDir() {
40
48
  const homeDir = os.homedir();
@@ -76,6 +84,37 @@ function saveCustomAgents() {
76
84
  console.error(`Failed to save agents: ${error.message}`);
77
85
  }
78
86
  }
87
+ // Get tools file path
88
+ function getToolsFilePath() {
89
+ return path.join(getConfigDir(), 'tools.json');
90
+ }
91
+ // Load custom tools from file
92
+ function loadCustomTools() {
93
+ try {
94
+ const filePath = getToolsFilePath();
95
+ if (fs.existsSync(filePath)) {
96
+ const data = fs.readFileSync(filePath, 'utf-8');
97
+ const tools = JSON.parse(data);
98
+ tools.forEach(tool => {
99
+ customTools.set(tool.name.toLowerCase(), tool);
100
+ });
101
+ }
102
+ }
103
+ catch (error) {
104
+ // Silently fail - file might not exist yet
105
+ }
106
+ }
107
+ // Save custom tools to file
108
+ function saveCustomTools() {
109
+ try {
110
+ const filePath = getToolsFilePath();
111
+ const tools = Array.from(customTools.values());
112
+ fs.writeFileSync(filePath, JSON.stringify(tools, null, 2), 'utf-8');
113
+ }
114
+ catch (error) {
115
+ console.error(`Failed to save tools: ${error.message}`);
116
+ }
117
+ }
79
118
  const aiConfig = {
80
119
  provider: null,
81
120
  model: null,
@@ -173,26 +212,14 @@ function promptMultiline(rl, question, endMarker = 'END') {
173
212
  return new Promise((resolve) => {
174
213
  console.log(question);
175
214
  console.log(`${gray}Enter your text. Type '${endMarker}' on a new line when finished:${reset}\n`);
176
- const lines = [];
177
- let collecting = true;
178
- const lineHandler = (line) => {
179
- if (!collecting)
180
- return;
181
- const trimmed = line.trim();
182
- // Check for end marker
183
- if (trimmed === endMarker || trimmed === endMarker.toUpperCase()) {
184
- collecting = false;
185
- rl.removeListener('line', lineHandler);
186
- rl.setPrompt(`${cyan}4runr>${reset} `);
187
- resolve(lines.join('\n').trim());
188
- return;
189
- }
190
- lines.push(line);
191
- };
192
- // Temporarily change prompt
215
+ // Set up multiline mode
216
+ multilineMode = true;
217
+ multilineResolver = resolve;
218
+ multilineLines = [];
219
+ multilineEndMarker = endMarker;
220
+ // Change prompt to indicate multiline mode
193
221
  rl.setPrompt(`${gray} >${reset} `);
194
222
  rl.prompt();
195
- rl.on('line', lineHandler);
196
223
  });
197
224
  }
198
225
  // Command handlers
@@ -472,11 +499,96 @@ const commands = {
472
499
  console.log(`${red}Invalid choice${reset}\n`);
473
500
  return;
474
501
  }
475
- localModelName = await prompt(rl, `${cyan}Model name (e.g., llama2, mistral, gpt-3.5-turbo):${reset} `);
502
+ // Integrated setup - automatically install and configure
503
+ console.log(`\n${bright}Setting up local model environment...${reset}`);
504
+ if (localModelProvider === 'ollama') {
505
+ // Check if Ollama is installed
506
+ const ollamaCheck = await checkOllamaInstallation();
507
+ if (!ollamaCheck.ollamaInstalled) {
508
+ console.log(`${yellow}Ollama not found. Installing automatically...${reset}`);
509
+ const installResult = await installOllama();
510
+ if (!installResult.success) {
511
+ console.log(`${red}✗ ${installResult.message}${reset}\n`);
512
+ const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
513
+ if (continueChoice?.toLowerCase() !== 'y') {
514
+ console.log(`${gray}Agent creation cancelled${reset}\n`);
515
+ return;
516
+ }
517
+ }
518
+ else {
519
+ console.log(`${green}✓ ${installResult.message}${reset}`);
520
+ }
521
+ }
522
+ // Start server if not running
523
+ const serverCheck = await checkOllamaInstallation();
524
+ if (!serverCheck.success) {
525
+ console.log(`${dim}Starting Ollama server...${reset}`);
526
+ const startResult = await startOllamaServer();
527
+ if (startResult.success) {
528
+ console.log(`${green}✓ ${startResult.message}${reset}`);
529
+ }
530
+ else {
531
+ console.log(`${yellow}⚠ ${startResult.message}${reset}`);
532
+ }
533
+ }
534
+ // Get available models
535
+ const finalCheck = await checkOllamaInstallation();
536
+ if (finalCheck.modelsAvailable && finalCheck.modelsAvailable.length > 0) {
537
+ console.log(`${green}✓ Ollama is ready${reset}`);
538
+ console.log(`${dim}Available models: ${finalCheck.modelsAvailable.join(', ')}${reset}`);
539
+ }
540
+ else {
541
+ console.log(`${yellow}⚠ No models installed yet${reset}`);
542
+ }
543
+ }
544
+ else if (localModelProvider === 'lm-studio') {
545
+ // LM Studio - check if running
546
+ const verification = await verifyLocalModelServer('lm-studio', localModelUrl);
547
+ if (!verification.available) {
548
+ console.log(`${yellow}⚠ LM Studio server not running${reset}`);
549
+ console.log(`${dim}Please start LM Studio and click "Start Server" in the Chat tab${reset}`);
550
+ const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
551
+ if (continueChoice?.toLowerCase() !== 'y') {
552
+ console.log(`${gray}Agent creation cancelled${reset}\n`);
553
+ return;
554
+ }
555
+ }
556
+ else {
557
+ console.log(`${green}✓ LM Studio server is running${reset}`);
558
+ if (verification.models && verification.models.length > 0) {
559
+ console.log(`${dim}Available models: ${verification.models.join(', ')}${reset}`);
560
+ }
561
+ }
562
+ }
563
+ // Get model name
564
+ localModelName = await prompt(rl, `\n${cyan}Model name (e.g., llama2, mistral, gpt-3.5-turbo):${reset} `);
476
565
  if (!localModelName) {
477
566
  console.log(`${red}Model name required${reset}\n`);
478
567
  return;
479
568
  }
569
+ // Auto-download model if using Ollama and model not found
570
+ if (localModelProvider === 'ollama') {
571
+ const finalCheck = await checkOllamaInstallation();
572
+ if (finalCheck.modelsAvailable && !finalCheck.modelsAvailable.includes(localModelName)) {
573
+ console.log(`\n${yellow}Model '${localModelName}' not found${reset}`);
574
+ const downloadChoice = await prompt(rl, `${cyan}Download it now? (y/n):${reset} `);
575
+ if (downloadChoice?.toLowerCase() === 'y') {
576
+ console.log(`\n${dim}Downloading model (this may take several minutes)...${reset}`);
577
+ const downloadResult = await downloadModel(localModelName);
578
+ if (downloadResult.success) {
579
+ console.log(`${green}✓ ${downloadResult.message}${reset}`);
580
+ }
581
+ else {
582
+ console.log(`${red}✗ ${downloadResult.message}${reset}`);
583
+ const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
584
+ if (continueChoice?.toLowerCase() !== 'y') {
585
+ console.log(`${gray}Agent creation cancelled${reset}\n`);
586
+ return;
587
+ }
588
+ }
589
+ }
590
+ }
591
+ }
480
592
  baseAgent = `local:${localModelProvider}:${localModelName}`;
481
593
  }
482
594
  else {
@@ -510,21 +622,55 @@ const commands = {
510
622
  const presencePenalty = presPenaltyStr ? parseFloat(presPenaltyStr) : undefined;
511
623
  // Tools selection
512
624
  console.log(`\n${bright}Available Tools:${reset}`);
513
- console.log(` 1. ${cyan}calculator${reset} - Perform math calculations`);
514
- console.log(` 2. ${cyan}get_current_time${reset} - Get current date/time`);
515
- console.log(` 3. ${cyan}web_search${reset} - Search the web (requires API key)`);
516
- console.log(` 4. ${cyan}read_file${reset} - Read files (requires configuration)`);
517
- console.log(` 5. ${cyan}None${reset} - No tools`);
518
- const toolsChoice = await prompt(rl, `${cyan}Select tools (comma-separated, e.g., 1,2 or 5 for none):${reset} `);
625
+ const predefinedTools = ['calculator', 'get_current_time', 'web_search', 'read_file'];
626
+ predefinedTools.forEach((tool, idx) => {
627
+ console.log(` ${idx + 1}. ${cyan}${tool}${reset}`);
628
+ });
629
+ // Show custom tools
630
+ let customToolStartIdx = predefinedTools.length + 1;
631
+ if (customTools.size > 0) {
632
+ customTools.forEach((tool) => {
633
+ console.log(` ${customToolStartIdx}. ${cyan}${tool.name}${reset} ${gray}(custom)${reset}`);
634
+ customToolStartIdx++;
635
+ });
636
+ }
637
+ const noneIdx = customToolStartIdx;
638
+ const createIdx = noneIdx + 1;
639
+ console.log(` ${noneIdx}. ${cyan}None${reset} - No tools`);
640
+ console.log(` ${createIdx}. ${cyan}Create New${reset} - Create a custom tool`);
641
+ const toolsChoice = await prompt(rl, `${cyan}Select tools (comma-separated, e.g., 1,2 or ${noneIdx} for none):${reset} `);
519
642
  const tools = [];
520
- if (toolsChoice && toolsChoice !== '5') {
521
- const toolIndices = toolsChoice.split(',').map(s => parseInt(s.trim()));
522
- const toolNames = ['calculator', 'get_current_time', 'web_search', 'read_file'];
523
- toolIndices.forEach(idx => {
524
- if (idx >= 1 && idx <= 4) {
525
- tools.push(toolNames[idx - 1]);
643
+ if (toolsChoice) {
644
+ const choiceNums = toolsChoice.split(',').map(s => parseInt(s.trim()));
645
+ choiceNums.forEach(choiceNum => {
646
+ if (choiceNum >= 1 && choiceNum <= predefinedTools.length) {
647
+ // Predefined tool
648
+ tools.push(predefinedTools[choiceNum - 1]);
649
+ }
650
+ else if (choiceNum > predefinedTools.length && choiceNum < noneIdx) {
651
+ // Custom tool
652
+ const customToolArray = Array.from(customTools.keys());
653
+ const customToolIdx = choiceNum - predefinedTools.length - 1;
654
+ if (customToolIdx >= 0 && customToolIdx < customToolArray.length) {
655
+ tools.push(customToolArray[customToolIdx]);
656
+ }
657
+ }
658
+ else if (choiceNum === createIdx) {
659
+ // Create new tool - handled below
526
660
  }
527
661
  });
662
+ // If create was selected, handle it
663
+ if (choiceNums.includes(createIdx)) {
664
+ console.log(`\n${yellow}Creating new tool...${reset}`);
665
+ console.log(`${gray}After creating the tool, run ${bright}build${reset}${gray} again to add it to this agent${reset}\n`);
666
+ // Trigger tools create command
667
+ await commands.tools.handler(['create'], rl);
668
+ // After creating, ask if they want to add it
669
+ const newToolName = await prompt(rl, `${cyan}Tool name to add to this agent (or Enter to skip):${reset} `);
670
+ if (newToolName && customTools.has(newToolName.toLowerCase())) {
671
+ tools.push(newToolName);
672
+ }
673
+ }
528
674
  }
529
675
  // Confirm
530
676
  console.log(`\n${dim}┌─ AGENT CONFIGURATION ─────────────────────────┐${reset}`);
@@ -588,20 +734,41 @@ const commands = {
588
734
  description: 'List available agents',
589
735
  usage: 'agents',
590
736
  handler: async () => {
591
- console.log(`\n${bright}Available Pre-built Agents:${reset}\n`);
592
- console.log(` ${cyan}gpt4-assistant${reset} General-purpose GPT-4 assistant`);
593
- console.log(` ${cyan}claude-assistant${reset} General-purpose Claude assistant`);
594
- console.log(` ${cyan}code-reviewer${reset} Code review and analysis`);
595
- console.log(` ${cyan}data-analyst${reset} Data analysis and insights`);
596
- console.log(` ${cyan}creative-writer${reset} Creative writing and content`);
597
- console.log(`\n${gray}Use ${bright}build${reset}${gray} to create custom agents${reset}\n`);
737
+ console.log(`\n${bright}Available Agents:${reset}\n`);
738
+ // Show built-in agents
739
+ console.log(`${bright}Built-in Agents:${reset}`);
740
+ console.log(` ${cyan}assistant${reset} GPT-3.5 Assistant (fast, economical)`);
741
+ console.log(` ${cyan}gpt4${reset} GPT-4 Assistant (most capable)`);
742
+ console.log(` ${cyan}test${reset} Test agent (mock, no AI)`);
743
+ // Show custom agents
744
+ if (customAgents.size > 0) {
745
+ console.log(`\n${bright}Custom Agents:${reset}`);
746
+ customAgents.forEach((agent) => {
747
+ const modelInfo = agent.useLocalModel
748
+ ? ` (Local: ${agent.localModelProvider}/${agent.localModelName})`
749
+ : ` (${agent.baseAgent})`;
750
+ const toolsInfo = agent.tools && agent.tools.length > 0
751
+ ? ` [Tools: ${agent.tools.join(', ')}]`
752
+ : '';
753
+ console.log(` ${cyan}${agent.name}${reset}${modelInfo}${toolsInfo}`);
754
+ if (agent.description) {
755
+ console.log(` ${gray}${agent.description}${reset}`);
756
+ }
757
+ });
758
+ }
759
+ else {
760
+ console.log(`\n${gray}No custom agents yet.${reset}`);
761
+ }
762
+ console.log(`\n${gray}Use ${bright}build${reset}${gray} to create custom agents${reset}`);
763
+ console.log(`${gray}Use ${bright}tools${reset}${gray} to manage tools${reset}\n`);
598
764
  }
599
765
  },
600
766
  'run-agent': {
601
767
  description: 'Start chatting with an AI agent',
602
768
  usage: 'run-agent [agent-name]',
603
769
  handler: async (args, rl) => {
604
- let agentName = args[0];
770
+ // Join all args to support multi-word agent names
771
+ let agentName = args.join(' ').trim();
605
772
  // If no agent specified, prompt for one
606
773
  if (!agentName) {
607
774
  console.log(`\n${bright}Available Agents:${reset}\n`);
@@ -638,7 +805,13 @@ const commands = {
638
805
  let agentConfig = null;
639
806
  if (customAgent) {
640
807
  // Use custom agent
641
- actualAgentId = customAgent.baseAgent;
808
+ // For local models, use gpt-3.5-turbo as the base executor (gateway handles local models)
809
+ if (customAgent.useLocalModel) {
810
+ actualAgentId = 'gpt-3.5-turbo';
811
+ }
812
+ else {
813
+ actualAgentId = customAgent.baseAgent;
814
+ }
642
815
  agentConfig = customAgent;
643
816
  }
644
817
  else {
@@ -844,6 +1017,224 @@ const commands = {
844
1017
  console.log(`${gray}Goodbye!${reset}\n`);
845
1018
  process.exit(0);
846
1019
  }
1020
+ },
1021
+ tools: {
1022
+ description: 'Manage tools for agents',
1023
+ usage: 'tools [list|create|edit|delete|test] [tool-name]',
1024
+ handler: async (args, rl) => {
1025
+ const action = args[0] || 'list';
1026
+ if (action === 'list') {
1027
+ console.log(`\n${bright}Available Tools:${reset}\n`);
1028
+ // Predefined tools
1029
+ console.log(`${bright}Predefined Tools:${reset}`);
1030
+ console.log(` ${cyan}calculator${reset} - Perform mathematical calculations`);
1031
+ console.log(` ${cyan}get_current_time${reset} - Get current date and time`);
1032
+ console.log(` ${cyan}web_search${reset} - Search the web (requires API key)`);
1033
+ console.log(` ${cyan}read_file${reset} - Read files from filesystem`);
1034
+ // Custom tools
1035
+ if (customTools.size > 0) {
1036
+ console.log(`\n${bright}Custom Tools:${reset}`);
1037
+ customTools.forEach((tool) => {
1038
+ console.log(` ${cyan}${tool.name}${reset}${tool.description ? ` - ${tool.description}` : ''}`);
1039
+ if (tool.version) {
1040
+ console.log(` ${gray}Version: ${tool.version}${reset}`);
1041
+ }
1042
+ if (tool.author) {
1043
+ console.log(` ${gray}Author: ${tool.author}${reset}`);
1044
+ }
1045
+ });
1046
+ }
1047
+ console.log(`\n${gray}Use ${bright}tools create${reset}${gray} to create a custom tool${reset}`);
1048
+ console.log(`${gray}Use ${bright}tools edit <name>${reset}${gray} to edit a tool${reset}`);
1049
+ console.log(`${gray}Use ${bright}tools delete <name>${reset}${gray} to delete a tool${reset}\n`);
1050
+ }
1051
+ else if (action === 'create') {
1052
+ console.log(`\n${dim}┌─ TOOL BUILDER ───────────────────────────────┐${reset}`);
1053
+ console.log(`${dim}│${reset} ${brightGreen}Create Custom Tool${reset}`);
1054
+ console.log(`${dim}└──────────────────────────────────────────────────┘${reset}\n`);
1055
+ const name = await prompt(rl, `${cyan}Tool name:${reset} `);
1056
+ if (!name) {
1057
+ console.log(`${red}Tool name required${reset}\n`);
1058
+ return;
1059
+ }
1060
+ if (customTools.has(name.toLowerCase())) {
1061
+ console.log(`${red}Tool '${name}' already exists${reset}\n`);
1062
+ return;
1063
+ }
1064
+ const description = await prompt(rl, `${cyan}Tool description:${reset} `);
1065
+ console.log(`\n${gray}Enter the tool code (JavaScript/TypeScript):${reset}`);
1066
+ console.log(`${gray}The tool function should accept (input: any) and return Promise<any>${reset}`);
1067
+ console.log(`${gray}Type 'END' on a new line when finished:${reset}\n`);
1068
+ const code = await promptMultiline(rl, `${cyan}Tool code:${reset}`, 'END');
1069
+ if (!code || code.trim().length === 0) {
1070
+ console.log(`${red}Tool code required${reset}\n`);
1071
+ return;
1072
+ }
1073
+ // Validate code has async function
1074
+ if (!code.includes('async') && !code.includes('Promise')) {
1075
+ console.log(`${yellow}⚠ Warning: Tool should be async or return a Promise${reset}`);
1076
+ }
1077
+ const version = await prompt(rl, `${cyan}Version (optional, default: 1.0.0):${reset} `) || '1.0.0';
1078
+ const author = await prompt(rl, `${cyan}Author (optional):${reset} `);
1079
+ // Ask for parameters
1080
+ const parameters = [];
1081
+ console.log(`\n${bright}Tool Parameters (optional):${reset}`);
1082
+ console.log(`${gray}Press Enter to skip, or enter parameter details${reset}`);
1083
+ let addParam = true;
1084
+ while (addParam) {
1085
+ const paramName = await prompt(rl, `${cyan}Parameter name (or Enter to finish):${reset} `);
1086
+ if (!paramName) {
1087
+ addParam = false;
1088
+ break;
1089
+ }
1090
+ const paramType = await prompt(rl, `${cyan}Parameter type (string|number|boolean|object):${reset} `) || 'string';
1091
+ const paramDesc = await prompt(rl, `${cyan}Parameter description:${reset} `);
1092
+ const paramRequired = await prompt(rl, `${cyan}Required? (yes/no, default: yes):${reset} `);
1093
+ parameters.push({
1094
+ name: paramName,
1095
+ type: paramType,
1096
+ description: paramDesc,
1097
+ required: paramRequired.toLowerCase() !== 'no'
1098
+ });
1099
+ }
1100
+ const tool = {
1101
+ name,
1102
+ description: description || '',
1103
+ code,
1104
+ parameters: parameters.length > 0 ? parameters : undefined,
1105
+ version,
1106
+ author
1107
+ };
1108
+ customTools.set(name.toLowerCase(), tool);
1109
+ saveCustomTools();
1110
+ console.log(`\n${green}✓${reset} Tool created: ${bright}${name}${reset}`);
1111
+ console.log(` Use ${cyan}tools test ${name}${reset} to test it${reset}`);
1112
+ console.log(` Add it to agents with: ${cyan}build${reset}${gray} (select tools)${reset}\n`);
1113
+ }
1114
+ else if (action === 'edit') {
1115
+ const toolName = args[1];
1116
+ if (!toolName) {
1117
+ console.log(`${red}Tool name required${reset}`);
1118
+ console.log(`${gray}Usage: tools edit <tool-name>${reset}\n`);
1119
+ return;
1120
+ }
1121
+ const tool = customTools.get(toolName.toLowerCase());
1122
+ if (!tool) {
1123
+ console.log(`${red}Tool '${toolName}' not found${reset}\n`);
1124
+ return;
1125
+ }
1126
+ console.log(`\n${bright}Editing tool: ${tool.name}${reset}\n`);
1127
+ const newDescription = await prompt(rl, `${cyan}Description (current: ${tool.description}):${reset} `) || tool.description;
1128
+ console.log(`\n${gray}Current code:${reset}`);
1129
+ console.log(`${gray}${'─'.repeat(50)}${reset}`);
1130
+ console.log(tool.code);
1131
+ console.log(`${gray}${'─'.repeat(50)}${reset}\n`);
1132
+ console.log(`${gray}Enter new code (or press Enter to keep current):${reset}`);
1133
+ const newCode = await promptMultiline(rl, `${cyan}Tool code:${reset}`, 'END');
1134
+ tool.description = newDescription;
1135
+ if (newCode && newCode.trim().length > 0) {
1136
+ tool.code = newCode;
1137
+ }
1138
+ saveCustomTools();
1139
+ console.log(`\n${green}✓${reset} Tool updated: ${bright}${tool.name}${reset}\n`);
1140
+ }
1141
+ else if (action === 'delete') {
1142
+ const toolName = args[1];
1143
+ if (!toolName) {
1144
+ console.log(`${red}Tool name required${reset}`);
1145
+ console.log(`${gray}Usage: tools delete <tool-name>${reset}\n`);
1146
+ return;
1147
+ }
1148
+ const tool = customTools.get(toolName.toLowerCase());
1149
+ if (!tool) {
1150
+ console.log(`${red}Tool '${toolName}' not found${reset}\n`);
1151
+ return;
1152
+ }
1153
+ const confirm = await prompt(rl, `${yellow}Delete tool '${tool.name}'? (yes/no):${reset} `);
1154
+ if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') {
1155
+ customTools.delete(toolName.toLowerCase());
1156
+ saveCustomTools();
1157
+ console.log(`\n${green}✓${reset} Tool deleted: ${bright}${tool.name}${reset}\n`);
1158
+ }
1159
+ else {
1160
+ console.log(`${yellow}Deletion cancelled${reset}\n`);
1161
+ }
1162
+ }
1163
+ else if (action === 'test') {
1164
+ const toolName = args[1];
1165
+ if (!toolName) {
1166
+ console.log(`${red}Tool name required${reset}`);
1167
+ console.log(`${gray}Usage: tools test <tool-name>${reset}\n`);
1168
+ return;
1169
+ }
1170
+ const tool = customTools.get(toolName.toLowerCase());
1171
+ if (!tool) {
1172
+ console.log(`${red}Tool '${toolName}' not found${reset}\n`);
1173
+ return;
1174
+ }
1175
+ console.log(`\n${bright}Testing tool: ${tool.name}${reset}\n`);
1176
+ console.log(`${gray}Tool code:${reset}`);
1177
+ console.log(`${gray}${'─'.repeat(50)}${reset}`);
1178
+ console.log(tool.code);
1179
+ console.log(`${gray}${'─'.repeat(50)}${reset}\n`);
1180
+ // Get test input
1181
+ const testInput = await prompt(rl, `${cyan}Test input (JSON or plain text):${reset} `);
1182
+ try {
1183
+ // Create a safe execution context
1184
+ const vm = await import('vm');
1185
+ const context = {
1186
+ console,
1187
+ setTimeout,
1188
+ setInterval,
1189
+ clearTimeout,
1190
+ clearInterval,
1191
+ Buffer,
1192
+ require: (module) => {
1193
+ // Whitelist safe modules
1194
+ const allowed = ['fs', 'path', 'os', 'crypto', 'url', 'http', 'https', 'axios'];
1195
+ if (allowed.includes(module)) {
1196
+ return require(module);
1197
+ }
1198
+ throw new Error(`Module '${module}' is not allowed`);
1199
+ }
1200
+ };
1201
+ // Parse test input
1202
+ let parsedInput = testInput;
1203
+ try {
1204
+ parsedInput = JSON.parse(testInput);
1205
+ }
1206
+ catch {
1207
+ // Use as string if not valid JSON
1208
+ }
1209
+ // Wrap tool code in async function
1210
+ const wrappedCode = `
1211
+ (async () => {
1212
+ ${tool.code}
1213
+ // Tool should export a function or be a function
1214
+ if (typeof tool === 'function') {
1215
+ return await tool(${JSON.stringify(parsedInput)});
1216
+ } else if (typeof execute === 'function') {
1217
+ return await execute(${JSON.stringify(parsedInput)});
1218
+ } else {
1219
+ throw new Error('Tool must export a function named "tool" or "execute"');
1220
+ }
1221
+ })()
1222
+ `;
1223
+ const result = await vm.default.runInNewContext(wrappedCode, context, { timeout: 5000 });
1224
+ console.log(`\n${green}✓${reset} Tool executed successfully:`);
1225
+ console.log(JSON.stringify(result, null, 2));
1226
+ console.log();
1227
+ }
1228
+ catch (error) {
1229
+ console.log(`\n${red}✗${reset} Tool execution failed:`);
1230
+ console.log(`${red}${error.message}${reset}\n`);
1231
+ }
1232
+ }
1233
+ else {
1234
+ console.log(`${red}Unknown action: ${action}${reset}`);
1235
+ console.log(`${gray}Available actions: list, create, edit, delete, test${reset}\n`);
1236
+ }
1237
+ }
847
1238
  }
848
1239
  };
849
1240
  function getStatusColor(status) {
@@ -865,6 +1256,26 @@ async function startREPL() {
865
1256
  showWelcome();
866
1257
  rl.prompt();
867
1258
  rl.on('line', async (line) => {
1259
+ // Handle multiline mode FIRST (before any command processing)
1260
+ if (multilineMode && multilineResolver) {
1261
+ const trimmed = line.trim();
1262
+ // Check for end marker (must be exactly END, case-insensitive)
1263
+ if (trimmed.toUpperCase() === multilineEndMarker.toUpperCase()) {
1264
+ multilineMode = false;
1265
+ const result = multilineLines.join('\n').trim();
1266
+ const resolver = multilineResolver;
1267
+ multilineResolver = null;
1268
+ multilineLines = [];
1269
+ // Restore normal prompt
1270
+ rl.setPrompt(`${cyan}4runr>${reset} `);
1271
+ resolver(result);
1272
+ return;
1273
+ }
1274
+ // Collect the line (preserve original line including empty lines)
1275
+ multilineLines.push(line);
1276
+ rl.prompt();
1277
+ return;
1278
+ }
868
1279
  const input = line.trim();
869
1280
  if (!input) {
870
1281
  rl.prompt();
@@ -888,50 +1299,104 @@ async function startREPL() {
888
1299
  try {
889
1300
  // Get custom agent config if available
890
1301
  const agentConfig = rl.chatAgentConfig;
891
- // Create and execute run
892
- const run = await client.runs.create({
893
- name: `${chatAgent}: ${message.substring(0, 50)}...`,
894
- input: {
895
- agent_id: chatAgent,
896
- data: {
897
- prompt: message,
898
- // Include custom agent settings
899
- ...(agentConfig?.systemPrompt && { systemPrompt: agentConfig.systemPrompt }),
900
- ...(agentConfig?.temperature !== undefined && { temperature: agentConfig.temperature }),
901
- ...(agentConfig?.maxTokens !== undefined && { maxTokens: agentConfig.maxTokens }),
902
- ...(conversationHistory.length > 0 && { conversationHistory })
903
- }
1302
+ // Check if this is a local model agent - execute directly (bypass gateway)
1303
+ if (agentConfig?.useLocalModel && agentConfig.localModelProvider && agentConfig.localModelName) {
1304
+ // Execute locally - this is the REAL local execution
1305
+ const messages = [];
1306
+ // Add system prompt
1307
+ if (agentConfig.systemPrompt) {
1308
+ messages.push({ role: 'system', content: agentConfig.systemPrompt });
904
1309
  }
905
- });
906
- await client.runs.start(run.id);
907
- // Monitor execution
908
- let status = await client.runs.get(run.id);
909
- while (status.status === 'running' || status.status === 'queued') {
910
- await sleep(500);
911
- status = await client.runs.get(run.id);
912
- }
913
- // Clear thinking indicator
914
- process.stdout.write(`\r${' '.repeat(20)}\r`);
915
- if (status.output) {
916
- const output = status.output;
917
- const response = output.result || output.message || JSON.stringify(output, null, 2);
1310
+ // Add conversation history
1311
+ if (conversationHistory.length > 0) {
1312
+ messages.push(...conversationHistory);
1313
+ }
1314
+ // Add current message
1315
+ messages.push({ role: 'user', content: message });
1316
+ // Execute local model directly
1317
+ const localConfig = {
1318
+ provider: agentConfig.localModelProvider,
1319
+ model: agentConfig.localModelName,
1320
+ baseUrl: agentConfig.localModelUrl,
1321
+ temperature: agentConfig.temperature,
1322
+ maxTokens: agentConfig.maxTokens,
1323
+ topP: agentConfig.topP,
1324
+ frequencyPenalty: agentConfig.frequencyPenalty,
1325
+ presencePenalty: agentConfig.presencePenalty,
1326
+ };
1327
+ // PROOF: Verify execution is truly local before executing
1328
+ const localProof = await verifyLocalExecution();
1329
+ if (!localProof.isLocal) {
1330
+ console.log(`${red}⚠ WARNING: Local execution verification failed!${reset}`);
1331
+ console.log(`${yellow}This may indicate the request is going through a remote server.${reset}\n`);
1332
+ }
1333
+ const result = await executeLocalModel(localConfig, messages);
1334
+ // Clear thinking indicator
1335
+ process.stdout.write(`\r${' '.repeat(20)}\r`);
918
1336
  // Update conversation history
919
1337
  conversationHistory.push({ role: 'user', content: message });
920
- conversationHistory.push({ role: 'assistant', content: response });
921
- // Show response with friendly name
922
- const agentConfig = rl.chatAgentConfig;
923
- const friendlyName = agentConfig ? agentConfig.name :
924
- chatAgent === 'gpt-3.5-turbo' ? 'Assistant' :
925
- chatAgent === 'gpt-4' ? 'GPT-4' :
926
- chatAgent;
927
- console.log(`${green}${friendlyName}:${reset} ${response}\n`);
928
- // Show usage if available
929
- if (output.usage) {
930
- console.log(`${gray}Tokens: ${output.usage.totalTokens || 'N/A'} | Cost: $${(output.cost || 0).toFixed(6)}${reset}\n`);
1338
+ conversationHistory.push({ role: 'assistant', content: result.result });
1339
+ // Show response with proof it's local
1340
+ console.log(`${green}${agentConfig.name}:${reset} ${result.result}\n`);
1341
+ // Show usage and proof
1342
+ if (result.usage) {
1343
+ console.log(`${gray}Tokens: ${result.usage.totalTokens || 'N/A'} | Model: ${result.model} | Cost: $0.000000 (local)${reset}`);
1344
+ console.log(`${dim}✓ Executed locally (${localProof.proof.executionPath}) | Response time: ${localProof.proof.responseTime}ms${reset}\n`);
931
1345
  }
932
1346
  }
933
1347
  else {
934
- console.log(`${yellow}No response received${reset}\n`);
1348
+ // Remote model - execute through gateway
1349
+ const run = await client.runs.create({
1350
+ name: `${chatAgent}: ${message.substring(0, 50)}...`,
1351
+ input: {
1352
+ agent_id: chatAgent,
1353
+ data: {
1354
+ prompt: message,
1355
+ // Include custom agent settings
1356
+ ...(agentConfig?.systemPrompt && { systemPrompt: agentConfig.systemPrompt }),
1357
+ ...(agentConfig?.temperature !== undefined && { temperature: agentConfig.temperature }),
1358
+ ...(agentConfig?.maxTokens !== undefined && { maxTokens: agentConfig.maxTokens }),
1359
+ // Advanced parameters
1360
+ ...(agentConfig?.topP !== undefined && { topP: agentConfig.topP }),
1361
+ ...(agentConfig?.frequencyPenalty !== undefined && { frequencyPenalty: agentConfig.frequencyPenalty }),
1362
+ ...(agentConfig?.presencePenalty !== undefined && { presencePenalty: agentConfig.presencePenalty }),
1363
+ // Tools
1364
+ ...(agentConfig?.tools && agentConfig.tools.length > 0 && { tools: agentConfig.tools }),
1365
+ // Always include conversation history for context
1366
+ conversationHistory: conversationHistory.length > 0 ? conversationHistory : []
1367
+ }
1368
+ }
1369
+ });
1370
+ await client.runs.start(run.id);
1371
+ // Monitor execution
1372
+ let status = await client.runs.get(run.id);
1373
+ while (status.status === 'running' || status.status === 'queued') {
1374
+ await sleep(500);
1375
+ status = await client.runs.get(run.id);
1376
+ }
1377
+ // Clear thinking indicator
1378
+ process.stdout.write(`\r${' '.repeat(20)}\r`);
1379
+ if (status.output) {
1380
+ const output = status.output;
1381
+ const response = output.result || output.message || JSON.stringify(output, null, 2);
1382
+ // Update conversation history
1383
+ conversationHistory.push({ role: 'user', content: message });
1384
+ conversationHistory.push({ role: 'assistant', content: response });
1385
+ // Show response with friendly name
1386
+ const agentConfig = rl.chatAgentConfig;
1387
+ const friendlyName = agentConfig ? agentConfig.name :
1388
+ chatAgent === 'gpt-3.5-turbo' ? 'Assistant' :
1389
+ chatAgent === 'gpt-4' ? 'GPT-4' :
1390
+ chatAgent;
1391
+ console.log(`${green}${friendlyName}:${reset} ${response}\n`);
1392
+ // Show usage if available
1393
+ if (output.usage) {
1394
+ console.log(`${gray}Tokens: ${output.usage.totalTokens || 'N/A'} | Cost: $${(output.cost || 0).toFixed(6)}${reset}\n`);
1395
+ }
1396
+ }
1397
+ else {
1398
+ console.log(`${yellow}No response received${reset}\n`);
1399
+ }
935
1400
  }
936
1401
  }
937
1402
  catch (error) {