morpheus-cli 0.4.14 → 0.5.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.
Files changed (38) hide show
  1. package/README.md +275 -1116
  2. package/dist/channels/telegram.js +210 -73
  3. package/dist/cli/commands/doctor.js +34 -0
  4. package/dist/cli/commands/init.js +128 -0
  5. package/dist/cli/commands/restart.js +17 -0
  6. package/dist/cli/commands/start.js +15 -0
  7. package/dist/config/manager.js +51 -0
  8. package/dist/config/schemas.js +7 -0
  9. package/dist/devkit/tools/network.js +1 -1
  10. package/dist/http/api.js +177 -10
  11. package/dist/runtime/apoc.js +139 -32
  12. package/dist/runtime/memory/sati/repository.js +30 -2
  13. package/dist/runtime/memory/sati/service.js +46 -15
  14. package/dist/runtime/memory/sati/system-prompts.js +71 -29
  15. package/dist/runtime/memory/sqlite.js +24 -0
  16. package/dist/runtime/neo.js +134 -0
  17. package/dist/runtime/oracle.js +244 -133
  18. package/dist/runtime/providers/factory.js +1 -12
  19. package/dist/runtime/tasks/context.js +53 -0
  20. package/dist/runtime/tasks/dispatcher.js +70 -0
  21. package/dist/runtime/tasks/notifier.js +68 -0
  22. package/dist/runtime/tasks/repository.js +370 -0
  23. package/dist/runtime/tasks/types.js +1 -0
  24. package/dist/runtime/tasks/worker.js +96 -0
  25. package/dist/runtime/tools/apoc-tool.js +61 -8
  26. package/dist/runtime/tools/delegation-guard.js +29 -0
  27. package/dist/runtime/tools/index.js +1 -0
  28. package/dist/runtime/tools/neo-tool.js +99 -0
  29. package/dist/runtime/tools/task-query-tool.js +76 -0
  30. package/dist/runtime/webhooks/dispatcher.js +10 -19
  31. package/dist/types/config.js +10 -0
  32. package/dist/ui/assets/index-20lLB1sM.js +112 -0
  33. package/dist/ui/assets/index-BJ56bRfs.css +1 -0
  34. package/dist/ui/index.html +2 -2
  35. package/dist/ui/sw.js +1 -1
  36. package/package.json +1 -1
  37. package/dist/ui/assets/index-LemKVRjC.js +0 -112
  38. package/dist/ui/assets/index-TCQ7VNYO.css +0 -1
@@ -0,0 +1,99 @@
1
+ import { tool } from "@langchain/core/tools";
2
+ import { z } from "zod";
3
+ import { TaskRepository } from "../tasks/repository.js";
4
+ import { TaskRequestContext } from "../tasks/context.js";
5
+ import { compositeDelegationError, isLikelyCompositeDelegationTask } from "./delegation-guard.js";
6
+ import { DisplayManager } from "../display.js";
7
+ const NEO_BASE_DESCRIPTION = `Delegate execution to Neo asynchronously.
8
+
9
+ This tool creates a background task and returns an acknowledgement with task id.
10
+ Use it for requests that require tools of morpheus config / motpheus analystics / morpheus diagnostics and available MCPs,
11
+ or external/stateful verification.
12
+ Each delegated task must contain one atomic objective.`;
13
+ function normalizeDescription(text) {
14
+ if (!text)
15
+ return "No description";
16
+ return text.replace(/\s+/g, " ").trim();
17
+ }
18
+ function buildCatalogSection(tools) {
19
+ if (tools.length === 0) {
20
+ return "\n\nNeo MCP tools catalog: no tools currently loaded.";
21
+ }
22
+ const maxItems = 32;
23
+ const lines = tools.slice(0, maxItems).map((t) => {
24
+ const desc = normalizeDescription(t.description).slice(0, 120);
25
+ return `- ${t.name}: ${desc}`;
26
+ });
27
+ const hidden = tools.length - lines.length;
28
+ if (hidden > 0) {
29
+ lines.push(`- ... and ${hidden} more tools`);
30
+ }
31
+ return `\n\nNeo MCP tools catalog (runtime loaded):\n${lines.join("\n")}`;
32
+ }
33
+ export function updateNeoDelegateToolDescription(tools) {
34
+ const full = `${NEO_BASE_DESCRIPTION}${buildCatalogSection(tools)}`;
35
+ NeoDelegateTool.description = full;
36
+ }
37
+ export const NeoDelegateTool = tool(async ({ task, context }) => {
38
+ try {
39
+ const display = DisplayManager.getInstance();
40
+ if (isLikelyCompositeDelegationTask(task)) {
41
+ display.log(`Neo delegation rejected (non-atomic task): ${task.slice(0, 140)}`, {
42
+ source: "NeoDelegateTool",
43
+ level: "warning",
44
+ });
45
+ return compositeDelegationError();
46
+ }
47
+ const existingAck = TaskRequestContext.findDuplicateDelegation("neo", task);
48
+ if (existingAck) {
49
+ display.log(`Neo delegation deduplicated. Reusing task ${existingAck.task_id}.`, {
50
+ source: "NeoDelegateTool",
51
+ level: "info",
52
+ });
53
+ return `Task ${existingAck.task_id} already queued for ${existingAck.agent} execution.`;
54
+ }
55
+ if (!TaskRequestContext.canEnqueueDelegation()) {
56
+ display.log(`Neo delegation blocked by per-turn limit.`, {
57
+ source: "NeoDelegateTool",
58
+ level: "warning",
59
+ });
60
+ return "Delegation limit reached for this user turn. Split the request or wait for current tasks.";
61
+ }
62
+ const ctx = TaskRequestContext.get();
63
+ const repository = TaskRepository.getInstance();
64
+ const created = repository.createTask({
65
+ agent: "neo",
66
+ input: task,
67
+ context: context ?? null,
68
+ origin_channel: ctx?.origin_channel ?? "api",
69
+ session_id: ctx?.session_id ?? "default",
70
+ origin_message_id: ctx?.origin_message_id ?? null,
71
+ origin_user_id: ctx?.origin_user_id ?? null,
72
+ max_attempts: 3,
73
+ });
74
+ TaskRequestContext.setDelegationAck({ task_id: created.id, agent: "neo", task });
75
+ display.log(`Neo task created: ${created.id}`, {
76
+ source: "NeoDelegateTool",
77
+ level: "info",
78
+ meta: {
79
+ agent: created.agent,
80
+ origin_channel: created.origin_channel,
81
+ session_id: created.session_id,
82
+ input: created.input,
83
+ }
84
+ });
85
+ return `Task ${created.id} queued for Neo execution.`;
86
+ }
87
+ catch (err) {
88
+ const display = DisplayManager.getInstance();
89
+ display.log(`NeoDelegateTool error: ${err.message}`, { source: "NeoDelegateTool", level: "error" });
90
+ return `Neo task enqueue failed: ${err.message}`;
91
+ }
92
+ }, {
93
+ name: "neo_delegate",
94
+ description: NEO_BASE_DESCRIPTION,
95
+ schema: z.object({
96
+ task: z.string().describe("Clear task objective for Neo to execute **in the user's language**"),
97
+ context: z.string().optional().describe("Optional context from conversation **in the user's language**"),
98
+ }),
99
+ });
@@ -0,0 +1,76 @@
1
+ import { tool } from "@langchain/core/tools";
2
+ import { z } from "zod";
3
+ import { TaskRepository } from "../tasks/repository.js";
4
+ import { TaskRequestContext } from "../tasks/context.js";
5
+ function toTaskView(task) {
6
+ return {
7
+ id: task.id,
8
+ agent: task.agent,
9
+ status: task.status,
10
+ input: task.input,
11
+ output: task.output,
12
+ error: task.error,
13
+ session_id: task.session_id,
14
+ origin_channel: task.origin_channel,
15
+ created_at: task.created_at,
16
+ started_at: task.started_at,
17
+ finished_at: task.finished_at,
18
+ updated_at: task.updated_at,
19
+ };
20
+ }
21
+ export const TaskQueryTool = tool(async ({ task_id, limit, session_id, include_completed }) => {
22
+ try {
23
+ const repository = TaskRepository.getInstance();
24
+ if (task_id) {
25
+ const task = repository.getTaskById(task_id);
26
+ if (!task) {
27
+ return JSON.stringify({
28
+ found: false,
29
+ query: { task_id },
30
+ message: "Task not found",
31
+ });
32
+ }
33
+ return JSON.stringify({
34
+ found: true,
35
+ query: { task_id },
36
+ task: toTaskView(task),
37
+ });
38
+ }
39
+ const ctx = TaskRequestContext.get();
40
+ const targetSessionId = session_id ?? ctx?.session_id;
41
+ const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
42
+ const baseLimit = Math.max(requestedLimit * 5, 50);
43
+ const tasks = repository.listTasks({
44
+ session_id: targetSessionId,
45
+ limit: baseLimit,
46
+ });
47
+ const filtered = tasks.filter((task) => include_completed ? true : task.status !== "completed");
48
+ const latest = filtered.slice(0, requestedLimit);
49
+ return JSON.stringify({
50
+ found: latest.length > 0,
51
+ query: {
52
+ task_id: null,
53
+ limit: requestedLimit,
54
+ session_id: targetSessionId ?? null,
55
+ include_completed: include_completed ?? false,
56
+ },
57
+ count: latest.length,
58
+ tasks: latest.map(toTaskView),
59
+ });
60
+ }
61
+ catch (error) {
62
+ return JSON.stringify({
63
+ found: false,
64
+ error: error?.message ?? String(error),
65
+ });
66
+ }
67
+ }, {
68
+ name: "task_query",
69
+ description: "Query task status directly from database without delegation. Supports lookup by task id, or latest tasks (default: only non-completed) for current session.",
70
+ schema: z.object({
71
+ task_id: z.string().uuid().optional().describe("Specific task id to fetch"),
72
+ limit: z.number().int().min(1).max(50).optional().describe("Max number of tasks to return when task_id is not provided (default: 10)"),
73
+ session_id: z.string().optional().describe("Optional session id filter; if omitted, uses current request session"),
74
+ include_completed: z.boolean().optional().describe("Include completed tasks when listing latest tasks (default: false)"),
75
+ }),
76
+ });
@@ -21,9 +21,8 @@ export class WebhookDispatcher {
21
21
  /**
22
22
  * Main orchestration method — runs in background (fire-and-forget).
23
23
  * 1. Builds the agent prompt from webhook.prompt + payload
24
- * 2. Sends to Oracle (which can use MCPs or delegate to Apoc)
25
- * 3. Persists result to DB
26
- * 4. Dispatches to configured channels
24
+ * 2. Sends to Oracle for async task enqueue
25
+ * 3. Final result is persisted by TaskNotifier through TaskDispatcher
27
26
  */
28
27
  async dispatch(webhook, payload, notificationId) {
29
28
  const repo = WebhookRepository.getInstance();
@@ -35,26 +34,18 @@ export class WebhookDispatcher {
35
34
  return;
36
35
  }
37
36
  const message = this.buildPrompt(webhook.prompt, payload);
38
- let result;
39
- let status;
40
37
  try {
41
- result = await oracle.chat(message);
42
- status = 'completed';
43
- this.display.log(`Webhook "${webhook.name}" completed (notification: ${notificationId})`, { source: 'Webhooks', level: 'success' });
38
+ await oracle.chat(message, undefined, false, {
39
+ origin_channel: 'webhook',
40
+ session_id: `webhook-${webhook.id}`,
41
+ origin_message_id: notificationId,
42
+ });
43
+ this.display.log(`Webhook "${webhook.name}" accepted and queued (notification: ${notificationId})`, { source: 'Webhooks', level: 'success' });
44
44
  }
45
45
  catch (err) {
46
- result = `Execution error: ${err.message}`;
47
- status = 'failed';
46
+ const result = `Execution error: ${err.message}`;
48
47
  this.display.log(`Webhook "${webhook.name}" failed: ${err.message}`, { source: 'Webhooks', level: 'error' });
49
- }
50
- // Persist result
51
- repo.updateNotificationResult(notificationId, status, result);
52
- // Dispatch to configured channels
53
- for (const channel of webhook.notification_channels) {
54
- if (channel === 'telegram') {
55
- await this.sendTelegram(webhook.name, result, status);
56
- }
57
- // 'ui' channel is handled by UI polling — nothing extra needed here
48
+ repo.updateNotificationResult(notificationId, 'failed', result);
58
49
  }
59
50
  }
60
51
  /**
@@ -18,6 +18,11 @@ export const DEFAULT_CONFIG = {
18
18
  memory: {
19
19
  limit: 100
20
20
  },
21
+ runtime: {
22
+ async_tasks: {
23
+ enabled: true,
24
+ },
25
+ },
21
26
  llm: {
22
27
  provider: 'openai',
23
28
  model: 'gpt-4',
@@ -45,5 +50,10 @@ export const DEFAULT_CONFIG = {
45
50
  model: 'gpt-4',
46
51
  temperature: 0.2,
47
52
  timeout_ms: 30000,
53
+ },
54
+ neo: {
55
+ provider: 'openai',
56
+ model: 'gpt-4',
57
+ temperature: 0.2,
48
58
  }
49
59
  };