agent-world 0.4.0 → 0.4.6
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/README.md +13 -4
- package/dist/cli/index.js +92 -66
- package/dist/server/index.js +146 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -107,14 +107,23 @@ Each Agent World has a collection of agents that can communicate through a share
|
|
|
107
107
|
|
|
108
108
|
### Quick Start
|
|
109
109
|
|
|
110
|
-
**Option 1:
|
|
110
|
+
**Option 1: Web Interface**
|
|
111
111
|
```bash
|
|
112
|
-
npx agent-world
|
|
112
|
+
npx agent-world-server
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
**Option 2:
|
|
115
|
+
**Option 2: CLI Interface**
|
|
116
|
+
1. Interactive Mode
|
|
117
|
+
```bash
|
|
118
|
+
npx agent-world
|
|
119
|
+
```
|
|
120
|
+
2. Command Mode
|
|
121
|
+
```bash
|
|
122
|
+
npx agent-world -w default-world "hi"
|
|
123
|
+
```
|
|
124
|
+
3. Pipeline Mode
|
|
116
125
|
```bash
|
|
117
|
-
npx agent-world
|
|
126
|
+
echo "hi" | npx agent-world -w default-world
|
|
118
127
|
```
|
|
119
128
|
|
|
120
129
|
## Project Structure (npm workspaces)
|
package/dist/cli/index.js
CHANGED
|
@@ -38,21 +38,12 @@ import path from 'path';
|
|
|
38
38
|
import { fileURLToPath } from 'url';
|
|
39
39
|
import { program } from 'commander';
|
|
40
40
|
import readline from 'readline';
|
|
41
|
-
import { listWorlds, subscribeWorld, createCategoryLogger, LLMProvider,
|
|
41
|
+
import { listWorlds, subscribeWorld, createCategoryLogger, LLMProvider, enableStreaming, disableStreaming } from '../core/index.js';
|
|
42
|
+
import { getDefaultRootPath } from '../core/storage-factory.js';
|
|
42
43
|
import { processCLIInput } from './commands.js';
|
|
43
44
|
import { createStreamingState, handleWorldEventWithStreaming } from './stream.js';
|
|
44
45
|
import { configureLLMProvider } from '../core/llm-config.js';
|
|
45
|
-
//
|
|
46
|
-
initializeLogger({
|
|
47
|
-
globalLevel: 'error',
|
|
48
|
-
categoryLevels: {
|
|
49
|
-
cli: 'error',
|
|
50
|
-
core: 'error',
|
|
51
|
-
events: 'error',
|
|
52
|
-
llm: 'error'
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
// Create CLI category logger after initialization
|
|
46
|
+
// Create CLI category logger after logger auto-initialization
|
|
56
47
|
const logger = createCategoryLogger('cli');
|
|
57
48
|
function setupPromptTimer(globalState, rl, callback, delay = 2000) {
|
|
58
49
|
clearPromptTimer(globalState);
|
|
@@ -85,25 +76,6 @@ const boldCyan = (text) => `\x1b[1m\x1b[36m${text}\x1b[0m`;
|
|
|
85
76
|
const success = (text) => `${boldGreen('✓')} ${text}`;
|
|
86
77
|
const error = (text) => `${boldRed('✗')} ${text}`;
|
|
87
78
|
const bullet = (text) => `${gray('•')} ${text}`;
|
|
88
|
-
// Logger configuration
|
|
89
|
-
async function configureLogger(logLevel) {
|
|
90
|
-
// Use the centralized logger configuration from core
|
|
91
|
-
const level = (logLevel || 'error');
|
|
92
|
-
// Reinitialize logger with new configuration
|
|
93
|
-
initializeLogger({
|
|
94
|
-
globalLevel: level,
|
|
95
|
-
categoryLevels: {
|
|
96
|
-
cli: 'error', // Always keep CLI at error level
|
|
97
|
-
core: level, // Core modules use global level
|
|
98
|
-
events: 'error', // Keep events at error level (too verbose)
|
|
99
|
-
llm: level, // LLM module uses global level
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
// Only log the debug message if we're actually at debug level for global
|
|
103
|
-
if (level === 'debug' || level === 'trace') {
|
|
104
|
-
logger.debug(`Global log level set to: ${level}, CLI log level: error`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
79
|
// LLM Provider configuration from environment variables
|
|
108
80
|
function configureLLMProvidersFromEnv() {
|
|
109
81
|
// OpenAI
|
|
@@ -167,8 +139,23 @@ function configureLLMProvidersFromEnv() {
|
|
|
167
139
|
logger.debug('Configured Ollama provider with default URL');
|
|
168
140
|
}
|
|
169
141
|
}
|
|
170
|
-
|
|
171
|
-
const DEFAULT_ROOT_PATH =
|
|
142
|
+
// Get default root path from storage-factory (no local defaults)
|
|
143
|
+
const DEFAULT_ROOT_PATH = getDefaultRootPath();
|
|
144
|
+
// Helper to print CLI results in a user-friendly way
|
|
145
|
+
function printCLIResult(result) {
|
|
146
|
+
if (result.success) {
|
|
147
|
+
if (result.message)
|
|
148
|
+
console.log(success(result.message));
|
|
149
|
+
if (result.data && typeof result.data === 'string')
|
|
150
|
+
console.log(result.data);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
if (result.message)
|
|
154
|
+
console.log(error(result.message));
|
|
155
|
+
if (result.error && result.error !== result.message)
|
|
156
|
+
console.log(error(result.error));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
172
159
|
// Pipeline mode execution with timer-based cleanup
|
|
173
160
|
async function runPipelineMode(options, messageFromArgs) {
|
|
174
161
|
const rootPath = options.root || DEFAULT_ROOT_PATH;
|
|
@@ -182,8 +169,12 @@ async function runPipelineMode(options, messageFromArgs) {
|
|
|
182
169
|
onWorldEvent: (eventType, eventData) => {
|
|
183
170
|
if (eventData.content && eventData.content.includes('Success message sent'))
|
|
184
171
|
return;
|
|
185
|
-
if ((eventType === 'system' || eventType === 'world') && eventData.message) {
|
|
186
|
-
|
|
172
|
+
if ((eventType === 'system' || eventType === 'world') && (eventData.message || eventData.content)) {
|
|
173
|
+
// existing logic
|
|
174
|
+
}
|
|
175
|
+
else if (eventType === 'message' && eventData.sender === 'system') {
|
|
176
|
+
const msg = eventData.content;
|
|
177
|
+
console.log(`${boldRed('● system:')} ${msg}`);
|
|
187
178
|
}
|
|
188
179
|
if (eventType === 'sse' && eventData.content) {
|
|
189
180
|
setupExitTimer(5000);
|
|
@@ -221,7 +212,7 @@ async function runPipelineMode(options, messageFromArgs) {
|
|
|
221
212
|
process.exit(1);
|
|
222
213
|
}
|
|
223
214
|
const result = await processCLIInput(options.command, world, rootPath, 'HUMAN');
|
|
224
|
-
|
|
215
|
+
printCLIResult(result);
|
|
225
216
|
// Only set timer if sending message to world (not for commands)
|
|
226
217
|
if (!options.command.startsWith('/') && world) {
|
|
227
218
|
setupExitTimer();
|
|
@@ -244,7 +235,7 @@ async function runPipelineMode(options, messageFromArgs) {
|
|
|
244
235
|
process.exit(1);
|
|
245
236
|
}
|
|
246
237
|
const result = await processCLIInput(messageFromArgs, world, rootPath, 'HUMAN');
|
|
247
|
-
|
|
238
|
+
printCLIResult(result);
|
|
248
239
|
// Set timer with longer delay for message processing (always needed for messages)
|
|
249
240
|
setupExitTimer(8000);
|
|
250
241
|
if (!result.success) {
|
|
@@ -264,7 +255,7 @@ async function runPipelineMode(options, messageFromArgs) {
|
|
|
264
255
|
process.exit(1);
|
|
265
256
|
}
|
|
266
257
|
const result = await processCLIInput(input.trim(), world, rootPath, 'HUMAN');
|
|
267
|
-
|
|
258
|
+
printCLIResult(result);
|
|
268
259
|
// Set timer with longer delay for message processing (always needed for stdin messages)
|
|
269
260
|
setupExitTimer(8000);
|
|
270
261
|
if (!result.success) {
|
|
@@ -311,8 +302,12 @@ function handleWorldEvent(eventType, eventData, streaming, globalState, rl) {
|
|
|
311
302
|
}
|
|
312
303
|
if (eventData.content && eventData.content.includes('Success message sent'))
|
|
313
304
|
return;
|
|
314
|
-
if ((eventType === 'system' || eventType === 'world') && eventData.message) {
|
|
315
|
-
|
|
305
|
+
if ((eventType === 'system' || eventType === 'world') && (eventData.message || eventData.content)) {
|
|
306
|
+
// existing logic
|
|
307
|
+
}
|
|
308
|
+
else if (eventType === 'message' && eventData.sender === 'system') {
|
|
309
|
+
const msg = eventData.content;
|
|
310
|
+
console.log(`${boldRed('● system:')} ${msg}`);
|
|
316
311
|
}
|
|
317
312
|
}
|
|
318
313
|
// World discovery and selection
|
|
@@ -444,7 +439,9 @@ async function runInteractiveMode(options) {
|
|
|
444
439
|
}
|
|
445
440
|
// Show usage tips
|
|
446
441
|
console.log(`\n${gray('Tips:')}`);
|
|
447
|
-
console.log(` ${bullet(gray('
|
|
442
|
+
console.log(` ${bullet(gray('Short commands:'))} ${cyan('/list')}, ${cyan('/show agent1')}, ${cyan('/edit agent1')}, ${cyan('/del agent1')}`);
|
|
443
|
+
console.log(` ${bullet(gray('Context-sensitive:'))} ${cyan('/list')} ${gray('shows agents if world selected, worlds otherwise')}`);
|
|
444
|
+
console.log(` ${bullet(gray('Legacy commands:'))} ${cyan('/clear agent1')}, ${cyan('/clear all')}, ${cyan('/add MyAgent')}`);
|
|
448
445
|
console.log(` ${bullet(gray('Use'))} ${cyan('/select')} ${gray('to choose a different world')}`);
|
|
449
446
|
console.log(` ${bullet(gray('Type messages to send to agents'))}`);
|
|
450
447
|
console.log(` ${bullet(gray('Use'))} ${cyan('/quit')} ${gray('or')} ${cyan('/exit')} ${gray('to exit, or press')} ${boldYellow('Ctrl+C')}`);
|
|
@@ -457,23 +454,39 @@ async function runInteractiveMode(options) {
|
|
|
457
454
|
rl.prompt();
|
|
458
455
|
return;
|
|
459
456
|
}
|
|
457
|
+
// Check for exit commands before anything else
|
|
458
|
+
const isExitCommand = trimmedInput.toLowerCase() === '/exit' || trimmedInput.toLowerCase() === '/quit';
|
|
459
|
+
if (isExitCommand) {
|
|
460
|
+
if (isExiting)
|
|
461
|
+
return;
|
|
462
|
+
isExiting = true;
|
|
463
|
+
// Clear any existing timers immediately
|
|
464
|
+
clearPromptTimer(globalState);
|
|
465
|
+
if (streaming.stopWait)
|
|
466
|
+
streaming.stopWait();
|
|
467
|
+
console.log(`\n${boldCyan('Goodbye!')}`);
|
|
468
|
+
if (worldState)
|
|
469
|
+
cleanupWorldSubscription(worldState);
|
|
470
|
+
rl.close();
|
|
471
|
+
process.exit(0);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
460
474
|
console.log(`\n${boldYellow('● you:')} ${trimmedInput}`);
|
|
461
475
|
try {
|
|
462
476
|
const result = await processCLIInput(trimmedInput, worldState?.world || null, rootPath, 'HUMAN');
|
|
463
|
-
// Handle exit commands
|
|
477
|
+
// Handle exit commands from result (redundant, but keep for safety)
|
|
464
478
|
if (result.data?.exit) {
|
|
465
479
|
if (isExiting)
|
|
466
480
|
return; // Prevent duplicate exit handling
|
|
467
481
|
isExiting = true;
|
|
468
|
-
|
|
469
|
-
if (streaming.stopWait)
|
|
482
|
+
clearPromptTimer(globalState);
|
|
483
|
+
if (streaming.stopWait)
|
|
470
484
|
streaming.stopWait();
|
|
471
|
-
}
|
|
472
485
|
console.log(`\n${boldCyan('Goodbye!')}`);
|
|
473
|
-
if (worldState)
|
|
486
|
+
if (worldState)
|
|
474
487
|
cleanupWorldSubscription(worldState);
|
|
475
|
-
}
|
|
476
488
|
rl.close();
|
|
489
|
+
process.exit(0);
|
|
477
490
|
return;
|
|
478
491
|
}
|
|
479
492
|
// Handle world selection command
|
|
@@ -520,7 +533,23 @@ async function runInteractiveMode(options) {
|
|
|
520
533
|
console.log(success(result.message));
|
|
521
534
|
}
|
|
522
535
|
if (result.data && !(result.data.sender === 'HUMAN')) {
|
|
523
|
-
|
|
536
|
+
// Print a concise summary of result.data if present and not already in message
|
|
537
|
+
if (result.data) {
|
|
538
|
+
if (typeof result.data === 'string') {
|
|
539
|
+
console.log(`${boldMagenta('Data:')} ${result.data}`);
|
|
540
|
+
}
|
|
541
|
+
else if (result.data.name) {
|
|
542
|
+
// If it's an agent or world object
|
|
543
|
+
console.log(`${boldMagenta('Data:')} ${result.data.name}`);
|
|
544
|
+
}
|
|
545
|
+
else if (Array.isArray(result.data)) {
|
|
546
|
+
console.log(`${boldMagenta('Data:')} ${result.data.length} items`);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// Fallback: print keys
|
|
550
|
+
console.log(`${boldMagenta('Data:')} ${Object.keys(result.data).join(', ')}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
524
553
|
}
|
|
525
554
|
// Refresh world if needed
|
|
526
555
|
if (result.refreshWorld && currentWorldName && worldState) {
|
|
@@ -541,13 +570,8 @@ async function runInteractiveMode(options) {
|
|
|
541
570
|
}
|
|
542
571
|
// Set timer based on input type: commands get short delay, messages get longer delay
|
|
543
572
|
const isCommand = trimmedInput.startsWith('/');
|
|
544
|
-
const isExitCommand = trimmedInput.toLowerCase() === '/exit' || trimmedInput.toLowerCase() === '/quit';
|
|
545
573
|
const isSelectCommand = trimmedInput.toLowerCase() === '/select';
|
|
546
|
-
if (
|
|
547
|
-
// For exit commands, don't set any timer - exit should be immediate
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
else if (isSelectCommand) {
|
|
574
|
+
if (isSelectCommand) {
|
|
551
575
|
// For select command, prompt is already shown in the handler
|
|
552
576
|
return;
|
|
553
577
|
}
|
|
@@ -564,27 +588,26 @@ async function runInteractiveMode(options) {
|
|
|
564
588
|
if (isExiting)
|
|
565
589
|
return; // Prevent duplicate cleanup
|
|
566
590
|
isExiting = true;
|
|
591
|
+
clearPromptTimer(globalState);
|
|
592
|
+
if (streaming.stopWait)
|
|
593
|
+
streaming.stopWait();
|
|
567
594
|
console.log(`\n${boldCyan('Goodbye!')}`);
|
|
568
|
-
if (worldState)
|
|
569
|
-
if (streaming.stopWait) {
|
|
570
|
-
streaming.stopWait();
|
|
571
|
-
}
|
|
595
|
+
if (worldState)
|
|
572
596
|
cleanupWorldSubscription(worldState);
|
|
573
|
-
}
|
|
574
597
|
process.exit(0);
|
|
575
598
|
});
|
|
576
599
|
rl.on('SIGINT', () => {
|
|
577
600
|
if (isExiting)
|
|
578
601
|
return; // Prevent duplicate cleanup
|
|
579
602
|
isExiting = true;
|
|
603
|
+
clearPromptTimer(globalState);
|
|
604
|
+
if (streaming.stopWait)
|
|
605
|
+
streaming.stopWait();
|
|
580
606
|
console.log(`\n${boldCyan('Goodbye!')}`);
|
|
581
|
-
if (worldState)
|
|
582
|
-
if (streaming.stopWait) {
|
|
583
|
-
streaming.stopWait();
|
|
584
|
-
}
|
|
607
|
+
if (worldState)
|
|
585
608
|
cleanupWorldSubscription(worldState);
|
|
586
|
-
}
|
|
587
609
|
rl.close();
|
|
610
|
+
process.exit(0);
|
|
588
611
|
});
|
|
589
612
|
}
|
|
590
613
|
catch (err) {
|
|
@@ -597,6 +620,9 @@ async function runInteractiveMode(options) {
|
|
|
597
620
|
async function main() {
|
|
598
621
|
// Configure LLM providers from environment variables at startup
|
|
599
622
|
configureLLMProvidersFromEnv();
|
|
623
|
+
// Import help generator from commands.ts
|
|
624
|
+
// (import at top: import { generateHelpMessage } from './commands.js';)
|
|
625
|
+
const { generateHelpMessage } = await import('./commands.js');
|
|
600
626
|
program
|
|
601
627
|
.name('cli')
|
|
602
628
|
.description('Agent World CLI')
|
|
@@ -608,6 +634,8 @@ async function main() {
|
|
|
608
634
|
.option('-s, --server', 'Launch the server before running CLI')
|
|
609
635
|
.allowUnknownOption()
|
|
610
636
|
.allowExcessArguments()
|
|
637
|
+
.helpOption('-h, --help', 'Display help for command')
|
|
638
|
+
.addHelpText('beforeAll', () => generateHelpMessage())
|
|
611
639
|
.parse();
|
|
612
640
|
const options = program.opts();
|
|
613
641
|
// If --server is specified, launch the server first
|
|
@@ -628,8 +656,6 @@ async function main() {
|
|
|
628
656
|
// If server exits, exit CLI as well
|
|
629
657
|
process.exit(serverProcess.status || 0);
|
|
630
658
|
}
|
|
631
|
-
// Configure logger - set global level first, then CLI-specific level
|
|
632
|
-
await configureLogger(options.logLevel);
|
|
633
659
|
const args = program.args;
|
|
634
660
|
const messageFromArgs = args.length > 0 ? args.join(' ') : null;
|
|
635
661
|
const isPipelineMode = !!(options.command ||
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Web Server - Express.js HTTP Server with REST API and SSE
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Express server with CORS, static files, and modular API routing
|
|
7
|
+
* - Environment-based LLM provider configuration
|
|
8
|
+
* - Category-based logging with configurable levels
|
|
9
|
+
* - Health check endpoint and proper error handling
|
|
10
|
+
* - Environment Variables: Automatically loads .env file for API keys and configuration
|
|
11
|
+
*
|
|
12
|
+
* Configuration: AGENT_WORLD_DATA_PATH, LOG_LEVEL, LLM provider keys
|
|
13
|
+
* Endpoints: /health + API routes from ./api.ts
|
|
14
|
+
*/
|
|
15
|
+
// Load environment variables from .env file
|
|
16
|
+
import dotenv from 'dotenv';
|
|
17
|
+
dotenv.config();
|
|
18
|
+
import open from 'open';
|
|
19
|
+
import { createCategoryLogger, LLMProvider } from '../core/index.js';
|
|
20
|
+
import { configureLLMProvider } from '../core/llm-config.js';
|
|
21
|
+
import express from 'express';
|
|
22
|
+
import cors from 'cors';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
25
|
+
import apiRouter from './api.js';
|
|
26
|
+
// ES modules setup
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = path.dirname(__filename);
|
|
29
|
+
// Configuration
|
|
30
|
+
const PORT = Number(process.env.PORT) || 0;
|
|
31
|
+
const HOST = process.env.HOST || '127.0.0.1';
|
|
32
|
+
// Create server logger after logger auto-initialization
|
|
33
|
+
const serverLogger = createCategoryLogger('server');
|
|
34
|
+
// LLM provider configuration
|
|
35
|
+
function configureLLMProvidersFromEnv() {
|
|
36
|
+
const providers = [
|
|
37
|
+
{ env: 'OPENAI_API_KEY', provider: LLMProvider.OPENAI, config: (key) => ({ apiKey: key }) },
|
|
38
|
+
{ env: 'ANTHROPIC_API_KEY', provider: LLMProvider.ANTHROPIC, config: (key) => ({ apiKey: key }) },
|
|
39
|
+
{ env: 'GOOGLE_API_KEY', provider: LLMProvider.GOOGLE, config: (key) => ({ apiKey: key }) },
|
|
40
|
+
{ env: 'XAI_API_KEY', provider: LLMProvider.XAI, config: (key) => ({ apiKey: key }) }
|
|
41
|
+
];
|
|
42
|
+
providers.forEach(({ env, provider, config }) => {
|
|
43
|
+
const key = process.env[env];
|
|
44
|
+
if (key) {
|
|
45
|
+
configureLLMProvider(provider, config(key));
|
|
46
|
+
serverLogger.debug(`Configured ${provider} provider from environment`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// Azure (requires multiple env vars)
|
|
50
|
+
if (process.env.AZURE_OPENAI_API_KEY && process.env.AZURE_ENDPOINT && process.env.AZURE_DEPLOYMENT) {
|
|
51
|
+
configureLLMProvider(LLMProvider.AZURE, {
|
|
52
|
+
apiKey: process.env.AZURE_OPENAI_API_KEY,
|
|
53
|
+
endpoint: process.env.AZURE_ENDPOINT,
|
|
54
|
+
deployment: process.env.AZURE_DEPLOYMENT,
|
|
55
|
+
apiVersion: process.env.AZURE_API_VERSION || '2023-12-01-preview'
|
|
56
|
+
});
|
|
57
|
+
serverLogger.debug('Configured Azure provider from environment');
|
|
58
|
+
}
|
|
59
|
+
// OpenAI Compatible
|
|
60
|
+
if (process.env.OPENAI_COMPATIBLE_API_KEY && process.env.OPENAI_COMPATIBLE_BASE_URL) {
|
|
61
|
+
configureLLMProvider(LLMProvider.OPENAI_COMPATIBLE, {
|
|
62
|
+
apiKey: process.env.OPENAI_COMPATIBLE_API_KEY,
|
|
63
|
+
baseUrl: process.env.OPENAI_COMPATIBLE_BASE_URL
|
|
64
|
+
});
|
|
65
|
+
serverLogger.debug('Configured OpenAI-Compatible provider from environment');
|
|
66
|
+
}
|
|
67
|
+
// Ollama
|
|
68
|
+
const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434/api';
|
|
69
|
+
configureLLMProvider(LLMProvider.OLLAMA, { baseUrl: ollamaUrl });
|
|
70
|
+
serverLogger.debug('Configured Ollama provider', { baseUrl: ollamaUrl });
|
|
71
|
+
}
|
|
72
|
+
// Express app setup
|
|
73
|
+
const app = express();
|
|
74
|
+
app.use(cors());
|
|
75
|
+
app.use(express.json());
|
|
76
|
+
// Request logging
|
|
77
|
+
app.use((req, res, next) => {
|
|
78
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
|
79
|
+
next();
|
|
80
|
+
});
|
|
81
|
+
// Static files and API routes
|
|
82
|
+
app.use(express.static(path.join(__dirname, '../public')));
|
|
83
|
+
app.use('/api', apiRouter);
|
|
84
|
+
// Health check endpoint
|
|
85
|
+
app.get('/health', (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
res.json({
|
|
88
|
+
status: 'healthy',
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
services: { express: 'running' }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error('Error getting server health:', error);
|
|
95
|
+
res.status(500).json({
|
|
96
|
+
status: 'error',
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
error: 'Failed to get server health'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// SPA fallback - serve index.html for all non-API routes
|
|
103
|
+
app.get('*', (req, res) => {
|
|
104
|
+
res.sendFile(path.join(__dirname, '../public/index.html'));
|
|
105
|
+
});
|
|
106
|
+
// Error handling middleware
|
|
107
|
+
app.use((error, req, res, next) => {
|
|
108
|
+
console.error('Unhandled error:', error);
|
|
109
|
+
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
|
|
110
|
+
});
|
|
111
|
+
// 404 handler
|
|
112
|
+
app.use((req, res) => {
|
|
113
|
+
res.status(404).json({ error: 'Endpoint not found', code: 'NOT_FOUND' });
|
|
114
|
+
});
|
|
115
|
+
// Server startup function
|
|
116
|
+
export function startWebServer(port = PORT, host = HOST) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
configureLLMProvidersFromEnv();
|
|
119
|
+
const server = app.listen(port, host, () => {
|
|
120
|
+
const serverAddress = server.address();
|
|
121
|
+
if (serverAddress && typeof serverAddress === 'object') {
|
|
122
|
+
const actualPort = serverAddress.port;
|
|
123
|
+
const url = `http://${host}:${actualPort}`;
|
|
124
|
+
console.log(`🌐 Web server running at ${url}`);
|
|
125
|
+
// console.log(`📁 Serving static files from: ${path.join(__dirname, '../public')}`);
|
|
126
|
+
// console.log(`🚀 HTTP server running with REST API and SSE chat`);
|
|
127
|
+
resolve(server);
|
|
128
|
+
open(url);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
server.on('error', reject);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Direct execution handling - check both direct execution and npm bin execution
|
|
135
|
+
const currentFileUrl = import.meta.url;
|
|
136
|
+
const entryPointUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
137
|
+
const isDirectExecution = currentFileUrl === entryPointUrl;
|
|
138
|
+
const isServerBinCommand = process.argv[1].includes('agent-world-server') || currentFileUrl.includes('server/index.js');
|
|
139
|
+
if (isDirectExecution || isServerBinCommand) {
|
|
140
|
+
startWebServer()
|
|
141
|
+
.then(() => console.log('Server started successfully'))
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
console.error('Failed to start server:', error);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-world",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"next"
|
|
9
9
|
],
|
|
10
10
|
"bin": {
|
|
11
|
-
"agent-world": "dist/cli/index.js"
|
|
11
|
+
"agent-world": "dist/cli/index.js",
|
|
12
|
+
"agent-world-server": "dist/server/index.js"
|
|
12
13
|
},
|
|
13
14
|
"scripts": {
|
|
14
15
|
"prestart": "npm run build",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"chalk": "^4.1.2",
|
|
50
51
|
"cors": "^2.8.5",
|
|
51
52
|
"dotenv": "^16.5.0",
|
|
53
|
+
"enquirer": "^2.4.1",
|
|
52
54
|
"events": "^3.3.0",
|
|
53
55
|
"express": "^4.21.2",
|
|
54
56
|
"ollama-ai-provider": "^1.2.0",
|