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.
- package/README.md +63 -43
- package/dist/channels/discord.js +3 -6
- package/dist/channels/telegram.js +3 -6
- package/dist/cli/commands/restart.js +15 -0
- package/dist/cli/commands/start.js +16 -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/http/routers/smiths.js +14 -4
- 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/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-5AeRYuRj.js → Chat-CK5sNcQ1.js} +8 -8
- package/dist/ui/assets/{Chronos-BrKldYVw.js → Chronos-m2h--GEe.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-DsbS3XkJ.js → ConfirmationModal-Dd5pUJme.js} +1 -1
- package/dist/ui/assets/{Dashboard-DvrTXLdo.js → Dashboard-ODwl7d-a.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-BfSjv04R.js → DeleteConfirmationModal-CCcojDmr.js} +1 -1
- package/dist/ui/assets/Documents-dWnSoxFO.js +7 -0
- package/dist/ui/assets/{Logs-B0ZYWs5x.js → Logs-Dc9Z2LBj.js} +1 -1
- package/dist/ui/assets/{MCPManager-BwHGTeNs.js → MCPManager-CMkb8vMn.js} +1 -1
- package/dist/ui/assets/{ModelPricing-CYhGRQr8.js → ModelPricing-DtHPPbEQ.js} +1 -1
- package/dist/ui/assets/{Notifications-BYMAtVMq.js → Notifications-BPvo-DWP.js} +1 -1
- package/dist/ui/assets/{Pagination-oTGieBLM.js → Pagination-BHZKk42X.js} +1 -1
- package/dist/ui/assets/{SatiMemories-I1vsYtP2.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-lGU3I5DO.js → Skills-BUlvJgJ4.js} +1 -1
- package/dist/ui/assets/Smiths-CDtJdY0I.js +1 -0
- package/dist/ui/assets/{Tasks-Bz92GPWK.js → Tasks-DK_cOsNK.js} +1 -1
- package/dist/ui/assets/{TrinityDatabases-BUY-3j7Q.js → TrinityDatabases-X07by-19.js} +1 -1
- package/dist/ui/assets/{UsageStats-Dr5eSgJc.js → UsageStats-dYcgckLq.js} +1 -1
- package/dist/ui/assets/{WebhookManager-DIASAC-1.js → WebhookManager-DDw5eX2R.js} +1 -1
- package/dist/ui/assets/{audit-CcAEDbZh.js → audit-DZ5WLUEm.js} +1 -1
- package/dist/ui/assets/{chronos-2Z9E96_1.js → chronos-B_HI4mlq.js} +1 -1
- package/dist/ui/assets/{config-DdfK4DX6.js → config-B-YxlVrc.js} +1 -1
- package/dist/ui/assets/index-DVjwJ8jT.css +1 -0
- package/dist/ui/assets/{index-Dpd1Mkgp.js → index-DfJwcKqG.js} +5 -5
- package/dist/ui/assets/{mcp-BWMt8aY7.js → mcp-k-_pwbqA.js} +1 -1
- package/dist/ui/assets/{skills-D7JjK7JH.js → skills-xMXangks.js} +1 -1
- package/dist/ui/assets/{stats-DoIhtLot.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-C1f6Hbdw.js +0 -1
- package/dist/ui/assets/SessionAudit-BCecQWde.js +0 -9
- package/dist/ui/assets/Settings-Cu4D-7tb.js +0 -47
- package/dist/ui/assets/Smiths-DnEH3nID.js +0 -1
- package/dist/ui/assets/index-D4fzIKy1.css +0 -1
package/dist/runtime/oracle.js
CHANGED
|
@@ -10,19 +10,21 @@ import { TaskRequestContext } from "./tasks/context.js";
|
|
|
10
10
|
import { TaskRepository } from "./tasks/repository.js";
|
|
11
11
|
import { Neo } from "./neo.js";
|
|
12
12
|
import { Trinity } from "./trinity.js";
|
|
13
|
+
import { Link } from "./link.js";
|
|
13
14
|
import { SmithDelegateTool } from "./tools/smith-tool.js";
|
|
14
15
|
import { TaskQueryTool, chronosTools, timeVerifierTool } from "./tools/index.js";
|
|
15
16
|
import { Construtor } from "./tools/factory.js";
|
|
16
17
|
import { MCPManager } from "../config/mcp-manager.js";
|
|
17
|
-
import { SkillRegistry,
|
|
18
|
+
import { SkillRegistry, createLoadSkillTool } from "./skills/index.js";
|
|
18
19
|
import { SmithRegistry } from "./smiths/registry.js";
|
|
19
20
|
import { AuditRepository } from "./audit/repository.js";
|
|
20
21
|
import { SetupRepository } from './setup/repository.js';
|
|
21
22
|
import { buildSetupTool } from './tools/setup-tool.js';
|
|
22
23
|
import { emitToolAuditEvents } from "./subagent-utils.js";
|
|
24
|
+
import { PATHS } from "../config/paths.js";
|
|
25
|
+
import { writeFileSync } from "fs";
|
|
23
26
|
const ORACLE_DELEGATION_TOOLS = new Set([
|
|
24
|
-
'apoc_delegate', 'neo_delegate', 'trinity_delegate', 'smith_delegate',
|
|
25
|
-
'skill_delegate', 'skill_execute',
|
|
27
|
+
'apoc_delegate', 'neo_delegate', 'trinity_delegate', 'smith_delegate', 'link_delegate',
|
|
26
28
|
]);
|
|
27
29
|
export class Oracle {
|
|
28
30
|
provider;
|
|
@@ -119,7 +121,7 @@ export class Oracle {
|
|
|
119
121
|
const toolCalls = msg.tool_calls ?? [];
|
|
120
122
|
if (!Array.isArray(toolCalls))
|
|
121
123
|
continue;
|
|
122
|
-
if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate" || tc?.name === "trinity_delegate" || tc?.name === "smith_delegate")) {
|
|
124
|
+
if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate" || tc?.name === "trinity_delegate" || tc?.name === "smith_delegate" || tc?.name === "link_delegate")) {
|
|
123
125
|
return true;
|
|
124
126
|
}
|
|
125
127
|
}
|
|
@@ -154,7 +156,7 @@ export class Oracle {
|
|
|
154
156
|
// Fail-open: Oracle can still initialize even if catalog refresh fails.
|
|
155
157
|
await Neo.refreshDelegateCatalog().catch(() => { });
|
|
156
158
|
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
157
|
-
|
|
159
|
+
await Link.refreshDelegateCatalog().catch(() => { });
|
|
158
160
|
// Build tool list — conditionally include SmithDelegateTool based on config
|
|
159
161
|
// Initialize setup repository (creates table if needed)
|
|
160
162
|
SetupRepository.getInstance();
|
|
@@ -164,8 +166,8 @@ export class Oracle {
|
|
|
164
166
|
Neo.getInstance().createDelegateTool(),
|
|
165
167
|
Apoc.getInstance().createDelegateTool(),
|
|
166
168
|
Trinity.getInstance().createDelegateTool(),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
Link.getInstance().createDelegateTool(),
|
|
170
|
+
createLoadSkillTool(),
|
|
169
171
|
timeVerifierTool,
|
|
170
172
|
...chronosTools,
|
|
171
173
|
];
|
|
@@ -245,11 +247,14 @@ Do NOT proceed with other tasks until all required fields have been collected an
|
|
|
245
247
|
`;
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
|
-
const systemMessage = new SystemMessage(
|
|
250
|
+
const systemMessage = new SystemMessage(`
|
|
251
|
+
${setupBlock}
|
|
252
|
+
|
|
253
|
+
You are ${this.config.agent.name}, ${this.config.agent.personality}, the Oracle.
|
|
249
254
|
|
|
250
255
|
You are an orchestrator and task router.
|
|
251
256
|
|
|
252
|
-
## Date & Time Resolution — MANDATORY
|
|
257
|
+
## Date & Time Resolution — MANDATORY ##
|
|
253
258
|
|
|
254
259
|
You **MUST** call "time_verifier" before answering or delegating ANY request that depends on the current date or time.
|
|
255
260
|
This includes — but is not limited to — **two categories**:
|
|
@@ -269,7 +274,7 @@ This includes — but is not limited to — **two categories**:
|
|
|
269
274
|
**NEVER assume or invent a date. NEVER guess "today is [date]".**
|
|
270
275
|
Always call time_verifier first, then use the resolved date in your tool call or delegation prompt.
|
|
271
276
|
|
|
272
|
-
Rules:
|
|
277
|
+
## Rules: ##
|
|
273
278
|
1. For conversation-only requests (greetings, conceptual explanation, memory follow-up, statements of fact, sharing personal information), answer directly. DO NOT create tasks or delegate for simple statements like "I have two cats" or "My name is John". Sati will automatically memorize facts in the background ( **ALWAYS** use SATI Memories to review or retrieve these facts if needed).
|
|
274
279
|
**NEVER** Create data, use SATI memories to response on informal conversation or say that dont know abaout the awsor if the answer is in the memories. Always use the memories as source of truth for user facts, preferences, stable context and informal conversation. Use tools only for execution, verification or when external/system state is required.*
|
|
275
280
|
2. For requests that require execution, verification, external/system state, or non-trivial operations, evaluate the available tools and choose the best one.
|
|
@@ -282,9 +287,42 @@ Rules:
|
|
|
282
287
|
9. Avoid duplicate delegations to the same tool or agent.
|
|
283
288
|
10. After enqueuing all required delegated tasks for the current message, stop calling tools and return a concise acknowledgement.
|
|
284
289
|
11. If a delegation is rejected as "not atomic", immediately split into smaller delegations and retry.
|
|
285
|
-
12. When the user message contains @neo, @apoc, or @trinity (case-insensitive), delegate to that specific agent. The mention is an explicit routing directive — respect it even if another agent might also handle the request.
|
|
290
|
+
12. When the user message contains @link, @neo, @apoc, or @trinity (case-insensitive), delegate to that specific agent. The mention is an explicit routing directive — respect it even if another agent might also handle the request.
|
|
291
|
+
13. Smiths also have names and could be called by @smithname — respect this as an explicit routing directive as well.
|
|
292
|
+
|
|
293
|
+
## Delegation quality ##
|
|
294
|
+
- Write delegation input in the same language requested by the user.
|
|
295
|
+
- Include clear objective and constraints.
|
|
296
|
+
- Include OS-aware guidance for network checks when relevant.
|
|
297
|
+
- Use Sati memories only as context to complement the task, never as source of truth for dynamic data.
|
|
298
|
+
- Use Sati memories to fill missing stable context fields (for example: city, timezone, language, currency, preferred units).
|
|
299
|
+
- If Sati memory is conflicting or uncertain for a required field, ask one short clarification before delegating.
|
|
300
|
+
- When completing missing fields from Sati, include explicit assumptions in delegation context using the format: "Assumption from Sati: key=value".
|
|
301
|
+
- Never infer sensitive data from Sati memories (credentials, legal identifiers, health details, financial account data).
|
|
302
|
+
- When assumptions were used, mention them briefly in the user-facing response and allow correction.
|
|
303
|
+
- break the request into multiple delegations if it contains multiple independent actions.
|
|
304
|
+
- Set a single task per delegation tool call. Do not combine multiple actions into one delegation, as it complicates execution and error handling.
|
|
305
|
+
- If user requested N independent actions, produce N delegated tasks (or direct answers), each one singular and tool-scoped.
|
|
306
|
+
- If use a delegation dont use the sati or messages history to answer directly in the same response. Just response with the delegations.
|
|
307
|
+
Example 1:
|
|
308
|
+
ask: "Tell me my account balance and do a ping on google.com"
|
|
309
|
+
good:
|
|
310
|
+
- delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and return the result."
|
|
311
|
+
- delegate to "apoc_delegate" with task "Ping google.com using the network diagnostics MCP and return reachability status. Use '-n' flag for Windows and '-c' for Linux/macOS."
|
|
312
|
+
bad:
|
|
313
|
+
- delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and ping google.com using the network diagnostics MCP, then return both results." (combines two independent actions into one delegation, which is not atomic and complicates execution and error handling)
|
|
314
|
+
|
|
315
|
+
Example 2:
|
|
316
|
+
ask: "I have two cats" or "My name is John"
|
|
317
|
+
good:
|
|
318
|
+
- Answer directly acknowledging the fact. Do NOT delegate.
|
|
319
|
+
bad:
|
|
320
|
+
- delegate to "neo_delegate" or "apoc_delegate" to save the fact. (Sati handles this automatically in the background)
|
|
286
321
|
|
|
287
|
-
|
|
322
|
+
--------------------------------------------------
|
|
323
|
+
CHRONOS SCHEDULING RULES
|
|
324
|
+
--------------------------------------------------
|
|
325
|
+
## Chronos Channel Routing ##
|
|
288
326
|
When calling chronos_schedule, set notify_channels based on the user's message:
|
|
289
327
|
- User mentions a specific channel (e.g., "no Discord", "no Telegram", "on Discord", "me avise pelo Discord"): set notify_channels to that channel — e.g. ["discord"] or ["telegram"].
|
|
290
328
|
- User says "all channels", "todos os canais", "em todos os canais": set notify_channels to [] (empty = broadcast to all active channels).
|
|
@@ -339,38 +377,33 @@ Behavior rules for Chronos execution context:
|
|
|
339
377
|
- **Action / task prompts** (e.g., "executar npm build", "verificar se o servidor está online", "enviar relatório"): execute normally using the appropriate tools.
|
|
340
378
|
- NEVER re-schedule or create new Chronos jobs from within a Chronos execution.
|
|
341
379
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
- break the request into multiple delegations if it contains multiple independent actions.
|
|
353
|
-
- Set a single task per delegation tool call. Do not combine multiple actions into one delegation, as it complicates execution and error handling.
|
|
354
|
-
- If user requested N independent actions, produce N delegated tasks (or direct answers), each one singular and tool-scoped.
|
|
355
|
-
- If use a delegation dont use the sati or messages history to answer directly in the same response. Just response with the delegations.
|
|
356
|
-
Example 1:
|
|
357
|
-
ask: "Tell me my account balance and do a ping on google.com"
|
|
358
|
-
good:
|
|
359
|
-
- delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and return the result."
|
|
360
|
-
- delegate to "apoc_delegate" with task "Ping google.com using the network diagnostics MCP and return reachability status. Use '-n' flag for Windows and '-c' for Linux/macOS."
|
|
361
|
-
bad:
|
|
362
|
-
- delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and ping google.com using the network diagnostics MCP, then return both results." (combines two independent actions into one delegation, which is not atomic and complicates execution and error handling)
|
|
380
|
+
---------------------
|
|
381
|
+
LINK DELEGATION RULES
|
|
382
|
+
---------------------
|
|
383
|
+
When delegating to Link:
|
|
384
|
+
- Include the user's original request and intent.
|
|
385
|
+
- Include a clear objective for what the Link agent should achieve.
|
|
386
|
+
- **NEVER inject Sati memories, assumptions, or pre-existing knowledge into the search query.** Link searches documents — the query must reflect ONLY what the user asked, not what you already know or assume.
|
|
387
|
+
- If the user asks "qual empresa trabalho atualmente?", delegate as-is: "Find the user's current company in their CV." Do NOT add specific names, values, or context from Sati memories to the query.
|
|
388
|
+
- Sati context may be included ONLY as a separate "context for interpretation" section, clearly separated from the search objective, so Link can use it to interpret results — never to filter or bias the search itself.
|
|
389
|
+
- Constraints such as response length, specific documents to search, or resources to avoid may be included.
|
|
363
390
|
|
|
364
|
-
Example 2:
|
|
365
|
-
ask: "I have two cats" or "My name is John"
|
|
366
|
-
good:
|
|
367
|
-
- Answer directly acknowledging the fact. Do NOT delegate.
|
|
368
|
-
bad:
|
|
369
|
-
- delegate to "neo_delegate" or "apoc_delegate" to save the fact. (Sati handles this automatically in the background)
|
|
370
391
|
|
|
392
|
+
---------------------
|
|
393
|
+
SKILLS
|
|
394
|
+
---------------------
|
|
371
395
|
${SkillRegistry.getInstance().getSystemPromptSection()}
|
|
396
|
+
|
|
397
|
+
---------------------
|
|
398
|
+
SMITHS
|
|
399
|
+
---------------------
|
|
372
400
|
${SmithRegistry.getInstance().getSystemPromptSection()}
|
|
373
401
|
`);
|
|
402
|
+
//save the system prompt on ~/.morpheus/system_prompt.txt for debugging and prompt engineering purposes
|
|
403
|
+
try {
|
|
404
|
+
writeFileSync(`${PATHS.root}/system_prompt.txt`, String(systemMessage.content), 'utf-8');
|
|
405
|
+
}
|
|
406
|
+
catch { }
|
|
374
407
|
// Resolve the authoritative session ID for this call.
|
|
375
408
|
// Priority: explicit taskContext > current history instance > fallback.
|
|
376
409
|
const currentSessionId = taskContext?.session_id
|
|
@@ -421,6 +454,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
421
454
|
Apoc.setSessionId(currentSessionId);
|
|
422
455
|
Neo.setSessionId(currentSessionId);
|
|
423
456
|
Trinity.setSessionId(currentSessionId);
|
|
457
|
+
Link.setSessionId(currentSessionId);
|
|
424
458
|
const invokeContext = {
|
|
425
459
|
origin_channel: taskContext?.origin_channel ?? "api",
|
|
426
460
|
session_id: taskContext?.session_id ?? currentSessionId ?? "default",
|
|
@@ -431,7 +465,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
431
465
|
let syncDelegationCount = 0;
|
|
432
466
|
const oracleStartMs = Date.now();
|
|
433
467
|
const response = await TaskRequestContext.run(invokeContext, async () => {
|
|
434
|
-
const agentResponse = await this.provider.invoke({ messages }, { recursionLimit:
|
|
468
|
+
const agentResponse = await this.provider.invoke({ messages }, { recursionLimit: 10 });
|
|
435
469
|
contextDelegationAcks = TaskRequestContext.getDelegationAcks();
|
|
436
470
|
syncDelegationCount = TaskRequestContext.getSyncDelegationCount();
|
|
437
471
|
return agentResponse;
|
|
@@ -464,7 +498,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
464
498
|
const startNewMessagesIndex = messages.length;
|
|
465
499
|
const newGeneratedMessages = response.messages.slice(startNewMessagesIndex);
|
|
466
500
|
// Emit tool_call audit events for Oracle's independent tool calls.
|
|
467
|
-
// Delegation tools (apoc/neo/trinity/smith/skill) are already audited
|
|
501
|
+
// Delegation tools (apoc/neo/trinity/smith/skill/link) are already audited
|
|
468
502
|
// inside buildDelegationTool or the task system — skip them here.
|
|
469
503
|
emitToolAuditEvents(newGeneratedMessages, currentSessionId ?? 'default', 'oracle', {
|
|
470
504
|
skipTools: ORACLE_DELEGATION_TOOLS,
|
|
@@ -648,19 +682,22 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
648
682
|
await Construtor.reload();
|
|
649
683
|
await Neo.refreshDelegateCatalog().catch(() => { });
|
|
650
684
|
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
651
|
-
|
|
685
|
+
await Link.refreshDelegateCatalog().catch(() => { });
|
|
652
686
|
this.provider = await ProviderFactory.create(this.config.llm, [
|
|
653
687
|
buildSetupTool(),
|
|
654
688
|
TaskQueryTool,
|
|
655
689
|
Neo.getInstance().createDelegateTool(),
|
|
656
690
|
Apoc.getInstance().createDelegateTool(),
|
|
657
691
|
Trinity.getInstance().createDelegateTool(),
|
|
658
|
-
|
|
659
|
-
|
|
692
|
+
Link.getInstance().createDelegateTool(),
|
|
693
|
+
createLoadSkillTool(),
|
|
660
694
|
timeVerifierTool,
|
|
661
695
|
...chronosTools,
|
|
662
696
|
]);
|
|
663
697
|
await Neo.getInstance().reload();
|
|
698
|
+
await Apoc.getInstance().reload();
|
|
699
|
+
await Trinity.getInstance().reload();
|
|
700
|
+
await Link.getInstance().reload();
|
|
664
701
|
this.display.log(`Oracle and Neo tools reloaded`, { source: 'Oracle' });
|
|
665
702
|
}
|
|
666
703
|
}
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -22,7 +22,6 @@ This folder contains custom skills for Morpheus.
|
|
|
22
22
|
---
|
|
23
23
|
name: my-skill
|
|
24
24
|
description: What this skill does (max 500 chars)
|
|
25
|
-
execution_mode: sync
|
|
26
25
|
version: 1.0.0
|
|
27
26
|
author: your-name
|
|
28
27
|
tags:
|
|
@@ -33,7 +32,7 @@ This folder contains custom skills for Morpheus.
|
|
|
33
32
|
|
|
34
33
|
# My Skill
|
|
35
34
|
|
|
36
|
-
Instructions for
|
|
35
|
+
Instructions for Oracle to follow when handling this skill.
|
|
37
36
|
|
|
38
37
|
## Steps
|
|
39
38
|
1. First step
|
|
@@ -43,23 +42,11 @@ This folder contains custom skills for Morpheus.
|
|
|
43
42
|
How to format the result.
|
|
44
43
|
\`\`\`
|
|
45
44
|
|
|
46
|
-
## Execution Modes
|
|
47
|
-
|
|
48
|
-
| Mode | Tool | Description |
|
|
49
|
-
|------|------|-------------|
|
|
50
|
-
| sync | skill_execute | Result returned immediately (default) |
|
|
51
|
-
| async | skill_delegate | Runs in background, notifies when done |
|
|
52
|
-
|
|
53
|
-
**sync** (default): Best for quick tasks like code review, analysis.
|
|
54
|
-
**async**: Best for long-running tasks like builds, deployments.
|
|
55
|
-
|
|
56
45
|
## How It Works
|
|
57
46
|
|
|
58
47
|
- Oracle lists available skills in its system prompt
|
|
59
|
-
- When a request matches a
|
|
60
|
-
-
|
|
61
|
-
- Keymaker has access to ALL tools (filesystem, shell, git, MCP, databases)
|
|
62
|
-
- Keymaker follows SKILL.md instructions to complete the task
|
|
48
|
+
- When a request matches a skill, Oracle calls \`load_skill\` to load its instructions
|
|
49
|
+
- Oracle then follows the skill instructions using its existing tools (DevKit, MCP, databases, etc.)
|
|
63
50
|
|
|
64
51
|
## Frontmatter Schema
|
|
65
52
|
|
|
@@ -67,7 +54,6 @@ This folder contains custom skills for Morpheus.
|
|
|
67
54
|
|-------|----------|---------|-------------|
|
|
68
55
|
| name | Yes | - | Unique identifier (a-z, 0-9, hyphens) |
|
|
69
56
|
| description | Yes | - | Short description (max 500 chars) |
|
|
70
|
-
| execution_mode | No | sync | sync or async |
|
|
71
57
|
| version | No | - | Semver (e.g., 1.0.0) |
|
|
72
58
|
| author | No | - | Your name |
|
|
73
59
|
| enabled | No | true | true/false |
|
|
@@ -85,6 +71,7 @@ export async function scaffold() {
|
|
|
85
71
|
fs.ensureDir(PATHS.cache),
|
|
86
72
|
fs.ensureDir(PATHS.commands),
|
|
87
73
|
fs.ensureDir(PATHS.skills),
|
|
74
|
+
fs.ensureDir(PATHS.docs),
|
|
88
75
|
]);
|
|
89
76
|
// Migrate config.yaml -> zaion.yaml if needed
|
|
90
77
|
await migrateConfigFile();
|
|
@@ -53,7 +53,6 @@ describe('SkillLoader', () => {
|
|
|
53
53
|
version: '1.0.0',
|
|
54
54
|
author: 'Test Author',
|
|
55
55
|
enabled: true,
|
|
56
|
-
execution_mode: 'sync',
|
|
57
56
|
tags: ['test', 'unit'],
|
|
58
57
|
examples: ['do something', 'do another thing'],
|
|
59
58
|
}, '# Test Skill\n\nInstructions here.');
|
|
@@ -66,7 +65,6 @@ describe('SkillLoader', () => {
|
|
|
66
65
|
expect(skill.version).toBe('1.0.0');
|
|
67
66
|
expect(skill.author).toBe('Test Author');
|
|
68
67
|
expect(skill.enabled).toBe(true);
|
|
69
|
-
expect(skill.execution_mode).toBe('sync');
|
|
70
68
|
expect(skill.tags).toEqual(['test', 'unit']);
|
|
71
69
|
expect(skill.examples).toEqual(['do something', 'do another thing']);
|
|
72
70
|
expect(skill.content).toBe('# Test Skill\n\nInstructions here.');
|
|
@@ -84,7 +82,6 @@ describe('SkillLoader', () => {
|
|
|
84
82
|
const skill = result.skills[0];
|
|
85
83
|
expect(skill.name).toBe('minimal-skill');
|
|
86
84
|
expect(skill.enabled).toBe(true); // default
|
|
87
|
-
expect(skill.execution_mode).toBe('sync'); // default
|
|
88
85
|
});
|
|
89
86
|
it('should report error for missing SKILL.md', async () => {
|
|
90
87
|
const skillDir = path.join(testDir, 'no-md-skill');
|
|
@@ -152,17 +149,17 @@ describe('SkillLoader', () => {
|
|
|
152
149
|
expect(result.skills).toHaveLength(0);
|
|
153
150
|
expect(result.errors).toHaveLength(0);
|
|
154
151
|
});
|
|
155
|
-
it('should load
|
|
156
|
-
const skillDir = path.join(testDir, '
|
|
152
|
+
it('should load skill with tags correctly', async () => {
|
|
153
|
+
const skillDir = path.join(testDir, 'tagged-skill');
|
|
157
154
|
fs.ensureDirSync(skillDir);
|
|
158
155
|
createSkillMd(skillDir, {
|
|
159
|
-
name: '
|
|
160
|
-
description: '
|
|
161
|
-
|
|
162
|
-
}, '
|
|
156
|
+
name: 'tagged-skill',
|
|
157
|
+
description: 'A tagged skill',
|
|
158
|
+
tags: ['ops', 'deploy'],
|
|
159
|
+
}, 'Tagged task instructions');
|
|
163
160
|
const result = await loader.scan();
|
|
164
161
|
expect(result.skills).toHaveLength(1);
|
|
165
|
-
expect(result.skills[0].
|
|
162
|
+
expect(result.skills[0].tags).toEqual(['ops', 'deploy']);
|
|
166
163
|
});
|
|
167
164
|
});
|
|
168
165
|
describe('content handling', () => {
|
|
@@ -129,13 +129,12 @@ describe('SkillRegistry', () => {
|
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
describe('getSystemPromptSection()', () => {
|
|
132
|
-
it('should generate prompt section with
|
|
132
|
+
it('should generate prompt section with skills', async () => {
|
|
133
133
|
const skillDir = path.join(TEST_DIR, 'prompt-skill');
|
|
134
134
|
fs.ensureDirSync(skillDir);
|
|
135
135
|
createSkillMd(skillDir, {
|
|
136
136
|
name: 'prompt-skill',
|
|
137
137
|
description: 'A skill for prompts',
|
|
138
|
-
execution_mode: 'sync',
|
|
139
138
|
examples: ['example usage'],
|
|
140
139
|
}, 'Instructions for prompt skill');
|
|
141
140
|
const registry = SkillRegistry.getInstance();
|
|
@@ -144,22 +143,7 @@ describe('SkillRegistry', () => {
|
|
|
144
143
|
expect(section).toContain('Available Skills');
|
|
145
144
|
expect(section).toContain('prompt-skill');
|
|
146
145
|
expect(section).toContain('A skill for prompts');
|
|
147
|
-
expect(section).toContain('
|
|
148
|
-
});
|
|
149
|
-
it('should generate prompt section with async skills', async () => {
|
|
150
|
-
const skillDir = path.join(TEST_DIR, 'async-skill');
|
|
151
|
-
fs.ensureDirSync(skillDir);
|
|
152
|
-
createSkillMd(skillDir, {
|
|
153
|
-
name: 'async-skill',
|
|
154
|
-
description: 'An async skill',
|
|
155
|
-
execution_mode: 'async',
|
|
156
|
-
}, 'Instructions for async skill');
|
|
157
|
-
const registry = SkillRegistry.getInstance();
|
|
158
|
-
await registry.load();
|
|
159
|
-
const section = registry.getSystemPromptSection();
|
|
160
|
-
expect(section).toContain('Async Skills');
|
|
161
|
-
expect(section).toContain('async-skill');
|
|
162
|
-
expect(section).toContain('skill_delegate');
|
|
146
|
+
expect(section).toContain('load_skill');
|
|
163
147
|
});
|
|
164
148
|
it('should return empty string when no skills', async () => {
|
|
165
149
|
const registry = SkillRegistry.getInstance();
|