morpheus-cli 0.9.4 → 0.9.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.
Files changed (76) hide show
  1. package/README.md +63 -43
  2. package/dist/channels/discord.js +3 -6
  3. package/dist/channels/telegram.js +3 -6
  4. package/dist/cli/commands/restart.js +15 -0
  5. package/dist/cli/commands/start.js +16 -0
  6. package/dist/config/manager.js +61 -0
  7. package/dist/config/paths.js +1 -0
  8. package/dist/config/schemas.js +11 -3
  9. package/dist/http/api.js +3 -0
  10. package/dist/http/routers/link.js +239 -0
  11. package/dist/http/routers/skills.js +1 -8
  12. package/dist/http/routers/smiths.js +14 -4
  13. package/dist/runtime/apoc.js +1 -1
  14. package/dist/runtime/audit/repository.js +1 -1
  15. package/dist/runtime/link-chunker.js +214 -0
  16. package/dist/runtime/link-repository.js +301 -0
  17. package/dist/runtime/link-search.js +298 -0
  18. package/dist/runtime/link-worker.js +284 -0
  19. package/dist/runtime/link.js +295 -0
  20. package/dist/runtime/memory/sati/service.js +1 -1
  21. package/dist/runtime/neo.js +1 -1
  22. package/dist/runtime/oracle.js +81 -44
  23. package/dist/runtime/scaffold.js +4 -17
  24. package/dist/runtime/skills/__tests__/loader.test.js +7 -10
  25. package/dist/runtime/skills/__tests__/registry.test.js +2 -18
  26. package/dist/runtime/skills/__tests__/tool.test.js +55 -224
  27. package/dist/runtime/skills/index.js +1 -2
  28. package/dist/runtime/skills/loader.js +0 -2
  29. package/dist/runtime/skills/registry.js +8 -20
  30. package/dist/runtime/skills/schema.js +0 -4
  31. package/dist/runtime/skills/tool.js +42 -209
  32. package/dist/runtime/smiths/delegator.js +1 -1
  33. package/dist/runtime/smiths/registry.js +1 -1
  34. package/dist/runtime/tasks/worker.js +12 -44
  35. package/dist/runtime/trinity.js +1 -1
  36. package/dist/types/config.js +14 -0
  37. package/dist/ui/assets/AuditDashboard-93LCGHG1.js +1 -0
  38. package/dist/ui/assets/{Chat-5AeRYuRj.js → Chat-CK5sNcQ1.js} +8 -8
  39. package/dist/ui/assets/{Chronos-BrKldYVw.js → Chronos-m2h--GEe.js} +1 -1
  40. package/dist/ui/assets/{ConfirmationModal-DsbS3XkJ.js → ConfirmationModal-Dd5pUJme.js} +1 -1
  41. package/dist/ui/assets/{Dashboard-DvrTXLdo.js → Dashboard-ODwl7d-a.js} +1 -1
  42. package/dist/ui/assets/{DeleteConfirmationModal-BfSjv04R.js → DeleteConfirmationModal-CCcojDmr.js} +1 -1
  43. package/dist/ui/assets/Documents-dWnSoxFO.js +7 -0
  44. package/dist/ui/assets/{Logs-B0ZYWs5x.js → Logs-Dc9Z2LBj.js} +1 -1
  45. package/dist/ui/assets/{MCPManager-BwHGTeNs.js → MCPManager-CMkb8vMn.js} +1 -1
  46. package/dist/ui/assets/{ModelPricing-CYhGRQr8.js → ModelPricing-DtHPPbEQ.js} +1 -1
  47. package/dist/ui/assets/{Notifications-BYMAtVMq.js → Notifications-BPvo-DWP.js} +1 -1
  48. package/dist/ui/assets/{Pagination-oTGieBLM.js → Pagination-BHZKk42X.js} +1 -1
  49. package/dist/ui/assets/{SatiMemories-I1vsYtP2.js → SatiMemories-BUPu1Lxr.js} +1 -1
  50. package/dist/ui/assets/SessionAudit-CFKF4DA8.js +9 -0
  51. package/dist/ui/assets/Settings-C4JrXfsR.js +47 -0
  52. package/dist/ui/assets/{Skills-lGU3I5DO.js → Skills-BUlvJgJ4.js} +1 -1
  53. package/dist/ui/assets/Smiths-CDtJdY0I.js +1 -0
  54. package/dist/ui/assets/{Tasks-Bz92GPWK.js → Tasks-DK_cOsNK.js} +1 -1
  55. package/dist/ui/assets/{TrinityDatabases-BUY-3j7Q.js → TrinityDatabases-X07by-19.js} +1 -1
  56. package/dist/ui/assets/{UsageStats-Dr5eSgJc.js → UsageStats-dYcgckLq.js} +1 -1
  57. package/dist/ui/assets/{WebhookManager-DIASAC-1.js → WebhookManager-DDw5eX2R.js} +1 -1
  58. package/dist/ui/assets/{audit-CcAEDbZh.js → audit-DZ5WLUEm.js} +1 -1
  59. package/dist/ui/assets/{chronos-2Z9E96_1.js → chronos-B_HI4mlq.js} +1 -1
  60. package/dist/ui/assets/{config-DdfK4DX6.js → config-B-YxlVrc.js} +1 -1
  61. package/dist/ui/assets/index-DVjwJ8jT.css +1 -0
  62. package/dist/ui/assets/{index-Dpd1Mkgp.js → index-DfJwcKqG.js} +5 -5
  63. package/dist/ui/assets/{mcp-BWMt8aY7.js → mcp-k-_pwbqA.js} +1 -1
  64. package/dist/ui/assets/{skills-D7JjK7JH.js → skills-xMXangks.js} +1 -1
  65. package/dist/ui/assets/{stats-DoIhtLot.js → stats-C4QZIv5O.js} +1 -1
  66. package/dist/ui/assets/{vendor-icons-DMd9RGvJ.js → vendor-icons-NHF9HNeN.js} +1 -1
  67. package/dist/ui/index.html +3 -3
  68. package/dist/ui/sw.js +1 -1
  69. package/package.json +3 -1
  70. package/dist/runtime/__tests__/keymaker.test.js +0 -148
  71. package/dist/runtime/keymaker.js +0 -157
  72. package/dist/ui/assets/AuditDashboard-C1f6Hbdw.js +0 -1
  73. package/dist/ui/assets/SessionAudit-BCecQWde.js +0 -9
  74. package/dist/ui/assets/Settings-Cu4D-7tb.js +0 -47
  75. package/dist/ui/assets/Smiths-DnEH3nID.js +0 -1
  76. package/dist/ui/assets/index-D4fzIKy1.css +0 -1
package/README.md CHANGED
@@ -22,8 +22,8 @@ It runs as a daemon and orchestrates LLMs, MCP tools, DevKit tools, memory, and
22
22
  - `Apoc`: DevTools/browser execution (filesystem, shell, git, network, packages, processes, system, browser automation).
23
23
  - `Sati`: long-term memory retrieval/evaluation.
24
24
  - `Trinity`: database specialist. Executes queries, introspects schemas, and manages registered databases (PostgreSQL, MySQL, SQLite, MongoDB).
25
+ - `Link`: documentation specialist. RAG over indexed user documents (PDF, Markdown, TXT, DOCX) with hybrid vector + keyword search.
25
26
  - `Chronos`: temporal scheduler. Runs Oracle prompts on a recurring or one-time schedule.
26
- - `Keymaker`: skill executor. Runs user-defined skills with full tool access (DevKit + MCP + internal tools).
27
27
  - `Smith`: remote DevKit executor. Runs DevKit operations on isolated machines (Docker, VMs, cloud) via WebSocket.
28
28
 
29
29
  ## Installation
@@ -510,14 +510,15 @@ Precedence order:
510
510
 
511
511
  ## Skills
512
512
 
513
- Skills are user-defined capabilities that extend Morpheus. Each skill is a folder in `~/.morpheus/skills/` containing a single `SKILL.md` file with YAML frontmatter for metadata.
513
+ Skills are user-defined instruction templates that extend Morpheus capabilities. Each skill is a folder in `~/.morpheus/skills/` containing a single `SKILL.md` file with YAML frontmatter for metadata.
514
514
 
515
- ### Execution Modes
515
+ ### How Skills Work
516
516
 
517
- Skills support two execution modes:
518
-
519
- - **`sync`** (default) - Executes immediately via `skill_execute`, returns result inline
520
- - **`async`** - Runs as background task via `skill_delegate`, notifies when complete
517
+ Skills are **instruction templates** loaded into Oracle's context on-demand:
518
+ 1. User request matches a skill's examples/description
519
+ 2. Oracle calls `load_skill` tool with the skill name
520
+ 3. Tool returns the skill's full content (SKILL.md)
521
+ 4. Oracle follows the instructions using its existing tools (delegation, MCP, etc.)
521
522
 
522
523
  ### Creating a Skill
523
524
 
@@ -533,7 +534,6 @@ description: Brief description of what this skill does
533
534
  version: 1.0.0
534
535
  author: your-name
535
536
  enabled: true
536
- execution_mode: sync
537
537
  tags:
538
538
  - automation
539
539
  - example
@@ -546,44 +546,17 @@ examples:
546
546
  You are an expert at [domain]. Follow these instructions...
547
547
 
548
548
  ## Your Task
549
- [Instructions for Keymaker to execute]
550
- ```
551
-
552
- ### Async Skill Example
553
-
554
- For long-running tasks like deployments or builds:
555
-
556
- ```markdown
557
- ---
558
- name: deploy-staging
559
- description: Deploy application to staging environment
560
- execution_mode: async
561
- tags:
562
- - deployment
563
- - devops
564
- ---
565
-
566
- # Deploy to Staging
567
-
568
- Execute a full deployment to the staging environment...
549
+ [Instructions for Oracle to execute]
569
550
  ```
570
551
 
571
552
  ### Using Skills
572
553
 
573
- Once a skill is loaded, Oracle will automatically suggest delegating matching tasks to Keymaker:
554
+ Once a skill is loaded, Oracle follows the instructions using its available tools:
574
555
 
575
- **Sync Skills (immediate result):**
576
556
  ```
577
557
  User: "Review the code in src/auth.ts"
578
- Oracle: [executes code-reviewer skill via skill_execute]
579
- Keymaker: [returns detailed review immediately]
580
- ```
581
-
582
- **Async Skills (background task):**
583
- ```
584
- User: "Deploy to staging"
585
- Oracle: [delegates via skill_delegate, task queued]
586
- Morpheus: [notifies via Telegram/Discord when complete]
558
+ Oracle: [loads code-reviewer skill via load_skill]
559
+ Oracle: [executes review using its tools and returns result]
587
560
  ```
588
561
 
589
562
  ### Managing Skills
@@ -617,15 +590,61 @@ POST /api/skills/:name/disable - Disable skill
617
590
  ### Sample Skills
618
591
 
619
592
  Example skills are available in `examples/skills/`:
620
- - `code-reviewer` - Reviews code for issues and best practices (sync)
621
- - `git-helper` - Assists with Git operations (sync)
622
- - `deploy-staging` - Deploy to staging environment (async)
593
+ - `code-reviewer` - Reviews code for issues and best practices
594
+ - `git-helper` - Assists with Git operations
623
595
 
624
596
  Copy them to your skills directory:
625
597
  ```bash
626
598
  cp -r examples/skills/* ~/.morpheus/skills/
627
599
  ```
628
600
 
601
+ ## Link — Document RAG
602
+
603
+ Link is a documentation specialist subagent that provides RAG (Retrieval-Augmented Generation) over user documents.
604
+
605
+ **Supported formats:** PDF, Markdown, TXT, DOCX
606
+
607
+ **Document storage:** `~/.morpheus/docs/`
608
+
609
+ **Embedding database:** `~/.morpheus/memory/link.db` (SQLite with sqlite-vec)
610
+
611
+ ### Using Link
612
+
613
+ Oracle automatically delegates to Link when users ask about their documents:
614
+
615
+ ```
616
+ User: "What does my contract say about termination?"
617
+ Oracle: [delegates to Link via link_delegate]
618
+ Link: [searches indexed documents and returns answer with citations]
619
+ ```
620
+
621
+ ### Managing Documents
622
+
623
+ **Web UI:** Navigate to `/documents` to upload, delete, and reindex documents.
624
+
625
+ **API Endpoints:**
626
+ ```
627
+ GET /api/link/documents - List all documents
628
+ POST /api/link/upload - Upload a document
629
+ DELETE /api/link/documents/:id - Delete a document
630
+ POST /api/link/documents/:id/reindex - Reindex a document
631
+ ```
632
+
633
+ ### Configuration
634
+
635
+ ```yaml
636
+ link:
637
+ provider: openai
638
+ model: gpt-4o-mini
639
+ temperature: 0.2
640
+ personality: documentation_specialist
641
+ execution_mode: async # 'sync' = inline, 'async' = background task
642
+ max_results: 10 # max search results per query
643
+ score_threshold: 0.5 # minimum similarity score (0-1)
644
+ chunk_size: 1000 # characters per chunk
645
+ chunk_overlap: 200 # overlap between chunks
646
+ ```
647
+
629
648
  ## MCP Configuration
630
649
 
631
650
  Configure MCP servers in `~/.morpheus/mcps.json`.
@@ -655,9 +674,10 @@ Authenticated endpoints (`x-architect-pass`):
655
674
  - Sessions: `/api/sessions*`
656
675
  - Chat: `POST /api/chat`
657
676
  - Tasks: `GET /api/tasks`, `GET /api/tasks/stats`, `GET /api/tasks/:id`, `POST /api/tasks/:id/retry`
658
- - Config: `/api/config`, `/api/config/sati`, `/api/config/neo`, `/api/config/apoc`, `/api/config/trinity`, `/api/config/chronos`, `/api/config/smiths`
677
+ - Config: `/api/config`, `/api/config/sati`, `/api/config/neo`, `/api/config/apoc`, `/api/config/trinity`, `/api/config/link`, `/api/config/chronos`, `/api/config/smiths`
659
678
  - MCP: `/api/mcp/*` (servers CRUD + reload + status)
660
679
  - Sati memories: `/api/sati/memories*`
680
+ - Link documents: `/api/link/documents`, `/api/link/upload`, `/api/link/documents/:id/reindex`
661
681
  - Trinity databases: `GET/POST/PUT/DELETE /api/trinity/databases`, `POST /api/trinity/databases/:id/test`, `POST /api/trinity/databases/:id/refresh-schema`
662
682
  - Chronos: `GET/POST /api/chronos`, `GET/PUT/DELETE /api/chronos/:id`, `PATCH /api/chronos/:id/enable`, `PATCH /api/chronos/:id/disable`, `GET /api/chronos/:id/executions`, `POST /api/chronos/preview`
663
683
  - Smiths: `GET /api/smiths`, `GET/PUT /api/smiths/config`, `GET/DELETE /api/smiths/:name`, `POST /api/smiths/:name/ping`
@@ -773,10 +773,9 @@ export class DiscordAdapter {
773
773
  }
774
774
  async cmdSkillReload(interaction) {
775
775
  try {
776
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
776
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
777
777
  const registry = SkillRegistry.getInstance();
778
778
  const result = await registry.reload();
779
- updateSkillDelegateDescription();
780
779
  const msg = result.errors.length > 0
781
780
  ? `Reloaded ${result.skills.length} skills with ${result.errors.length} error(s).`
782
781
  : `Reloaded ${result.skills.length} skill(s).`;
@@ -789,14 +788,13 @@ export class DiscordAdapter {
789
788
  async cmdSkillEnable(interaction) {
790
789
  const name = interaction.options.getString('name', true);
791
790
  try {
792
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
791
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
793
792
  const registry = SkillRegistry.getInstance();
794
793
  const success = registry.enable(name);
795
794
  if (!success) {
796
795
  await interaction.reply({ content: `Skill "${name}" not found.` });
797
796
  return;
798
797
  }
799
- updateSkillDelegateDescription();
800
798
  await interaction.reply({ content: `Skill \`${name}\` enabled.` });
801
799
  }
802
800
  catch (err) {
@@ -806,14 +804,13 @@ export class DiscordAdapter {
806
804
  async cmdSkillDisable(interaction) {
807
805
  const name = interaction.options.getString('name', true);
808
806
  try {
809
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
807
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
810
808
  const registry = SkillRegistry.getInstance();
811
809
  const success = registry.disable(name);
812
810
  if (!success) {
813
811
  await interaction.reply({ content: `Skill "${name}" not found.` });
814
812
  return;
815
813
  }
816
- updateSkillDelegateDescription();
817
814
  await interaction.reply({ content: `Skill \`${name}\` disabled.` });
818
815
  }
819
816
  catch (err) {
@@ -1087,10 +1087,9 @@ export class TelegramAdapter {
1087
1087
  }
1088
1088
  async handleSkillsReload(ctx) {
1089
1089
  try {
1090
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
1090
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
1091
1091
  const registry = SkillRegistry.getInstance();
1092
1092
  const result = await registry.reload();
1093
- updateSkillDelegateDescription();
1094
1093
  const msg = result.errors.length > 0
1095
1094
  ? `Reloaded ${result.skills.length} skills with ${result.errors.length} error(s).`
1096
1095
  : `Reloaded ${result.skills.length} skill(s).`;
@@ -1106,14 +1105,13 @@ export class TelegramAdapter {
1106
1105
  return;
1107
1106
  }
1108
1107
  try {
1109
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
1108
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
1110
1109
  const registry = SkillRegistry.getInstance();
1111
1110
  const success = registry.enable(name);
1112
1111
  if (!success) {
1113
1112
  await ctx.reply(`Skill "${name}" not found.`);
1114
1113
  return;
1115
1114
  }
1116
- updateSkillDelegateDescription();
1117
1115
  await ctx.reply(`Skill \`${name}\` enabled.`, { parse_mode: 'Markdown' });
1118
1116
  }
1119
1117
  catch (err) {
@@ -1126,14 +1124,13 @@ export class TelegramAdapter {
1126
1124
  return;
1127
1125
  }
1128
1126
  try {
1129
- const { SkillRegistry, updateSkillDelegateDescription } = await import('../runtime/skills/index.js');
1127
+ const { SkillRegistry } = await import('../runtime/skills/index.js');
1130
1128
  const registry = SkillRegistry.getInstance();
1131
1129
  const success = registry.disable(name);
1132
1130
  if (!success) {
1133
1131
  await ctx.reply(`Skill "${name}" not found.`);
1134
1132
  return;
1135
1133
  }
1136
- updateSkillDelegateDescription();
1137
1134
  await ctx.reply(`Skill \`${name}\` disabled.`, { parse_mode: 'Markdown' });
1138
1135
  }
1139
1136
  catch (err) {
@@ -15,6 +15,8 @@ import { HttpServer } from '../../http/server.js';
15
15
  import { getVersion } from '../utils/version.js';
16
16
  import { TaskWorker } from '../../runtime/tasks/worker.js';
17
17
  import { TaskNotifier } from '../../runtime/tasks/notifier.js';
18
+ import { Link } from '../../runtime/link.js';
19
+ import { LinkWorker } from '../../runtime/link-worker.js';
18
20
  export const restartCommand = new Command('restart')
19
21
  .description('Restart the Morpheus agent')
20
22
  .option('--ui', 'Enable web UI', true)
@@ -97,6 +99,15 @@ export const restartCommand = new Command('restart')
97
99
  await clearPid();
98
100
  process.exit(1);
99
101
  }
102
+ // Initialize Link (Documentation Specialist)
103
+ try {
104
+ const link = Link.getInstance(config);
105
+ await link.initialize();
106
+ display.log(chalk.green('✓ Link initialized'), { source: 'Link' });
107
+ }
108
+ catch (err) {
109
+ display.log(chalk.yellow(`Link initialization warning: ${err.message}`), { source: 'Link' });
110
+ }
100
111
  const adapters = [];
101
112
  let httpServer;
102
113
  const taskWorker = new TaskWorker();
@@ -135,6 +146,9 @@ export const restartCommand = new Command('restart')
135
146
  taskWorker.start();
136
147
  taskNotifier.start();
137
148
  }
149
+ // Start LinkWorker for document indexing
150
+ const linkWorker = LinkWorker.getInstance();
151
+ linkWorker.start();
138
152
  // Handle graceful shutdown
139
153
  const shutdown = async (signal) => {
140
154
  display.stopSpinner();
@@ -145,6 +159,7 @@ export const restartCommand = new Command('restart')
145
159
  for (const adapter of adapters) {
146
160
  await adapter.disconnect();
147
161
  }
162
+ linkWorker.stop();
148
163
  if (asyncTasksEnabled) {
149
164
  taskWorker.stop();
150
165
  taskNotifier.stop();
@@ -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 {
@@ -259,6 +271,9 @@ export const startCommand = new Command('start')
259
271
  // Start Background Services
260
272
  startSessionEmbeddingScheduler();
261
273
  chronosWorker.start();
274
+ // Start LinkWorker for document indexing
275
+ const linkWorker = LinkWorker.getInstance();
276
+ linkWorker.start();
262
277
  if (asyncTasksEnabled) {
263
278
  taskWorker.start();
264
279
  taskNotifier.start();
@@ -278,6 +293,7 @@ export const startCommand = new Command('start')
278
293
  await adapter.disconnect();
279
294
  }
280
295
  chronosWorker.stop();
296
+ linkWorker.stop();
281
297
  if (asyncTasksEnabled) {
282
298
  taskWorker.stop();
283
299
  taskNotifier.stop();
@@ -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(),
@@ -15,4 +15,5 @@ export const PATHS = {
15
15
  commands: path.join(MORPHEUS_ROOT, 'commands'),
16
16
  mcps: path.join(MORPHEUS_ROOT, 'mcps.json'),
17
17
  skills: path.join(MORPHEUS_ROOT, 'skills'),
18
+ docs: path.join(MORPHEUS_ROOT, 'docs'),
18
19
  };
@@ -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 KeymakerConfigSchema = LLMConfigSchema.extend({
39
- skills_dir: z.string().optional(),
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
- keymaker: KeymakerConfigSchema.optional(),
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 {