morpheus-cli 0.9.5 → 0.9.7
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 +63 -43
- package/dist/channels/discord.js +71 -21
- package/dist/channels/telegram.js +73 -19
- package/dist/cli/commands/restart.js +15 -0
- package/dist/cli/commands/start.js +18 -0
- package/dist/config/manager.js +61 -0
- package/dist/config/paths.js +1 -0
- package/dist/config/schemas.js +11 -3
- package/dist/http/api.js +3 -0
- package/dist/http/routers/link.js +239 -0
- package/dist/http/routers/skills.js +1 -8
- package/dist/runtime/apoc.js +1 -1
- package/dist/runtime/audit/repository.js +1 -1
- package/dist/runtime/link-chunker.js +214 -0
- package/dist/runtime/link-repository.js +301 -0
- package/dist/runtime/link-search.js +298 -0
- package/dist/runtime/link-worker.js +284 -0
- package/dist/runtime/link.js +295 -0
- package/dist/runtime/memory/sati/service.js +1 -1
- package/dist/runtime/memory/sqlite.js +52 -0
- package/dist/runtime/neo.js +1 -1
- package/dist/runtime/oracle.js +81 -44
- package/dist/runtime/scaffold.js +4 -17
- package/dist/runtime/skills/__tests__/loader.test.js +7 -10
- package/dist/runtime/skills/__tests__/registry.test.js +2 -18
- package/dist/runtime/skills/__tests__/tool.test.js +55 -224
- package/dist/runtime/skills/index.js +1 -2
- package/dist/runtime/skills/loader.js +0 -2
- package/dist/runtime/skills/registry.js +8 -20
- package/dist/runtime/skills/schema.js +0 -4
- package/dist/runtime/skills/tool.js +42 -209
- package/dist/runtime/smiths/delegator.js +1 -1
- package/dist/runtime/smiths/registry.js +1 -1
- package/dist/runtime/tasks/worker.js +12 -44
- package/dist/runtime/trinity.js +1 -1
- package/dist/types/config.js +14 -0
- package/dist/ui/assets/AuditDashboard-93LCGHG1.js +1 -0
- package/dist/ui/assets/{Chat-BNtutgja.js → Chat-CK5sNcQ1.js} +8 -8
- package/dist/ui/assets/{Chronos-3C8RPZcl.js → Chronos-m2h--GEe.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-ZQPBeJ2Z.js → ConfirmationModal-Dd5pUJme.js} +1 -1
- package/dist/ui/assets/{Dashboard-CqkHzr2F.js → Dashboard-ODwl7d-a.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-CioxFWn_.js → DeleteConfirmationModal-CCcojDmr.js} +1 -1
- package/dist/ui/assets/Documents-dWnSoxFO.js +7 -0
- package/dist/ui/assets/{Logs-DBVanS0O.js → Logs-Dc9Z2LBj.js} +1 -1
- package/dist/ui/assets/{MCPManager-vXfL3P2U.js → MCPManager-CMkb8vMn.js} +1 -1
- package/dist/ui/assets/{ModelPricing-DyfdunLT.js → ModelPricing-DtHPPbEQ.js} +1 -1
- package/dist/ui/assets/{Notifications-VL-vep6d.js → Notifications-BPvo-DWP.js} +1 -1
- package/dist/ui/assets/{Pagination-oTGieBLM.js → Pagination-BHZKk42X.js} +1 -1
- package/dist/ui/assets/{SatiMemories-jaadkW0U.js → SatiMemories-BUPu1Lxr.js} +1 -1
- package/dist/ui/assets/SessionAudit-CFKF4DA8.js +9 -0
- package/dist/ui/assets/Settings-C4JrXfsR.js +47 -0
- package/dist/ui/assets/{Skills-DE3zziXL.js → Skills-BUlvJgJ4.js} +1 -1
- package/dist/ui/assets/{Smiths-pmogN1mU.js → Smiths-CDtJdY0I.js} +1 -1
- package/dist/ui/assets/{Tasks-Bs8s34Jc.js → Tasks-DK_cOsNK.js} +1 -1
- package/dist/ui/assets/{TrinityDatabases-D7uihcdp.js → TrinityDatabases-X07by-19.js} +1 -1
- package/dist/ui/assets/{UsageStats-B9gePLZ0.js → UsageStats-dYcgckLq.js} +1 -1
- package/dist/ui/assets/{WebhookManager-B2L3rCLM.js → WebhookManager-DDw5eX2R.js} +1 -1
- package/dist/ui/assets/{audit-Cggeu9mM.js → audit-DZ5WLUEm.js} +1 -1
- package/dist/ui/assets/{chronos-D3-sWhfU.js → chronos-B_HI4mlq.js} +1 -1
- package/dist/ui/assets/{config-CBqRUPgn.js → config-B-YxlVrc.js} +1 -1
- package/dist/ui/assets/index-DVjwJ8jT.css +1 -0
- package/dist/ui/assets/{index-zKplfrXZ.js → index-DfJwcKqG.js} +5 -5
- package/dist/ui/assets/{mcp-uL1R9hyA.js → mcp-k-_pwbqA.js} +1 -1
- package/dist/ui/assets/{skills-jmw8yTJs.js → skills-xMXangks.js} +1 -1
- package/dist/ui/assets/{stats-HOms6GnM.js → stats-C4QZIv5O.js} +1 -1
- package/dist/ui/assets/{vendor-icons-DMd9RGvJ.js → vendor-icons-NHF9HNeN.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/sw.js +1 -1
- package/package.json +3 -1
- package/dist/runtime/__tests__/keymaker.test.js +0 -148
- package/dist/runtime/keymaker.js +0 -157
- package/dist/ui/assets/AuditDashboard-DliJ1CX0.js +0 -1
- package/dist/ui/assets/SessionAudit-BsXrWlwz.js +0 -9
- package/dist/ui/assets/Settings-B4eezRcg.js +0 -47
- package/dist/ui/assets/index-D4fzIKy1.css +0 -1
|
@@ -26,6 +26,8 @@ import { ChronosRepository } from '../../runtime/chronos/repository.js';
|
|
|
26
26
|
import { SkillRegistry } from '../../runtime/skills/index.js';
|
|
27
27
|
import { MCPToolCache } from '../../runtime/tools/cache.js';
|
|
28
28
|
import { SmithRegistry } from '../../runtime/smiths/registry.js';
|
|
29
|
+
import { Link } from '../../runtime/link.js';
|
|
30
|
+
import { LinkWorker } from '../../runtime/link-worker.js';
|
|
29
31
|
// Load .env file explicitly in start command
|
|
30
32
|
const envPath = path.join(process.cwd(), '.env');
|
|
31
33
|
if (fs.existsSync(envPath)) {
|
|
@@ -173,6 +175,16 @@ export const startCommand = new Command('start')
|
|
|
173
175
|
catch (err) {
|
|
174
176
|
display.log(chalk.yellow(`Smiths initialization warning: ${err.message}`), { source: 'Smiths' });
|
|
175
177
|
}
|
|
178
|
+
// Initialize Link (Documentation Specialist) before Oracle
|
|
179
|
+
try {
|
|
180
|
+
const linkConfig = ConfigManager.getInstance().getLinkConfig();
|
|
181
|
+
const link = Link.getInstance(config);
|
|
182
|
+
await link.initialize();
|
|
183
|
+
display.log(chalk.green('✓ Link initialized'), { source: 'Link' });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
display.log(chalk.yellow(`Link initialization warning: ${err.message}`), { source: 'Link' });
|
|
187
|
+
}
|
|
176
188
|
// Initialize Oracle
|
|
177
189
|
const oracle = new Oracle(config);
|
|
178
190
|
try {
|
|
@@ -228,6 +240,7 @@ export const startCommand = new Command('start')
|
|
|
228
240
|
const telegram = new TelegramAdapter(oracle);
|
|
229
241
|
try {
|
|
230
242
|
await telegram.connect(config.channels.telegram.token, config.channels.telegram.allowedUsers || []);
|
|
243
|
+
await telegram.restoreUserSessions();
|
|
231
244
|
ChannelRegistry.register(telegram);
|
|
232
245
|
adapters.push(telegram);
|
|
233
246
|
}
|
|
@@ -245,6 +258,7 @@ export const startCommand = new Command('start')
|
|
|
245
258
|
const discord = new DiscordAdapter(oracle);
|
|
246
259
|
try {
|
|
247
260
|
await discord.connect(config.channels.discord.token, config.channels.discord.allowedUsers || []);
|
|
261
|
+
await discord.restoreUserSessions();
|
|
248
262
|
ChannelRegistry.register(discord);
|
|
249
263
|
adapters.push(discord);
|
|
250
264
|
}
|
|
@@ -259,6 +273,9 @@ export const startCommand = new Command('start')
|
|
|
259
273
|
// Start Background Services
|
|
260
274
|
startSessionEmbeddingScheduler();
|
|
261
275
|
chronosWorker.start();
|
|
276
|
+
// Start LinkWorker for document indexing
|
|
277
|
+
const linkWorker = LinkWorker.getInstance();
|
|
278
|
+
linkWorker.start();
|
|
262
279
|
if (asyncTasksEnabled) {
|
|
263
280
|
taskWorker.start();
|
|
264
281
|
taskNotifier.start();
|
|
@@ -278,6 +295,7 @@ export const startCommand = new Command('start')
|
|
|
278
295
|
await adapter.disconnect();
|
|
279
296
|
}
|
|
280
297
|
chronosWorker.stop();
|
|
298
|
+
linkWorker.stop();
|
|
281
299
|
if (asyncTasksEnabled) {
|
|
282
300
|
taskWorker.stop();
|
|
283
301
|
taskNotifier.stop();
|
package/dist/config/manager.js
CHANGED
|
@@ -53,6 +53,10 @@ export class ConfigManager {
|
|
|
53
53
|
if (decrypted.trinity?.api_key) {
|
|
54
54
|
decrypted.trinity = { ...decrypted.trinity, api_key: tryDecrypt(decrypted.trinity.api_key) };
|
|
55
55
|
}
|
|
56
|
+
// Decrypt Link
|
|
57
|
+
if (decrypted.link?.api_key) {
|
|
58
|
+
decrypted.link = { ...decrypted.link, api_key: tryDecrypt(decrypted.link.api_key) };
|
|
59
|
+
}
|
|
56
60
|
// Decrypt Audio (Telephonist)
|
|
57
61
|
if (decrypted.audio?.apiKey) {
|
|
58
62
|
decrypted.audio = { ...decrypted.audio, apiKey: tryDecrypt(decrypted.audio.apiKey) };
|
|
@@ -95,6 +99,10 @@ export class ConfigManager {
|
|
|
95
99
|
if (encrypted.trinity?.api_key) {
|
|
96
100
|
encrypted.trinity = { ...encrypted.trinity, api_key: tryEncrypt(encrypted.trinity.api_key) };
|
|
97
101
|
}
|
|
102
|
+
// Encrypt Link
|
|
103
|
+
if (encrypted.link?.api_key) {
|
|
104
|
+
encrypted.link = { ...encrypted.link, api_key: tryEncrypt(encrypted.link.api_key) };
|
|
105
|
+
}
|
|
98
106
|
// Encrypt Audio (Telephonist)
|
|
99
107
|
if (encrypted.audio?.apiKey) {
|
|
100
108
|
encrypted.audio = { ...encrypted.audio, apiKey: tryEncrypt(encrypted.audio.apiKey) };
|
|
@@ -240,6 +248,38 @@ export class ConfigManager {
|
|
|
240
248
|
execution_mode: resolveString('MORPHEUS_TRINITY_EXECUTION_MODE', config.trinity?.execution_mode, 'async'),
|
|
241
249
|
};
|
|
242
250
|
}
|
|
251
|
+
// Apply precedence to Link config
|
|
252
|
+
const linkEnvVars = [
|
|
253
|
+
'MORPHEUS_LINK_PROVIDER',
|
|
254
|
+
'MORPHEUS_LINK_MODEL',
|
|
255
|
+
'MORPHEUS_LINK_TEMPERATURE',
|
|
256
|
+
'MORPHEUS_LINK_API_KEY',
|
|
257
|
+
];
|
|
258
|
+
const hasLinkEnvOverrides = linkEnvVars.some((envVar) => process.env[envVar] !== undefined);
|
|
259
|
+
let linkConfig;
|
|
260
|
+
if (config.link || hasLinkEnvOverrides) {
|
|
261
|
+
const linkProvider = resolveProvider('MORPHEUS_LINK_PROVIDER', config.link?.provider, llmConfig.provider);
|
|
262
|
+
const linkMaxTokensFallback = config.link?.max_tokens ?? llmConfig.max_tokens;
|
|
263
|
+
const linkContextWindowFallback = config.link?.context_window ?? llmConfig.context_window;
|
|
264
|
+
linkConfig = {
|
|
265
|
+
provider: linkProvider,
|
|
266
|
+
model: resolveModel(linkProvider, 'MORPHEUS_LINK_MODEL', config.link?.model || llmConfig.model),
|
|
267
|
+
temperature: resolveNumeric('MORPHEUS_LINK_TEMPERATURE', config.link?.temperature, llmConfig.temperature),
|
|
268
|
+
max_tokens: resolveOptionalNumeric('MORPHEUS_LINK_MAX_TOKENS', config.link?.max_tokens, linkMaxTokensFallback),
|
|
269
|
+
api_key: resolveApiKey(linkProvider, 'MORPHEUS_LINK_API_KEY', config.link?.api_key || llmConfig.api_key),
|
|
270
|
+
base_url: config.link?.base_url || config.llm.base_url,
|
|
271
|
+
context_window: resolveOptionalNumeric('MORPHEUS_LINK_CONTEXT_WINDOW', config.link?.context_window, linkContextWindowFallback),
|
|
272
|
+
chunk_size: resolveNumeric('MORPHEUS_LINK_CHUNK_SIZE', config.link?.chunk_size, 500),
|
|
273
|
+
score_threshold: resolveNumeric('MORPHEUS_LINK_SCORE_THRESHOLD', config.link?.score_threshold, 0.5),
|
|
274
|
+
max_results: resolveNumeric('MORPHEUS_LINK_MAX_RESULTS', config.link?.max_results, 10),
|
|
275
|
+
execution_mode: resolveString('MORPHEUS_LINK_EXECUTION_MODE', config.link?.execution_mode, 'async'),
|
|
276
|
+
scan_interval_ms: resolveNumeric('MORPHEUS_LINK_SCAN_INTERVAL_MS', config.link?.scan_interval_ms, 30000),
|
|
277
|
+
max_file_size_mb: resolveNumeric('MORPHEUS_LINK_MAX_FILE_SIZE_MB', config.link?.max_file_size_mb, 50),
|
|
278
|
+
vector_weight: resolveNumeric('MORPHEUS_LINK_VECTOR_WEIGHT', config.link?.vector_weight, 0.8),
|
|
279
|
+
bm25_weight: resolveNumeric('MORPHEUS_LINK_BM25_WEIGHT', config.link?.bm25_weight, 0.2),
|
|
280
|
+
personality: resolveString('MORPHEUS_LINK_PERSONALITY', config.link?.personality, 'documentation_specialist'),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
243
283
|
// Apply precedence to audio config
|
|
244
284
|
const audioProvider = resolveString('MORPHEUS_AUDIO_PROVIDER', config.audio.provider, DEFAULT_CONFIG.audio.provider);
|
|
245
285
|
// AudioProvider uses 'google' but resolveApiKey expects LLMProvider which uses 'gemini'
|
|
@@ -312,6 +352,7 @@ export class ConfigManager {
|
|
|
312
352
|
neo: neoConfig,
|
|
313
353
|
apoc: apocConfig,
|
|
314
354
|
trinity: trinityConfig,
|
|
355
|
+
link: linkConfig,
|
|
315
356
|
audio: audioConfig,
|
|
316
357
|
channels: channelsConfig,
|
|
317
358
|
ui: uiConfig,
|
|
@@ -436,6 +477,26 @@ export class ConfigManager {
|
|
|
436
477
|
}
|
|
437
478
|
return defaults;
|
|
438
479
|
}
|
|
480
|
+
getLinkConfig() {
|
|
481
|
+
const defaults = {
|
|
482
|
+
provider: this.config.llm.provider,
|
|
483
|
+
model: this.config.llm.model,
|
|
484
|
+
temperature: this.config.llm.temperature,
|
|
485
|
+
personality: 'documentation_specialist',
|
|
486
|
+
chunk_size: 500,
|
|
487
|
+
score_threshold: 0.5,
|
|
488
|
+
max_results: 10,
|
|
489
|
+
execution_mode: 'async',
|
|
490
|
+
scan_interval_ms: 30000,
|
|
491
|
+
max_file_size_mb: 50,
|
|
492
|
+
vector_weight: 0.8,
|
|
493
|
+
bm25_weight: 0.2,
|
|
494
|
+
};
|
|
495
|
+
if (this.config.link) {
|
|
496
|
+
return { ...defaults, ...this.config.link };
|
|
497
|
+
}
|
|
498
|
+
return defaults;
|
|
499
|
+
}
|
|
439
500
|
getDevKitConfig() {
|
|
440
501
|
const defaults = {
|
|
441
502
|
sandbox_dir: process.cwd(),
|
package/dist/config/paths.js
CHANGED
package/dist/config/schemas.js
CHANGED
|
@@ -35,8 +35,16 @@ export const NeoConfigSchema = LLMConfigSchema.extend({
|
|
|
35
35
|
export const TrinityConfigSchema = LLMConfigSchema.extend({
|
|
36
36
|
execution_mode: z.enum(['sync', 'async']).default('async'),
|
|
37
37
|
});
|
|
38
|
-
export const
|
|
39
|
-
|
|
38
|
+
export const LinkConfigSchema = LLMConfigSchema.extend({
|
|
39
|
+
personality: z.string().optional(),
|
|
40
|
+
chunk_size: z.number().int().positive().default(500),
|
|
41
|
+
score_threshold: z.number().min(0).max(1).default(0.5),
|
|
42
|
+
max_results: z.number().int().positive().default(10),
|
|
43
|
+
execution_mode: z.enum(['sync', 'async']).default('async'),
|
|
44
|
+
scan_interval_ms: z.number().int().min(5000).default(30000),
|
|
45
|
+
max_file_size_mb: z.number().int().positive().default(50),
|
|
46
|
+
vector_weight: z.number().min(0).max(1).default(0.8),
|
|
47
|
+
bm25_weight: z.number().min(0).max(1).default(0.2),
|
|
40
48
|
});
|
|
41
49
|
export const WebhookConfigSchema = z.object({
|
|
42
50
|
telegram_notify_all: z.boolean().optional(),
|
|
@@ -86,7 +94,7 @@ export const ConfigSchema = z.object({
|
|
|
86
94
|
neo: NeoConfigSchema.optional(),
|
|
87
95
|
apoc: ApocConfigSchema.optional(),
|
|
88
96
|
trinity: TrinityConfigSchema.optional(),
|
|
89
|
-
|
|
97
|
+
link: LinkConfigSchema.optional(),
|
|
90
98
|
webhooks: WebhookConfigSchema,
|
|
91
99
|
audio: AudioConfigSchema.default(DEFAULT_CONFIG.audio),
|
|
92
100
|
memory: z.object({
|
package/dist/http/api.js
CHANGED
|
@@ -21,6 +21,7 @@ import { createChronosJobRouter, createChronosConfigRouter } from './routers/chr
|
|
|
21
21
|
import { createSkillsRouter } from './routers/skills.js';
|
|
22
22
|
import { createSmithsRouter } from './routers/smiths.js';
|
|
23
23
|
import { createDangerRouter } from './routers/danger.js';
|
|
24
|
+
import { createLinkRouter } from './routers/link.js';
|
|
24
25
|
import { getActiveEnvOverrides } from '../config/precedence.js';
|
|
25
26
|
import { hotReloadConfig, getRestartRequiredChanges } from '../runtime/hot-reload.js';
|
|
26
27
|
import { AuditRepository } from '../runtime/audit/repository.js';
|
|
@@ -52,6 +53,8 @@ export function createApiRouter(oracle, chronosWorker) {
|
|
|
52
53
|
router.use('/smiths', createSmithsRouter());
|
|
53
54
|
// Mount Danger Zone router
|
|
54
55
|
router.use('/danger', createDangerRouter());
|
|
56
|
+
// Mount Link router (Documentation management)
|
|
57
|
+
router.use('/link', createLinkRouter());
|
|
55
58
|
// --- Session Management ---
|
|
56
59
|
router.get('/sessions', async (req, res) => {
|
|
57
60
|
try {
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import multer from 'multer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { LinkRepository } from '../../runtime/link-repository.js';
|
|
7
|
+
import { LinkWorker } from '../../runtime/link-worker.js';
|
|
8
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
9
|
+
const DOCS_PATH = path.join(homedir(), '.morpheus', 'docs');
|
|
10
|
+
// Configure multer for file uploads
|
|
11
|
+
const storage = multer.diskStorage({
|
|
12
|
+
destination: async (req, file, cb) => {
|
|
13
|
+
await fs.ensureDir(DOCS_PATH);
|
|
14
|
+
cb(null, DOCS_PATH);
|
|
15
|
+
},
|
|
16
|
+
filename: (req, file, cb) => {
|
|
17
|
+
// Multer decodes originalname as Latin1 per HTTP spec.
|
|
18
|
+
// Re-encode to get the raw bytes and decode as UTF-8.
|
|
19
|
+
const fixedName = Buffer.from(file.originalname, 'latin1').toString('utf-8');
|
|
20
|
+
cb(null, fixedName);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const upload = multer({
|
|
24
|
+
storage,
|
|
25
|
+
limits: {
|
|
26
|
+
fileSize: 50 * 1024 * 1024, // 50MB default, will check config
|
|
27
|
+
},
|
|
28
|
+
fileFilter: (req, file, cb) => {
|
|
29
|
+
const name = Buffer.from(file.originalname, 'latin1').toString('utf-8');
|
|
30
|
+
const ext = path.extname(name).toLowerCase();
|
|
31
|
+
const allowed = ['.pdf', '.txt', '.md', '.docx'];
|
|
32
|
+
if (allowed.includes(ext)) {
|
|
33
|
+
cb(null, true);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
cb(new Error(`Unsupported file type: ${ext}. Allowed: ${allowed.join(', ')}`));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Create the Link router for document management.
|
|
42
|
+
*/
|
|
43
|
+
export function createLinkRouter() {
|
|
44
|
+
const router = Router();
|
|
45
|
+
const repository = LinkRepository.getInstance();
|
|
46
|
+
const worker = LinkWorker.getInstance();
|
|
47
|
+
// GET /api/link/documents - List all documents
|
|
48
|
+
router.get('/documents', (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const status = req.query.status;
|
|
51
|
+
const documents = repository.listDocuments(status);
|
|
52
|
+
const stats = repository.getStats();
|
|
53
|
+
res.json({
|
|
54
|
+
documents,
|
|
55
|
+
stats,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
res.status(500).json({ error: err.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// GET /api/link/documents/:id - Get single document
|
|
63
|
+
router.get('/documents/:id', (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const document = repository.getDocument(req.params.id);
|
|
66
|
+
if (!document) {
|
|
67
|
+
return res.status(404).json({ error: 'Document not found' });
|
|
68
|
+
}
|
|
69
|
+
// Also fetch chunks
|
|
70
|
+
const chunks = repository.getChunksByDocument(req.params.id);
|
|
71
|
+
res.json({ document, chunks });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
res.status(500).json({ error: err.message });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// POST /api/link/documents/upload - Upload a new document
|
|
78
|
+
router.post('/documents/upload', async (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const config = ConfigManager.getInstance().getLinkConfig();
|
|
81
|
+
const maxSizeMB = config.max_file_size_mb;
|
|
82
|
+
// Configure multer with config max size
|
|
83
|
+
const uploadWithConfig = multer({
|
|
84
|
+
storage,
|
|
85
|
+
limits: { fileSize: maxSizeMB * 1024 * 1024 },
|
|
86
|
+
fileFilter: (req, file, cb) => {
|
|
87
|
+
const name = Buffer.from(file.originalname, 'latin1').toString('utf-8');
|
|
88
|
+
const ext = path.extname(name).toLowerCase();
|
|
89
|
+
const allowed = ['.pdf', '.txt', '.md', '.docx'];
|
|
90
|
+
if (allowed.includes(ext)) {
|
|
91
|
+
cb(null, true);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
cb(new Error(`Unsupported file type: ${ext}`));
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
// Handle upload
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
uploadWithConfig.single('file')(req, res, (err) => {
|
|
101
|
+
if (err)
|
|
102
|
+
reject(err);
|
|
103
|
+
else
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
if (!req.file) {
|
|
108
|
+
return res.status(400).json({ error: 'No file uploaded' });
|
|
109
|
+
}
|
|
110
|
+
// Trigger immediate scan
|
|
111
|
+
const result = await worker.tick();
|
|
112
|
+
res.json({
|
|
113
|
+
message: 'File uploaded successfully',
|
|
114
|
+
filename: Buffer.from(req.file.originalname, 'latin1').toString('utf-8'),
|
|
115
|
+
path: req.file.path,
|
|
116
|
+
indexed: result.indexed,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
res.status(500).json({ error: err.message });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// DELETE /api/link/documents/:id - Delete a document
|
|
124
|
+
router.delete('/documents/:id', async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const document = repository.getDocument(req.params.id);
|
|
127
|
+
if (!document) {
|
|
128
|
+
return res.status(404).json({ error: 'Document not found' });
|
|
129
|
+
}
|
|
130
|
+
// Delete from repository (CASCADE removes chunks and embeddings)
|
|
131
|
+
const deleted = repository.deleteDocument(req.params.id);
|
|
132
|
+
// Also delete file from disk
|
|
133
|
+
try {
|
|
134
|
+
await fs.unlink(document.file_path);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// File may not exist, ignore
|
|
138
|
+
}
|
|
139
|
+
res.json({ message: 'Document deleted', deleted });
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
res.status(500).json({ error: err.message });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// POST /api/link/documents/:id/reindex - Force reindex a document
|
|
146
|
+
router.post('/documents/:id/reindex', async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const document = repository.getDocument(req.params.id);
|
|
149
|
+
if (!document) {
|
|
150
|
+
return res.status(404).json({ error: 'Document not found' });
|
|
151
|
+
}
|
|
152
|
+
// Check if file still exists
|
|
153
|
+
const exists = await fs.pathExists(document.file_path);
|
|
154
|
+
if (!exists) {
|
|
155
|
+
return res.status(400).json({ error: 'Document file no longer exists' });
|
|
156
|
+
}
|
|
157
|
+
// Reset status to pending and trigger processing
|
|
158
|
+
repository.updateDocumentStatus(req.params.id, 'pending');
|
|
159
|
+
// Process the document
|
|
160
|
+
const result = await worker.processDocument(document.file_path);
|
|
161
|
+
res.json({
|
|
162
|
+
message: 'Document reindexed',
|
|
163
|
+
result,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
res.status(500).json({ error: err.message });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// GET /api/link/config - Get Link configuration
|
|
171
|
+
router.get('/config', (req, res) => {
|
|
172
|
+
try {
|
|
173
|
+
const config = ConfigManager.getInstance().getLinkConfig();
|
|
174
|
+
res.json(config);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
res.status(500).json({ error: err.message });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// POST /api/link/config - Update Link configuration (partial update)
|
|
181
|
+
router.post('/config', async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const configManager = ConfigManager.getInstance();
|
|
184
|
+
const currentConfig = configManager.get();
|
|
185
|
+
const currentLinkConfig = configManager.getLinkConfig();
|
|
186
|
+
const updates = req.body;
|
|
187
|
+
// Merge updates with current config (ensuring all required fields are present)
|
|
188
|
+
const newLinkConfig = {
|
|
189
|
+
...currentLinkConfig,
|
|
190
|
+
...updates,
|
|
191
|
+
};
|
|
192
|
+
// Save to zaion.yaml
|
|
193
|
+
await configManager.save({
|
|
194
|
+
...currentConfig,
|
|
195
|
+
link: newLinkConfig,
|
|
196
|
+
});
|
|
197
|
+
// Update worker interval if changed
|
|
198
|
+
if (updates.scan_interval_ms) {
|
|
199
|
+
worker.updateInterval(updates.scan_interval_ms);
|
|
200
|
+
}
|
|
201
|
+
res.json({
|
|
202
|
+
message: 'Configuration updated',
|
|
203
|
+
config: configManager.getLinkConfig(),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
res.status(500).json({ error: err.message });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// POST /api/link/worker/scan - Trigger manual scan
|
|
211
|
+
router.post('/worker/scan', async (req, res) => {
|
|
212
|
+
try {
|
|
213
|
+
const result = await worker.tick();
|
|
214
|
+
res.json({
|
|
215
|
+
message: 'Scan completed',
|
|
216
|
+
...result,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
res.status(500).json({ error: err.message });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// GET /api/link/worker/status - Get worker status
|
|
224
|
+
router.get('/worker/status', (req, res) => {
|
|
225
|
+
try {
|
|
226
|
+
const config = ConfigManager.getInstance().getLinkConfig();
|
|
227
|
+
const stats = repository.getStats();
|
|
228
|
+
res.json({
|
|
229
|
+
running: true, // Worker is always running when daemon is up
|
|
230
|
+
scan_interval_ms: config.scan_interval_ms,
|
|
231
|
+
...stats,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
res.status(500).json({ error: err.message });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
return router;
|
|
239
|
+
}
|
|
@@ -4,7 +4,7 @@ import extract from 'extract-zip';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
|
-
import { SkillRegistry
|
|
7
|
+
import { SkillRegistry } from '../../runtime/skills/index.js';
|
|
8
8
|
import { DisplayManager } from '../../runtime/display.js';
|
|
9
9
|
import { PATHS } from '../../config/paths.js';
|
|
10
10
|
import { SkillMetadataSchema } from '../../runtime/skills/schema.js';
|
|
@@ -119,8 +119,6 @@ export function createSkillsRouter() {
|
|
|
119
119
|
try {
|
|
120
120
|
const registry = SkillRegistry.getInstance();
|
|
121
121
|
const result = await registry.reload();
|
|
122
|
-
// Update skill_delegate tool description with new skills
|
|
123
|
-
updateSkillDelegateDescription();
|
|
124
122
|
display.log(`Skills reloaded: ${result.skills.length} loaded, ${result.errors.length} errors`, {
|
|
125
123
|
source: 'SkillsAPI',
|
|
126
124
|
});
|
|
@@ -207,7 +205,6 @@ export function createSkillsRouter() {
|
|
|
207
205
|
// Reload skills
|
|
208
206
|
const registry = SkillRegistry.getInstance();
|
|
209
207
|
await registry.reload();
|
|
210
|
-
updateSkillDelegateDescription();
|
|
211
208
|
display.log(`Skill "${metadata.name}" uploaded successfully`, { source: 'SkillsAPI' });
|
|
212
209
|
res.json({
|
|
213
210
|
success: true,
|
|
@@ -258,8 +255,6 @@ export function createSkillsRouter() {
|
|
|
258
255
|
if (!success) {
|
|
259
256
|
return res.status(404).json({ error: `Skill "${name}" not found` });
|
|
260
257
|
}
|
|
261
|
-
// Update skill_delegate tool description
|
|
262
|
-
updateSkillDelegateDescription();
|
|
263
258
|
display.log(`Skill "${name}" enabled`, { source: 'SkillsAPI' });
|
|
264
259
|
res.json({ success: true, name, enabled: true });
|
|
265
260
|
}
|
|
@@ -277,8 +272,6 @@ export function createSkillsRouter() {
|
|
|
277
272
|
if (!success) {
|
|
278
273
|
return res.status(404).json({ error: `Skill "${name}" not found` });
|
|
279
274
|
}
|
|
280
|
-
// Update skill_delegate tool description
|
|
281
|
-
updateSkillDelegateDescription();
|
|
282
275
|
display.log(`Skill "${name}" disabled`, { source: 'SkillsAPI' });
|
|
283
276
|
res.json({ success: true, name, enabled: false });
|
|
284
277
|
}
|
package/dist/runtime/apoc.js
CHANGED
|
@@ -259,7 +259,7 @@ ${context ? `CONTEXT FROM ORACLE:\n${context}` : ""}
|
|
|
259
259
|
try {
|
|
260
260
|
const inputCount = messages.length;
|
|
261
261
|
const startMs = Date.now();
|
|
262
|
-
const response = await this.agent.invoke({ messages }, { recursionLimit:
|
|
262
|
+
const response = await this.agent.invoke({ messages }, { recursionLimit: 10 });
|
|
263
263
|
const durationMs = Date.now() - startMs;
|
|
264
264
|
const apocConfig = this.config.apoc || this.config.llm;
|
|
265
265
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
@@ -188,7 +188,7 @@ export class AuditRepository {
|
|
|
188
188
|
SUM(CASE WHEN ae.event_type = 'llm_call' THEN 1 ELSE 0 END) as llmCallCount,
|
|
189
189
|
SUM(CASE WHEN ae.event_type = 'tool_call' THEN 1 ELSE 0 END) as toolCallCount,
|
|
190
190
|
SUM(CASE WHEN ae.event_type = 'mcp_tool' THEN 1 ELSE 0 END) as mcpToolCount,
|
|
191
|
-
SUM(CASE WHEN ae.event_type = '
|
|
191
|
+
SUM(CASE WHEN ae.event_type = 'skill_loaded' THEN 1 ELSE 0 END) as skillCount,
|
|
192
192
|
SUM(CASE WHEN ae.event_type = 'memory_recovery' THEN 1 ELSE 0 END) as memoryRecoveryCount,
|
|
193
193
|
SUM(CASE WHEN ae.event_type = 'memory_persist' THEN 1 ELSE 0 END) as memoryPersistCount,
|
|
194
194
|
SUM(CASE WHEN ae.event_type = 'chronos_job' THEN 1 ELSE 0 END) as chronosJobCount,
|