aiden-runtime 3.16.2 → 3.18.0
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 +185 -7
- package/config/devos.config.json +29 -19
- package/config/hardware.json +2 -2
- package/dist/api/dashboard.js +480 -0
- package/dist/api/server.js +150 -142
- package/dist/core/agentLoop.js +94 -13
- package/dist/core/channels/email.js +1 -1
- package/dist/core/modelRegistry.js +261 -0
- package/dist/core/permissionSystem.js +239 -0
- package/dist/core/pluginLoader.js +161 -0
- package/dist/core/skillLoader.js +6 -24
- package/dist/core/toolRegistry.js +316 -31
- package/dist/core/version.js +1 -1
- package/dist/providers/router.js +2 -1
- package/dist-bundle/cli.js +50946 -29225
- package/dist-bundle/index.js +6462 -5274
- package/package.json +3 -2
package/dist/api/server.js
CHANGED
|
@@ -123,7 +123,8 @@ const skillLibrary_1 = require("../core/skillLibrary");
|
|
|
123
123
|
const costTracker_1 = require("../core/costTracker");
|
|
124
124
|
const sessionMemory_1 = require("../core/sessionMemory");
|
|
125
125
|
const memoryExtractor_1 = require("../core/memoryExtractor");
|
|
126
|
-
const
|
|
126
|
+
const pluginLoader_1 = require("../core/pluginLoader");
|
|
127
|
+
const permissionSystem_1 = require("../core/permissionSystem");
|
|
127
128
|
const aidenIdentity_1 = require("../core/aidenIdentity");
|
|
128
129
|
const eventBus_1 = require("../core/eventBus");
|
|
129
130
|
const workflowTracker_1 = require("../core/workflowTracker");
|
|
@@ -150,6 +151,7 @@ const signal_1 = require("../core/channels/signal");
|
|
|
150
151
|
const twilio_1 = require("../core/channels/twilio");
|
|
151
152
|
const imessage_1 = require("../core/channels/imessage");
|
|
152
153
|
const email_1 = require("../core/channels/email");
|
|
154
|
+
const dashboard_1 = require("./dashboard");
|
|
153
155
|
// —— Sprint 25: module-level WebSocket clients registry (shared between createApiServer routes and startApiServer WS setup)
|
|
154
156
|
let wsBroadcastClients = new Set();
|
|
155
157
|
let activeTelegramBot = null;
|
|
@@ -157,97 +159,10 @@ const lastExchangeBySession = new Map();
|
|
|
157
159
|
// ── Bookmarklet — clip selected text from any page ────────────
|
|
158
160
|
const BOOKMARKLET = `javascript:void(fetch('http://localhost:4200/api/clip',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:window.getSelection().toString()||document.title,source:window.location.href,title:document.title})}).then(()=>alert('Clipped!')))`;
|
|
159
161
|
const INSTANT_ACTIONS = [
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'chrome' });
|
|
166
|
-
}
|
|
167
|
-
catch { }
|
|
168
|
-
return 'Opening Chrome...';
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
// 2. Open Firefox
|
|
172
|
-
{
|
|
173
|
-
patterns: [/^open\s+firefox\s*$/i],
|
|
174
|
-
action: async () => {
|
|
175
|
-
try {
|
|
176
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'firefox' });
|
|
177
|
-
}
|
|
178
|
-
catch { }
|
|
179
|
-
return 'Opening Firefox...';
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
// 3. Open Edge
|
|
183
|
-
{
|
|
184
|
-
patterns: [/^open\s+(?:microsoft\s+)?edge\s*$/i],
|
|
185
|
-
action: async () => {
|
|
186
|
-
try {
|
|
187
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'msedge' });
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
190
|
-
return 'Opening Microsoft Edge...';
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
// 4. Open Notepad
|
|
194
|
-
{
|
|
195
|
-
patterns: [/^open\s+notepad\s*$/i],
|
|
196
|
-
action: async () => {
|
|
197
|
-
try {
|
|
198
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'notepad' });
|
|
199
|
-
}
|
|
200
|
-
catch { }
|
|
201
|
-
return 'Opening Notepad...';
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
// 5. Open Calculator
|
|
205
|
-
{
|
|
206
|
-
patterns: [/^open\s+calc(?:ulator)?\s*$/i],
|
|
207
|
-
action: async () => {
|
|
208
|
-
try {
|
|
209
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'calc' });
|
|
210
|
-
}
|
|
211
|
-
catch { }
|
|
212
|
-
return 'Opening Calculator...';
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
// 6. Open VS Code
|
|
216
|
-
{
|
|
217
|
-
patterns: [/^open\s+(?:vs[\s-]?code|vscode)\s*$/i],
|
|
218
|
-
action: async () => {
|
|
219
|
-
try {
|
|
220
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'code' });
|
|
221
|
-
}
|
|
222
|
-
catch { }
|
|
223
|
-
return 'Opening VS Code...';
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
// 7. Open Terminal / CMD
|
|
227
|
-
{
|
|
228
|
-
patterns: [/^open\s+(?:terminal|cmd|command\s+prompt)\s*$/i],
|
|
229
|
-
action: async () => {
|
|
230
|
-
try {
|
|
231
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'cmd' });
|
|
232
|
-
}
|
|
233
|
-
catch { }
|
|
234
|
-
return 'Opening terminal...';
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
// 8. Open File Explorer
|
|
238
|
-
{
|
|
239
|
-
patterns: [
|
|
240
|
-
/^open\s+(?:file\s+)?explorer\s*$/i,
|
|
241
|
-
/^open\s+(?:my\s+)?files?\s*$/i,
|
|
242
|
-
],
|
|
243
|
-
action: async () => {
|
|
244
|
-
try {
|
|
245
|
-
await (0, toolRegistry_1.executeTool)('app_launch', { app: 'explorer' });
|
|
246
|
-
}
|
|
247
|
-
catch { }
|
|
248
|
-
return 'Opening File Explorer...';
|
|
249
|
-
},
|
|
250
|
-
},
|
|
162
|
+
// NOTE: "open X" / "close X" / "launch X" entries removed — they faked success via
|
|
163
|
+
// try/catch swallowing, returned hardcoded strings regardless of tool outcome, and
|
|
164
|
+
// used the wrong param key ({app:} vs {app_name:}). The planner handles these
|
|
165
|
+
// correctly via app_launch / app_close with real success verification.
|
|
251
166
|
// 9. Take Screenshot
|
|
252
167
|
{
|
|
253
168
|
patterns: [
|
|
@@ -478,6 +393,13 @@ function initWorkspaceDefaults() {
|
|
|
478
393
|
console.log(`[init] Created ${rel}`);
|
|
479
394
|
}
|
|
480
395
|
}
|
|
396
|
+
// Copy permissions.yaml from template if not present
|
|
397
|
+
const permTarget = path.join(WORKSPACE_ROOT, 'workspace', 'permissions.yaml');
|
|
398
|
+
const permTemplate = path.join(WORKSPACE_ROOT, 'workspace-templates', 'permissions.yaml');
|
|
399
|
+
if (!fs.existsSync(permTarget) && fs.existsSync(permTemplate)) {
|
|
400
|
+
fs.copyFileSync(permTemplate, permTarget);
|
|
401
|
+
console.log('[init] Created workspace/permissions.yaml from template');
|
|
402
|
+
}
|
|
481
403
|
}
|
|
482
404
|
initWorkspaceDefaults();
|
|
483
405
|
// ── Knowledge upload — multer + progress tracking ─────────────
|
|
@@ -530,11 +452,20 @@ function createApiServer() {
|
|
|
530
452
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
531
453
|
next();
|
|
532
454
|
});
|
|
533
|
-
// CORS
|
|
455
|
+
// CORS — localhost only by default.
|
|
456
|
+
// Set AIDEN_CORS_ORIGIN=* (or a specific origin) to allow remote access.
|
|
457
|
+
const _corsAllowedOrigin = process.env.AIDEN_CORS_ORIGIN || null;
|
|
534
458
|
app.use((req, res, next) => {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
459
|
+
const origin = req.headers.origin || '';
|
|
460
|
+
const isLocal = !origin ||
|
|
461
|
+
origin.startsWith('http://localhost') ||
|
|
462
|
+
origin.startsWith('http://127.0.0.1');
|
|
463
|
+
const allowed = _corsAllowedOrigin || (isLocal ? origin || '*' : null);
|
|
464
|
+
if (allowed) {
|
|
465
|
+
res.setHeader('Access-Control-Allow-Origin', allowed);
|
|
466
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
467
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
468
|
+
}
|
|
538
469
|
if (req.method === 'OPTIONS') {
|
|
539
470
|
res.sendStatus(200);
|
|
540
471
|
return;
|
|
@@ -542,6 +473,15 @@ function createApiServer() {
|
|
|
542
473
|
next();
|
|
543
474
|
});
|
|
544
475
|
// ── Core routes ──────────────────────────────────────────────
|
|
476
|
+
// GET /ui — local web dashboard
|
|
477
|
+
app.get('/ui', (_req, res) => {
|
|
478
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
479
|
+
res.send((0, dashboard_1.getDashboardHTML)());
|
|
480
|
+
});
|
|
481
|
+
// GET /api/ping — lightweight status probe for dashboard
|
|
482
|
+
app.get('/api/ping', (_req, res) => {
|
|
483
|
+
res.json({ ok: true, version: version_1.VERSION, ts: Date.now() });
|
|
484
|
+
});
|
|
545
485
|
// GET /api/health — liveness probe (no auth required)
|
|
546
486
|
app.get('/api/health', (_req, res) => {
|
|
547
487
|
res.json({ status: 'ok', version: version_1.VERSION, timestamp: new Date().toISOString() });
|
|
@@ -733,7 +673,7 @@ function createApiServer() {
|
|
|
733
673
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
734
674
|
res.setHeader('Cache-Control', 'no-cache');
|
|
735
675
|
res.setHeader('Connection', 'keep-alive');
|
|
736
|
-
|
|
676
|
+
// CORS already set by global middleware
|
|
737
677
|
res.flushHeaders();
|
|
738
678
|
res.write(`data: ${JSON.stringify({ thinking: { stage: 'understanding', message: 'Understanding...' } })}\n\n`);
|
|
739
679
|
}
|
|
@@ -748,7 +688,7 @@ function createApiServer() {
|
|
|
748
688
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
749
689
|
res.setHeader('Cache-Control', 'no-cache');
|
|
750
690
|
res.setHeader('Connection', 'keep-alive');
|
|
751
|
-
|
|
691
|
+
// CORS already set by global middleware
|
|
752
692
|
res.flushHeaders();
|
|
753
693
|
}
|
|
754
694
|
res.write(`data: ${JSON.stringify({ token: text, done: false, provider: 'fast-path' })}\n\n`);
|
|
@@ -1023,35 +963,43 @@ function createApiServer() {
|
|
|
1023
963
|
{ regex: /(?:search|find|look\s+up)\s+(?:for\s+)?(.+?)\s+on\s+github/i, url: q => `https://github.com/search?q=${encodeURIComponent(q)}`, label: 'GitHub' },
|
|
1024
964
|
{ regex: /open\s+github\s+(?:and\s+)?(?:search|find)\s+(?:for\s+)?(.+)/i, url: q => `https://github.com/search?q=${encodeURIComponent(q)}`, label: 'GitHub' },
|
|
1025
965
|
];
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
966
|
+
// Play/listen/watch intents must go through the planner so the open_browser
|
|
967
|
+
// auto-chain (toolRegistry.ts) fires and actually starts playback.
|
|
968
|
+
const hasPlayIntent = /\b(play|listen|watch)\b/i.test(message);
|
|
969
|
+
if (hasPlayIntent) {
|
|
970
|
+
console.log('[FastPath] Skipping search fast-paths for play/listen/watch intent — routing to planner');
|
|
971
|
+
}
|
|
972
|
+
if (!hasPlayIntent) {
|
|
973
|
+
for (const fp of searchFastPaths) {
|
|
974
|
+
const m = message.match(fp.regex);
|
|
975
|
+
if (m) {
|
|
976
|
+
const query = (m[m.length - 1] || '').trim().replace(/[.!?]+$/, '');
|
|
977
|
+
if (query.length > 1) {
|
|
978
|
+
const url = fp.url(query);
|
|
979
|
+
console.log(`[FastPath] ${fp.label} search: “${query}” → ${url}`);
|
|
1038
980
|
try {
|
|
1039
|
-
await (0, toolRegistry_1.executeTool)('
|
|
981
|
+
await (0, toolRegistry_1.executeTool)('open_browser', { url });
|
|
1040
982
|
}
|
|
1041
|
-
catch {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
replyMsg
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
983
|
+
catch (e) {
|
|
984
|
+
console.warn('[FastPath] open_browser failed, trying shell:', e.message);
|
|
985
|
+
try {
|
|
986
|
+
await (0, toolRegistry_1.executeTool)('shell_exec', { command: `start “” “${url}”` });
|
|
987
|
+
}
|
|
988
|
+
catch { }
|
|
989
|
+
}
|
|
990
|
+
let replyMsg;
|
|
991
|
+
if (fp.label === 'YouTube') {
|
|
992
|
+
replyMsg = `Opening YouTube search for “${query}” — click the first result to play.\n→ ${url}`;
|
|
993
|
+
}
|
|
994
|
+
else if (fp.label === 'DuckDuckGo') {
|
|
995
|
+
replyMsg = `Searching DuckDuckGo for “${query}” — opening results in your browser.\n→ ${url}`;
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
replyMsg = `Opening ${fp.label} in your browser.\n→ ${url}`;
|
|
999
|
+
}
|
|
1000
|
+
fastReply(replyMsg);
|
|
1001
|
+
return;
|
|
1052
1002
|
}
|
|
1053
|
-
fastReply(replyMsg);
|
|
1054
|
-
return;
|
|
1055
1003
|
}
|
|
1056
1004
|
}
|
|
1057
1005
|
}
|
|
@@ -1097,8 +1045,9 @@ function createApiServer() {
|
|
|
1097
1045
|
return;
|
|
1098
1046
|
}
|
|
1099
1047
|
// 2. "play X on youtube" / "play X on spotify"
|
|
1048
|
+
// hasPlayIntent guard: these go through the planner so open_browser auto-chain fires.
|
|
1100
1049
|
const onPlatformMatch = /^play\s+(.+?)\s+on\s+(youtube|spotify)\s*$/i.exec(message);
|
|
1101
|
-
if (onPlatformMatch) {
|
|
1050
|
+
if (onPlatformMatch && !hasPlayIntent) {
|
|
1102
1051
|
const query = onPlatformMatch[1].trim();
|
|
1103
1052
|
const platform = onPlatformMatch[2].toLowerCase();
|
|
1104
1053
|
const url = buildMusicUrl(query, platform);
|
|
@@ -1380,6 +1329,10 @@ function createApiServer() {
|
|
|
1380
1329
|
// ── Callback system — additive layer alongside existing SSE sends ──
|
|
1381
1330
|
const sid = sessionId || 'default';
|
|
1382
1331
|
callbackSystem_1.callbacks.emit('session_start', sid, { message }).catch(() => { });
|
|
1332
|
+
// Fire flat-plugin session hooks
|
|
1333
|
+
for (const fn of pluginLoader_1.pluginHooks.onSessionStart) {
|
|
1334
|
+
fn(sid, { message }).catch(() => { });
|
|
1335
|
+
}
|
|
1383
1336
|
// Forward callback events from other sessions to this SSE connection.
|
|
1384
1337
|
// The sessionId guard prevents re-sending this session's own emitted events.
|
|
1385
1338
|
const unsubscribeSSE = callbackSystem_1.callbacks.onAny((payload) => {
|
|
@@ -1393,6 +1346,9 @@ function createApiServer() {
|
|
|
1393
1346
|
(0, toolRegistry_1.setProgressEmitter)(null);
|
|
1394
1347
|
unsubscribeSSE();
|
|
1395
1348
|
callbackSystem_1.callbacks.emit('session_end', sid, {}).catch(() => { });
|
|
1349
|
+
for (const fn of pluginLoader_1.pluginHooks.onSessionEnd) {
|
|
1350
|
+
fn(sid, {}).catch(() => { });
|
|
1351
|
+
}
|
|
1396
1352
|
(0, memoryDistiller_1.distillSession)(sid).catch(() => { });
|
|
1397
1353
|
});
|
|
1398
1354
|
// Sprint 6: tiered model selection
|
|
@@ -2661,15 +2617,44 @@ function createApiServer() {
|
|
|
2661
2617
|
res.status(500).json({ error: err.message });
|
|
2662
2618
|
}
|
|
2663
2619
|
});
|
|
2664
|
-
// GET /api/plugins — list loaded
|
|
2620
|
+
// GET /api/plugins — list all loaded plugins
|
|
2665
2621
|
app.get('/api/plugins', (_req, res) => {
|
|
2666
2622
|
try {
|
|
2667
|
-
res.json({ plugins:
|
|
2623
|
+
res.json({ plugins: (0, pluginLoader_1.listFlatPlugins)() });
|
|
2668
2624
|
}
|
|
2669
2625
|
catch (e) {
|
|
2670
2626
|
res.status(500).json({ error: e.message });
|
|
2671
2627
|
}
|
|
2672
2628
|
});
|
|
2629
|
+
// GET /api/plugins/list — alias for /api/plugins (kept for backward compat)
|
|
2630
|
+
app.get('/api/plugins/list', (_req, res) => {
|
|
2631
|
+
try {
|
|
2632
|
+
res.json({ plugins: (0, pluginLoader_1.listFlatPlugins)() });
|
|
2633
|
+
}
|
|
2634
|
+
catch (e) {
|
|
2635
|
+
res.status(500).json({ error: e.message });
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
// POST /api/plugins/reload — hot-reload all flat .js plugins
|
|
2639
|
+
app.post('/api/plugins/reload', requireLocalhost, async (_req, res) => {
|
|
2640
|
+
try {
|
|
2641
|
+
const dir = path.join(process.cwd(), 'workspace', 'plugins');
|
|
2642
|
+
await (0, pluginLoader_1.reloadPlugins)(dir);
|
|
2643
|
+
res.json({ ok: true, plugins: (0, pluginLoader_1.listFlatPlugins)() });
|
|
2644
|
+
}
|
|
2645
|
+
catch (e) {
|
|
2646
|
+
res.status(500).json({ error: e.message });
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
// GET /api/permissions/config — return the current parsed permissions config
|
|
2650
|
+
app.get('/api/permissions/config', (_req, res) => {
|
|
2651
|
+
res.json(permissionSystem_1.permissionSystem.getConfig());
|
|
2652
|
+
});
|
|
2653
|
+
// POST /api/permissions/reload — hot-reload workspace/permissions.yaml
|
|
2654
|
+
app.post('/api/permissions/reload', requireLocalhost, (_req, res) => {
|
|
2655
|
+
permissionSystem_1.permissionSystem.reload();
|
|
2656
|
+
res.json({ ok: true, mode: permissionSystem_1.permissionSystem.getMode() });
|
|
2657
|
+
});
|
|
2673
2658
|
// GET /api/telegram/config — load Telegram bot config
|
|
2674
2659
|
app.get('/api/telegram/config', (_req, res) => {
|
|
2675
2660
|
try {
|
|
@@ -3067,7 +3052,7 @@ function createApiServer() {
|
|
|
3067
3052
|
}
|
|
3068
3053
|
});
|
|
3069
3054
|
// POST /api/mcp/servers -- register a new MCP server and discover its tools
|
|
3070
|
-
app.post('/api/mcp/servers', async (req, res) => {
|
|
3055
|
+
app.post('/api/mcp/servers', requireLocalhost, async (req, res) => {
|
|
3071
3056
|
const { name, url, description } = req.body;
|
|
3072
3057
|
if (!name || !url) {
|
|
3073
3058
|
res.status(400).json({ error: 'name and url are required' });
|
|
@@ -3078,7 +3063,7 @@ function createApiServer() {
|
|
|
3078
3063
|
res.json({ server, tools });
|
|
3079
3064
|
});
|
|
3080
3065
|
// DELETE /api/mcp/servers/:name -- remove an MCP server
|
|
3081
|
-
app.delete('/api/mcp/servers/:name', (req, res) => {
|
|
3066
|
+
app.delete('/api/mcp/servers/:name', requireLocalhost, (req, res) => {
|
|
3082
3067
|
mcpClient_1.mcpClient.removeServer(String(req.params.name));
|
|
3083
3068
|
res.json({ success: true });
|
|
3084
3069
|
});
|
|
@@ -3503,7 +3488,7 @@ function createApiServer() {
|
|
|
3503
3488
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
3504
3489
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3505
3490
|
res.setHeader('Connection', 'keep-alive');
|
|
3506
|
-
|
|
3491
|
+
// CORS already set by global middleware
|
|
3507
3492
|
res.flushHeaders();
|
|
3508
3493
|
const ping = setInterval(() => {
|
|
3509
3494
|
try {
|
|
@@ -3738,7 +3723,7 @@ function createApiServer() {
|
|
|
3738
3723
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
3739
3724
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3740
3725
|
res.setHeader('Connection', 'keep-alive');
|
|
3741
|
-
|
|
3726
|
+
// CORS already set by global middleware
|
|
3742
3727
|
res.flushHeaders();
|
|
3743
3728
|
// Send ping every 25s to keep connection alive
|
|
3744
3729
|
const ping = setInterval(() => {
|
|
@@ -4478,7 +4463,7 @@ function createApiServer() {
|
|
|
4478
4463
|
}
|
|
4479
4464
|
});
|
|
4480
4465
|
// POST /api/skills/migrate — backfill skill.json for skills that are missing it
|
|
4481
|
-
app.post('/api/skills/migrate', async (_req, res) => {
|
|
4466
|
+
app.post('/api/skills/migrate', requireLocalhost, async (_req, res) => {
|
|
4482
4467
|
try {
|
|
4483
4468
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
4484
4469
|
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
@@ -4880,12 +4865,12 @@ function createApiServer() {
|
|
|
4880
4865
|
});
|
|
4881
4866
|
});
|
|
4882
4867
|
// DELETE /api/memory — clear all conversation memory
|
|
4883
|
-
app.delete('/api/memory', (_req, res) => {
|
|
4868
|
+
app.delete('/api/memory', requireLocalhost, (_req, res) => {
|
|
4884
4869
|
conversationMemory_1.conversationMemory.clear();
|
|
4885
4870
|
res.json({ success: true, message: 'Conversation memory cleared' });
|
|
4886
4871
|
});
|
|
4887
4872
|
// POST /api/memory/clear — alias for DELETE (for frontend compatibility)
|
|
4888
|
-
app.post('/api/memory/clear', (_req, res) => {
|
|
4873
|
+
app.post('/api/memory/clear', requireLocalhost, (_req, res) => {
|
|
4889
4874
|
try {
|
|
4890
4875
|
conversationMemory_1.conversationMemory.clear();
|
|
4891
4876
|
res.json({ success: true, message: 'All memory cleared' });
|
|
@@ -4895,7 +4880,7 @@ function createApiServer() {
|
|
|
4895
4880
|
}
|
|
4896
4881
|
});
|
|
4897
4882
|
// POST /api/conversations/clear — clear all saved conversation sessions from disk
|
|
4898
|
-
app.post('/api/conversations/clear', (_req, res) => {
|
|
4883
|
+
app.post('/api/conversations/clear', requireLocalhost, (_req, res) => {
|
|
4899
4884
|
try {
|
|
4900
4885
|
const sessionsDir = path.join(WORKSPACE_ROOT, 'workspace', 'sessions');
|
|
4901
4886
|
if (fs.existsSync(sessionsDir)) {
|
|
@@ -4913,7 +4898,7 @@ function createApiServer() {
|
|
|
4913
4898
|
}
|
|
4914
4899
|
});
|
|
4915
4900
|
// POST /api/knowledge/clear — clear knowledge base files
|
|
4916
|
-
app.post('/api/knowledge/clear', (_req, res) => {
|
|
4901
|
+
app.post('/api/knowledge/clear', requireLocalhost, (_req, res) => {
|
|
4917
4902
|
try {
|
|
4918
4903
|
const kbDir = path.join(WORKSPACE_ROOT, 'workspace', 'knowledge');
|
|
4919
4904
|
if (fs.existsSync(kbDir)) {
|
|
@@ -5116,6 +5101,17 @@ function createApiServer() {
|
|
|
5116
5101
|
httpReq.end();
|
|
5117
5102
|
});
|
|
5118
5103
|
}
|
|
5104
|
+
// ── Localhost-only guard for destructive endpoints ───────────
|
|
5105
|
+
// Applied as middleware to endpoints that must not be reachable
|
|
5106
|
+
// from remote hosts even when AIDEN_HOST=0.0.0.0.
|
|
5107
|
+
function requireLocalhost(req, res, next) {
|
|
5108
|
+
const ip = req.ip || req.socket?.remoteAddress || '';
|
|
5109
|
+
const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
5110
|
+
if (!isLocal) {
|
|
5111
|
+
return res.status(403).json({ error: 'This endpoint is only accessible from localhost' });
|
|
5112
|
+
}
|
|
5113
|
+
next();
|
|
5114
|
+
}
|
|
5119
5115
|
// ── API key guard (optional) ─────────────────────────────────
|
|
5120
5116
|
function _checkApiKey(req, res) {
|
|
5121
5117
|
const required = process.env.AIDEN_API_KEY;
|
|
@@ -5674,13 +5670,24 @@ function startupCheck() {
|
|
|
5674
5670
|
}
|
|
5675
5671
|
// ── Server launcher ───────────────────────────────────────────
|
|
5676
5672
|
function startApiServer(portArg) {
|
|
5673
|
+
// ── Redirect all diagnostic output to stderr ─────────────────────────────
|
|
5674
|
+
// The CLI writes the streaming response to process.stdout character-by-character.
|
|
5675
|
+
// If console.log also writes to stdout (the default), server logs physically
|
|
5676
|
+
// interleave with rendered tokens in the same terminal, producing output like:
|
|
5677
|
+
// "I'm back. What's up, sh[Router] planner: groq-1...iva?"
|
|
5678
|
+
// Sending ALL diagnostic output to stderr prevents this regardless of how the
|
|
5679
|
+
// user runs the server (same terminal, background process, pipe, etc.).
|
|
5680
|
+
// console.error already targets stderr — leave it alone.
|
|
5681
|
+
const _toStderr = (...args) => process.stderr.write(args.map(String).join(' ') + '\n');
|
|
5682
|
+
console.log = _toStderr;
|
|
5683
|
+
console.info = _toStderr;
|
|
5684
|
+
console.warn = _toStderr;
|
|
5677
5685
|
// Read port from config/api.json with sensible fallback.
|
|
5678
|
-
// Host defaults to
|
|
5679
|
-
//
|
|
5680
|
-
// Electron mode keeps 127.0.0.1 for security (loopback only).
|
|
5686
|
+
// Host defaults to 127.0.0.1 (loopback only) for security.
|
|
5687
|
+
// Set AIDEN_HOST=0.0.0.0 to expose on all interfaces (e.g. headless/WSL2).
|
|
5681
5688
|
let port = portArg ?? 4200;
|
|
5682
5689
|
const isHeadless = process.env.AIDEN_HEADLESS === 'true';
|
|
5683
|
-
let host = isHeadless ? '0.0.0.0' : '127.0.0.1';
|
|
5690
|
+
let host = process.env.AIDEN_HOST || (isHeadless ? '0.0.0.0' : '127.0.0.1');
|
|
5684
5691
|
try {
|
|
5685
5692
|
const cfgPath = path.join(WORKSPACE_ROOT, 'config', 'api.json');
|
|
5686
5693
|
if (fs.existsSync(cfgPath)) {
|
|
@@ -5849,8 +5856,9 @@ function startApiServer(portArg) {
|
|
|
5849
5856
|
catch (e) {
|
|
5850
5857
|
console.error('[Startup] setupHttpKeepalive failed:', e.message);
|
|
5851
5858
|
}
|
|
5852
|
-
// Load
|
|
5853
|
-
|
|
5859
|
+
// Load plugins from workspace/plugins/*.js (unified flat format)
|
|
5860
|
+
const flatPluginDir = path.join(process.cwd(), 'workspace', 'plugins');
|
|
5861
|
+
(0, pluginLoader_1.loadPlugins)(flatPluginDir).catch(e => console.error('[PluginLoader] Load failed:', e.message));
|
|
5854
5862
|
// Start background license refresh (12-hour interval, silent)
|
|
5855
5863
|
(0, licenseManager_1.startLicenseRefresh)();
|
|
5856
5864
|
// Log provider chain before listening so it's visible in startup log
|