morpheus-cli 0.4.15 → 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.
- package/README.md +275 -1116
- package/dist/channels/telegram.js +206 -74
- package/dist/cli/commands/doctor.js +34 -0
- package/dist/cli/commands/init.js +128 -0
- package/dist/cli/commands/restart.js +17 -0
- package/dist/cli/commands/start.js +15 -0
- package/dist/config/manager.js +51 -0
- package/dist/config/schemas.js +7 -0
- package/dist/devkit/tools/network.js +1 -1
- package/dist/http/api.js +177 -10
- package/dist/runtime/apoc.js +25 -17
- package/dist/runtime/memory/sati/repository.js +30 -2
- package/dist/runtime/memory/sati/service.js +46 -15
- package/dist/runtime/memory/sati/system-prompts.js +71 -29
- package/dist/runtime/memory/sqlite.js +24 -0
- package/dist/runtime/neo.js +134 -0
- package/dist/runtime/oracle.js +244 -205
- package/dist/runtime/providers/factory.js +1 -12
- package/dist/runtime/tasks/context.js +53 -0
- package/dist/runtime/tasks/dispatcher.js +70 -0
- package/dist/runtime/tasks/notifier.js +68 -0
- package/dist/runtime/tasks/repository.js +370 -0
- package/dist/runtime/tasks/types.js +1 -0
- package/dist/runtime/tasks/worker.js +96 -0
- package/dist/runtime/tools/apoc-tool.js +61 -8
- package/dist/runtime/tools/delegation-guard.js +29 -0
- package/dist/runtime/tools/index.js +1 -0
- package/dist/runtime/tools/neo-tool.js +99 -0
- package/dist/runtime/tools/task-query-tool.js +76 -0
- package/dist/runtime/webhooks/dispatcher.js +10 -19
- package/dist/types/config.js +10 -0
- package/dist/ui/assets/index-20lLB1sM.js +112 -0
- package/dist/ui/assets/index-BJ56bRfs.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/sw.js +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-LemKVRjC.js +0 -112
- package/dist/ui/assets/index-TCQ7VNYO.css +0 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { tool } from "@langchain/core/tools";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
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";
|
|
4
7
|
/**
|
|
5
8
|
* Tool that Oracle uses to delegate devtools tasks to Apoc.
|
|
6
9
|
* Oracle should call this whenever the user requests operations like:
|
|
@@ -14,16 +17,66 @@ import { Apoc } from "../apoc.js";
|
|
|
14
17
|
*/
|
|
15
18
|
export const ApocDelegateTool = tool(async ({ task, context }) => {
|
|
16
19
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const display = DisplayManager.getInstance();
|
|
21
|
+
if (isLikelyCompositeDelegationTask(task)) {
|
|
22
|
+
display.log(`Apoc delegation rejected (non-atomic task): ${task.slice(0, 140)}`, {
|
|
23
|
+
source: "ApocDelegateTool",
|
|
24
|
+
level: "warning",
|
|
25
|
+
});
|
|
26
|
+
return compositeDelegationError();
|
|
27
|
+
}
|
|
28
|
+
const existingAck = TaskRequestContext.findDuplicateDelegation("apoc", task);
|
|
29
|
+
if (existingAck) {
|
|
30
|
+
display.log(`Apoc delegation deduplicated. Reusing task ${existingAck.task_id}.`, {
|
|
31
|
+
source: "ApocDelegateTool",
|
|
32
|
+
level: "info",
|
|
33
|
+
});
|
|
34
|
+
return `Task ${existingAck.task_id} already queued for ${existingAck.agent} execution.`;
|
|
35
|
+
}
|
|
36
|
+
if (!TaskRequestContext.canEnqueueDelegation()) {
|
|
37
|
+
display.log(`Apoc delegation blocked by per-turn limit.`, {
|
|
38
|
+
source: "ApocDelegateTool",
|
|
39
|
+
level: "warning",
|
|
40
|
+
});
|
|
41
|
+
return "Delegation limit reached for this user turn. Split the request or wait for current tasks.";
|
|
42
|
+
}
|
|
43
|
+
const ctx = TaskRequestContext.get();
|
|
44
|
+
const repository = TaskRepository.getInstance();
|
|
45
|
+
const created = repository.createTask({
|
|
46
|
+
agent: "apoc",
|
|
47
|
+
input: task,
|
|
48
|
+
context: context ?? null,
|
|
49
|
+
origin_channel: ctx?.origin_channel ?? "api",
|
|
50
|
+
session_id: ctx?.session_id ?? "default",
|
|
51
|
+
origin_message_id: ctx?.origin_message_id ?? null,
|
|
52
|
+
origin_user_id: ctx?.origin_user_id ?? null,
|
|
53
|
+
max_attempts: 3,
|
|
54
|
+
});
|
|
55
|
+
TaskRequestContext.setDelegationAck({ task_id: created.id, agent: "apoc", task });
|
|
56
|
+
display.log(`Apoc task created: ${created.id}`, {
|
|
57
|
+
source: "ApocDelegateTool",
|
|
58
|
+
level: "info",
|
|
59
|
+
meta: {
|
|
60
|
+
agent: created.agent,
|
|
61
|
+
origin_channel: created.origin_channel,
|
|
62
|
+
session_id: created.session_id,
|
|
63
|
+
input: created.input,
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return `Task ${created.id} queued for Apoc execution.`;
|
|
20
67
|
}
|
|
21
68
|
catch (err) {
|
|
22
|
-
|
|
69
|
+
const display = DisplayManager.getInstance();
|
|
70
|
+
display.log(`ApocDelegateTool error: ${err.message}`, { source: "ApocDelegateTool", level: "error" });
|
|
71
|
+
return `Apoc task enqueue failed: ${err.message}`;
|
|
23
72
|
}
|
|
24
73
|
}, {
|
|
25
74
|
name: "apoc_delegate",
|
|
26
|
-
description: `Delegate a devtools task to Apoc, the specialized development subagent.
|
|
75
|
+
description: `Delegate a devtools task to Apoc, the specialized development subagent, asynchronously.
|
|
76
|
+
|
|
77
|
+
This tool enqueues a background task and returns an acknowledgement with task id.
|
|
78
|
+
Do not expect final execution output in the same response.
|
|
79
|
+
Each task must contain a single atomic action with a clear expected result.
|
|
27
80
|
|
|
28
81
|
Use this tool when the user asks for ANY of the following:
|
|
29
82
|
- File operations: read, write, create, delete files or directories
|
|
@@ -39,7 +92,7 @@ Use this tool when the user asks for ANY of the following:
|
|
|
39
92
|
Provide a clear natural language task description. Optionally provide context
|
|
40
93
|
from the current conversation to help Apoc understand the broader goal.`,
|
|
41
94
|
schema: z.object({
|
|
42
|
-
task: z.string().describe("Clear description of the devtools task to execute"),
|
|
43
|
-
context: z.string().optional().describe("Optional context from the conversation to help Apoc understand the goal"),
|
|
95
|
+
task: z.string().describe("Clear description of the devtools task to execute **in the user's language**"),
|
|
96
|
+
context: z.string().optional().describe("Optional context from the conversation to help Apoc understand the goal **in the user's language**"),
|
|
44
97
|
}),
|
|
45
98
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function normalize(text) {
|
|
2
|
+
return text
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/\s+/g, " ")
|
|
5
|
+
.trim();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Heuristic guard to reject clearly multi-action delegations.
|
|
9
|
+
* Allows phrases like "do X and return/report Y".
|
|
10
|
+
*/
|
|
11
|
+
export function isLikelyCompositeDelegationTask(task) {
|
|
12
|
+
const t = normalize(task);
|
|
13
|
+
if (!t)
|
|
14
|
+
return false;
|
|
15
|
+
if (/[;\n]/.test(t))
|
|
16
|
+
return true;
|
|
17
|
+
const conjunction = /\b(and|then|also|e|depois|tambem|também|alem disso|além disso)\b/;
|
|
18
|
+
if (!conjunction.test(t))
|
|
19
|
+
return false;
|
|
20
|
+
// "and return/report" is usually part of a single atomic objective.
|
|
21
|
+
const allowedSecondStep = /\b(and|then|e|depois)\s+(return|report|summarize|retorne|informe|resuma|mostre|show)\b/;
|
|
22
|
+
if (allowedSecondStep.test(t))
|
|
23
|
+
return false;
|
|
24
|
+
const actionVerbAfterConjunction = /\b(and|then|also|e|depois|tambem|também)\s+(check|ping|run|execute|search|fetch|get|list|create|update|delete|open|verify|consult|verificar|fazer|executar|buscar|obter|listar|criar|atualizar|deletar|abrir)\b/;
|
|
25
|
+
return actionVerbAfterConjunction.test(t);
|
|
26
|
+
}
|
|
27
|
+
export function compositeDelegationError() {
|
|
28
|
+
return "Delegation rejected: task must be atomic (single action). Split this request into multiple delegations, one task per action/tool.";
|
|
29
|
+
}
|
|
@@ -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
|
|
25
|
-
* 3.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
/**
|
package/dist/types/config.js
CHANGED
|
@@ -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
|
};
|