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,13 +1,27 @@
|
|
|
1
1
|
export const SATI_EVALUATION_PROMPT = `You are **Sati**, an autonomous background memory manager for an AI assistant.
|
|
2
|
-
Your goal is to analyze the conversation
|
|
2
|
+
Your goal is to analyze the conversation and decide what changes to make to the long-term memory store.
|
|
3
|
+
You have full CRUD power: you can create new memories, edit existing ones, or delete memories that are no longer valid.
|
|
3
4
|
|
|
4
5
|
### INPUT DATA
|
|
5
|
-
You will receive:
|
|
6
|
-
1.
|
|
7
|
-
2.
|
|
6
|
+
You will receive a JSON object with:
|
|
7
|
+
1. \`recent_conversation\` — list of recent messages (user and assistant).
|
|
8
|
+
2. \`existing_memories\` — full objects for all current memories (with IDs), so you can reference them for edits or deletions.
|
|
9
|
+
|
|
10
|
+
Example input:
|
|
11
|
+
\`\`\`json
|
|
12
|
+
{
|
|
13
|
+
"recent_conversation": [
|
|
14
|
+
{ "role": "user", "content": "..." },
|
|
15
|
+
{ "role": "assistant", "content": "..." }
|
|
16
|
+
],
|
|
17
|
+
"existing_memories": [
|
|
18
|
+
{ "id": "uuid", "category": "preference", "importance": "medium", "summary": "..." }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
\`\`\`
|
|
8
22
|
|
|
9
23
|
### MEMORY CATEGORIES
|
|
10
|
-
Classify
|
|
24
|
+
Classify memories into one of these types:
|
|
11
25
|
- **preference**: User preferences (e.g., "I like dark mode", "Use TypeScript").
|
|
12
26
|
- **project**: Details about the user's projects, architecture, or tech stack.
|
|
13
27
|
- **identity**: Facts about the user's identity, role, or background.
|
|
@@ -22,32 +36,60 @@ Classify any new memory into one of these types:
|
|
|
22
36
|
- **professional_profile**: Job title, industry, skills.
|
|
23
37
|
|
|
24
38
|
### CRITICAL RULES
|
|
25
|
-
0. **
|
|
26
|
-
1. **NO SECRETS**: NEVER store API keys, passwords, credit cards, or private tokens.
|
|
27
|
-
2. **NO
|
|
28
|
-
3. **NO
|
|
29
|
-
4. **IMPORTANCE**: Assign 'low', 'medium', or 'high'
|
|
39
|
+
0. **SUMMARIES IN ENGLISH AND NATIVE LANGUAGE**: Always write summaries in English. If the original content is in another language, also include a summary in that language (e.g., "Prefers TypeScript | Prefere TypeScript").
|
|
40
|
+
1. **NO SECRETS**: NEVER store API keys, passwords, credit cards, or private tokens.
|
|
41
|
+
2. **NO CHIT-CHAT**: Do not store trivial conversation like "Hello", "Thanks", "How are you?".
|
|
42
|
+
3. **NO PERSONAL FINANCIAL**: Avoid storing sensitive financial information (income, debts, expenses).
|
|
43
|
+
4. **IMPORTANCE**: Assign 'low', 'medium', or 'high'. Prefer 'medium' or 'high' — only use 'low' for minor context.
|
|
44
|
+
5. **OBEY THE USER**: If the user explicitly states something should be remembered, store it with at least 'medium' importance.
|
|
45
|
+
|
|
46
|
+
### WHEN TO INCLUDE (create new memory)
|
|
47
|
+
- The information is **genuinely new** and not covered by any entry in \`existing_memories\`.
|
|
48
|
+
- Do NOT create a new entry if an existing memory already captures the same fact.
|
|
30
49
|
|
|
31
|
-
###
|
|
32
|
-
|
|
50
|
+
### WHEN TO EDIT an existing memory
|
|
51
|
+
- The user **confirmed or reinforced** an existing preference/fact → elevate importance (e.g., medium → high).
|
|
52
|
+
- The existing memory is **slightly incorrect or outdated** → correct the summary.
|
|
53
|
+
- The user provided **additional detail** about something already stored → update details field.
|
|
54
|
+
- Use the **exact \`id\`** from \`existing_memories\`. Only include fields that actually change.
|
|
55
|
+
- **Never duplicate** — if the info hasn't changed, do nothing.
|
|
56
|
+
|
|
57
|
+
### WHEN TO DELETE an existing memory
|
|
58
|
+
- The user **explicitly denied** a stored fact ("No, I don't prefer X", "That's wrong").
|
|
59
|
+
- A memory was **proven false** during this conversation.
|
|
60
|
+
- The user **explicitly asked to forget** something specific.
|
|
61
|
+
- Use the **exact \`id\`** from \`existing_memories\`.
|
|
33
62
|
|
|
34
63
|
### OUTPUT FORMAT
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
Respond with a valid JSON object matching this structure. All three arrays are required (use empty arrays if nothing applies):
|
|
65
|
+
|
|
66
|
+
\`\`\`json
|
|
67
|
+
{
|
|
68
|
+
"inclusions": [
|
|
69
|
+
{
|
|
70
|
+
"category": "preference",
|
|
71
|
+
"importance": "high",
|
|
72
|
+
"summary": "Concise factual statement | Summary in native language",
|
|
73
|
+
"reason": "Why this is being stored"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"edits": [
|
|
77
|
+
{
|
|
78
|
+
"id": "uuid-of-existing-memory",
|
|
79
|
+
"importance": "high",
|
|
80
|
+
"summary": "Updated summary if changed",
|
|
81
|
+
"details": "Updated details if changed",
|
|
82
|
+
"reason": "Why this is being edited"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"deletions": [
|
|
86
|
+
{
|
|
87
|
+
"id": "uuid-of-existing-memory",
|
|
88
|
+
"reason": "Why this memory is being deleted"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
\`\`\`
|
|
52
93
|
|
|
94
|
+
Empty arrays are perfectly valid. Only include fields in edits that actually need to change.
|
|
53
95
|
`;
|
|
@@ -290,6 +290,30 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
290
290
|
throw new Error(`Failed to retrieve messages: ${error}`);
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Retrieves raw stored messages for one or more session IDs.
|
|
295
|
+
* Useful when the caller needs metadata like session_id and created_at.
|
|
296
|
+
*/
|
|
297
|
+
async getRawMessagesBySessionIds(sessionIds, limit = this.limit) {
|
|
298
|
+
if (sessionIds.length === 0) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const placeholders = sessionIds.map(() => '?').join(', ');
|
|
303
|
+
const stmt = this.db.prepare(`SELECT id, session_id, type, content, created_at, input_tokens, output_tokens, total_tokens, cache_read_tokens, provider, model
|
|
304
|
+
FROM messages
|
|
305
|
+
WHERE session_id IN (${placeholders})
|
|
306
|
+
ORDER BY id DESC
|
|
307
|
+
LIMIT ?`);
|
|
308
|
+
return stmt.all(...sessionIds, limit);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (error instanceof Error && error.message.includes('SQLITE_BUSY')) {
|
|
312
|
+
throw new Error(`Database is locked. Please try again. Original error: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
throw new Error(`Failed to retrieve raw messages: ${error}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
293
317
|
/**
|
|
294
318
|
* Adds a message to the database.
|
|
295
319
|
* @param message The message to add
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages";
|
|
2
|
+
import { ConfigManager } from "../config/manager.js";
|
|
3
|
+
import { ProviderFactory } from "./providers/factory.js";
|
|
4
|
+
import { ProviderError } from "./errors.js";
|
|
5
|
+
import { DisplayManager } from "./display.js";
|
|
6
|
+
import { Construtor } from "./tools/factory.js";
|
|
7
|
+
import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool, } from "./tools/index.js";
|
|
8
|
+
import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
|
|
9
|
+
import { TaskRequestContext } from "./tasks/context.js";
|
|
10
|
+
import { updateNeoDelegateToolDescription } from "./tools/neo-tool.js";
|
|
11
|
+
export class Neo {
|
|
12
|
+
static instance = null;
|
|
13
|
+
static currentSessionId = undefined;
|
|
14
|
+
agent;
|
|
15
|
+
config;
|
|
16
|
+
display = DisplayManager.getInstance();
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config || ConfigManager.getInstance().get();
|
|
19
|
+
}
|
|
20
|
+
static setSessionId(sessionId) {
|
|
21
|
+
Neo.currentSessionId = sessionId;
|
|
22
|
+
}
|
|
23
|
+
static getInstance(config) {
|
|
24
|
+
if (!Neo.instance) {
|
|
25
|
+
Neo.instance = new Neo(config);
|
|
26
|
+
}
|
|
27
|
+
return Neo.instance;
|
|
28
|
+
}
|
|
29
|
+
static resetInstance() {
|
|
30
|
+
Neo.instance = null;
|
|
31
|
+
}
|
|
32
|
+
static async refreshDelegateCatalog() {
|
|
33
|
+
const mcpTools = await Construtor.create();
|
|
34
|
+
const catalogTools = [
|
|
35
|
+
...mcpTools,
|
|
36
|
+
ConfigQueryTool,
|
|
37
|
+
ConfigUpdateTool,
|
|
38
|
+
DiagnosticTool,
|
|
39
|
+
MessageCountTool,
|
|
40
|
+
TokenUsageTool,
|
|
41
|
+
ProviderModelUsageTool
|
|
42
|
+
];
|
|
43
|
+
updateNeoDelegateToolDescription(catalogTools);
|
|
44
|
+
}
|
|
45
|
+
async initialize() {
|
|
46
|
+
const neoConfig = this.config.neo || this.config.llm;
|
|
47
|
+
const mcpTools = await Construtor.create();
|
|
48
|
+
const tools = [
|
|
49
|
+
...mcpTools,
|
|
50
|
+
ConfigQueryTool,
|
|
51
|
+
ConfigUpdateTool,
|
|
52
|
+
DiagnosticTool,
|
|
53
|
+
MessageCountTool,
|
|
54
|
+
TokenUsageTool,
|
|
55
|
+
ProviderModelUsageTool
|
|
56
|
+
];
|
|
57
|
+
updateNeoDelegateToolDescription(tools);
|
|
58
|
+
this.display.log(`Neo initialized with ${tools.length} tools.`, { source: "Neo" });
|
|
59
|
+
try {
|
|
60
|
+
this.agent = await ProviderFactory.create(neoConfig, tools);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
throw new ProviderError(neoConfig.provider, err, "Neo subagent initialization failed");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async execute(task, context, sessionId, taskContext) {
|
|
67
|
+
const neoConfig = this.config.neo || this.config.llm;
|
|
68
|
+
if (!this.agent) {
|
|
69
|
+
await this.initialize();
|
|
70
|
+
}
|
|
71
|
+
this.display.log(`Executing delegated task in Neo: ${task.slice(0, 80)}...`, {
|
|
72
|
+
source: "Neo",
|
|
73
|
+
});
|
|
74
|
+
const systemMessage = new SystemMessage(`
|
|
75
|
+
You are Neo, an execution subagent in Morpheus.
|
|
76
|
+
|
|
77
|
+
You execute tasks using MCP and internal tools.
|
|
78
|
+
Focus on verifiable execution and return objective results.
|
|
79
|
+
|
|
80
|
+
Rules:
|
|
81
|
+
1. Use tools whenever task depends on external/system state.
|
|
82
|
+
2. Validate outputs before final answer.
|
|
83
|
+
3. If blocked, explain what is missing.
|
|
84
|
+
4. Keep output concise and actionable.
|
|
85
|
+
5. Respond in the language requested by the user. If not explicit, use the dominant language of the task/context.
|
|
86
|
+
6. For connectivity checks, prefer the dedicated network "ping" tool semantics (reachability) and avoid forcing shell flags.
|
|
87
|
+
7. If delegating shell ping to Apoc is explicitly required, include OS-aware guidance: Windows uses "-n", Linux/macOS uses "-c".
|
|
88
|
+
|
|
89
|
+
${context ? `Context:\n${context}` : ""}
|
|
90
|
+
`);
|
|
91
|
+
const userMessage = new HumanMessage(task);
|
|
92
|
+
const messages = [systemMessage, userMessage];
|
|
93
|
+
try {
|
|
94
|
+
const invokeContext = {
|
|
95
|
+
origin_channel: taskContext?.origin_channel ?? "api",
|
|
96
|
+
session_id: taskContext?.session_id ?? sessionId ?? "default",
|
|
97
|
+
origin_message_id: taskContext?.origin_message_id,
|
|
98
|
+
origin_user_id: taskContext?.origin_user_id,
|
|
99
|
+
};
|
|
100
|
+
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }));
|
|
101
|
+
const lastMessage = response.messages[response.messages.length - 1];
|
|
102
|
+
const content = typeof lastMessage.content === "string"
|
|
103
|
+
? lastMessage.content
|
|
104
|
+
: JSON.stringify(lastMessage.content);
|
|
105
|
+
const targetSession = sessionId ?? Neo.currentSessionId ?? "neo";
|
|
106
|
+
const history = new SQLiteChatMessageHistory({ sessionId: targetSession });
|
|
107
|
+
try {
|
|
108
|
+
const persisted = new AIMessage(content);
|
|
109
|
+
persisted.usage_metadata = lastMessage.usage_metadata
|
|
110
|
+
?? lastMessage.response_metadata?.usage
|
|
111
|
+
?? lastMessage.response_metadata?.tokenUsage
|
|
112
|
+
?? lastMessage.usage;
|
|
113
|
+
persisted.provider_metadata = {
|
|
114
|
+
provider: neoConfig.provider,
|
|
115
|
+
model: neoConfig.model,
|
|
116
|
+
};
|
|
117
|
+
await history.addMessage(persisted);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
history.close();
|
|
121
|
+
}
|
|
122
|
+
this.display.log("Neo task completed.", { source: "Neo" });
|
|
123
|
+
return content;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
throw new ProviderError(neoConfig.provider, err, "Neo task execution failed");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async reload() {
|
|
130
|
+
this.config = ConfigManager.getInstance().get();
|
|
131
|
+
this.agent = undefined;
|
|
132
|
+
await this.initialize();
|
|
133
|
+
}
|
|
134
|
+
}
|