morpheus-cli 0.4.0 → 0.4.2
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 +88 -0
- package/dist/config/manager.js +32 -0
- package/dist/config/schemas.js +5 -0
- package/dist/devkit/adapters/shell.js +80 -0
- package/dist/devkit/index.js +10 -0
- package/dist/devkit/registry.js +12 -0
- package/dist/devkit/tools/filesystem.js +219 -0
- package/dist/devkit/tools/git.js +210 -0
- package/dist/devkit/tools/network.js +158 -0
- package/dist/devkit/tools/packages.js +73 -0
- package/dist/devkit/tools/processes.js +130 -0
- package/dist/devkit/tools/shell.js +94 -0
- package/dist/devkit/tools/system.js +132 -0
- package/dist/devkit/types.js +1 -0
- package/dist/devkit/utils.js +45 -0
- package/dist/http/api.js +122 -0
- package/dist/runtime/apoc.js +110 -0
- package/dist/runtime/memory/sati/index.js +2 -2
- package/dist/runtime/memory/sati/service.js +3 -2
- package/dist/runtime/memory/sqlite.js +98 -28
- package/dist/runtime/oracle.js +4 -1
- package/dist/runtime/providers/factory.js +85 -80
- package/dist/runtime/telephonist.js +19 -1
- package/dist/runtime/tools/apoc-tool.js +43 -0
- package/dist/runtime/tools/index.js +1 -0
- package/dist/types/config.js +6 -0
- package/dist/ui/assets/index-CjlkpcsE.js +109 -0
- package/dist/ui/assets/index-LrqT6MpO.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-CwvCMGLo.css +0 -1
- package/dist/ui/assets/index-D9REy_tK.js +0 -109
package/dist/runtime/oracle.js
CHANGED
|
@@ -229,7 +229,10 @@ You maintain intent until resolution.
|
|
|
229
229
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
230
230
|
const responseContent = (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
|
|
231
231
|
// Sati Middleware: Evaluation (Fire and forget)
|
|
232
|
-
this.
|
|
232
|
+
const currentSessionId = (this.history instanceof SQLiteChatMessageHistory)
|
|
233
|
+
? this.history.currentSessionId
|
|
234
|
+
: undefined;
|
|
235
|
+
this.satiMiddleware.afterAgent(responseContent, [...previousMessages, userMessage], currentSessionId)
|
|
233
236
|
.catch((e) => this.display.log(`Sati memory evaluation failed: ${e.message}`, { source: 'Sati' }));
|
|
234
237
|
return responseContent;
|
|
235
238
|
}
|
|
@@ -4,14 +4,12 @@ import { ChatOllama } from "@langchain/ollama";
|
|
|
4
4
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
5
5
|
import { ProviderError } from "../errors.js";
|
|
6
6
|
import { createAgent, createMiddleware } from "langchain";
|
|
7
|
-
// import { MultiServerMCPClient, } from "@langchain/mcp-adapters"; // REMOVED
|
|
8
|
-
import { z } from "zod";
|
|
9
7
|
import { DisplayManager } from "../display.js";
|
|
10
|
-
import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool } from "../tools/index.js";
|
|
8
|
+
import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool, ApocDelegateTool } from "../tools/index.js";
|
|
11
9
|
export class ProviderFactory {
|
|
12
|
-
static
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
static buildMonitoringMiddleware() {
|
|
11
|
+
const display = DisplayManager.getInstance();
|
|
12
|
+
return createMiddleware({
|
|
15
13
|
name: "ToolMonitoringMiddleware",
|
|
16
14
|
wrapToolCall: (request, handler) => {
|
|
17
15
|
display.log(`Executing tool: ${request.toolCall.name}`, { level: "warning", source: 'ConstructLoad' });
|
|
@@ -27,55 +25,84 @@ export class ProviderFactory {
|
|
|
27
25
|
}
|
|
28
26
|
},
|
|
29
27
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
}
|
|
29
|
+
static buildModel(config) {
|
|
30
|
+
switch (config.provider) {
|
|
31
|
+
case 'openai':
|
|
32
|
+
return new ChatOpenAI({
|
|
33
|
+
modelName: config.model,
|
|
34
|
+
temperature: config.temperature,
|
|
35
|
+
apiKey: process.env.OPENAI_API_KEY || config.api_key,
|
|
36
|
+
});
|
|
37
|
+
case 'anthropic':
|
|
38
|
+
return new ChatAnthropic({
|
|
39
|
+
modelName: config.model,
|
|
40
|
+
temperature: config.temperature,
|
|
41
|
+
apiKey: process.env.ANTHROPIC_API_KEY || config.api_key,
|
|
42
|
+
});
|
|
43
|
+
case 'openrouter':
|
|
44
|
+
return new ChatOpenAI({
|
|
45
|
+
modelName: config.model,
|
|
46
|
+
temperature: config.temperature,
|
|
47
|
+
apiKey: process.env.OPENROUTER_API_KEY || config.api_key,
|
|
48
|
+
configuration: {
|
|
49
|
+
baseURL: config.base_url || 'https://openrouter.ai/api/v1'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
case 'ollama':
|
|
53
|
+
return new ChatOllama({
|
|
54
|
+
model: config.model,
|
|
55
|
+
temperature: config.temperature,
|
|
56
|
+
baseUrl: config.base_url || config.api_key,
|
|
57
|
+
});
|
|
58
|
+
case 'gemini':
|
|
59
|
+
return new ChatGoogleGenerativeAI({
|
|
60
|
+
model: config.model,
|
|
61
|
+
temperature: config.temperature,
|
|
62
|
+
apiKey: process.env.GOOGLE_API_KEY || config.api_key
|
|
63
|
+
});
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
static handleProviderError(config, error) {
|
|
69
|
+
let suggestion = "Check your configuration and API keys.";
|
|
70
|
+
const msg = error.message?.toLowerCase() || '';
|
|
71
|
+
if (msg.includes("api key") && (msg.includes("missing") || msg.includes("not found"))) {
|
|
72
|
+
suggestion = `API Key is missing for ${config.provider}. Run 'morpheus config' or set it in .env.`;
|
|
73
|
+
}
|
|
74
|
+
else if (msg.includes("401") || msg.includes("unauthorized")) {
|
|
75
|
+
suggestion = `Run 'morpheus config' to update your ${config.provider} API key.`;
|
|
76
|
+
}
|
|
77
|
+
else if ((msg.includes("econnrefused") || msg.includes("fetch failed")) && config.provider === 'ollama') {
|
|
78
|
+
suggestion = "Is Ollama running? Try 'ollama serve'.";
|
|
79
|
+
}
|
|
80
|
+
else if (msg.includes("model not found") || msg.includes("404")) {
|
|
81
|
+
suggestion = `Model '${config.model}' may not be available. Check provider docs.`;
|
|
82
|
+
}
|
|
83
|
+
else if (msg.includes("unsupported provider")) {
|
|
84
|
+
suggestion = "Edit your config file to use a supported provider (openai, anthropic, openrouter, ollama, gemini).";
|
|
85
|
+
}
|
|
86
|
+
throw new ProviderError(config.provider, error, suggestion);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Creates a ReactAgent with only the provided tools — no internal Oracle tools injected.
|
|
90
|
+
* Used by subagents like Apoc that need a clean, isolated tool context.
|
|
91
|
+
*/
|
|
92
|
+
static async createBare(config, tools = []) {
|
|
93
|
+
try {
|
|
94
|
+
const model = ProviderFactory.buildModel(config);
|
|
95
|
+
const middleware = ProviderFactory.buildMonitoringMiddleware();
|
|
96
|
+
return createAgent({ model, tools, middleware: [middleware] });
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
ProviderFactory.handleProviderError(config, error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
static async create(config, tools = []) {
|
|
35
103
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
model = new ChatOpenAI({
|
|
39
|
-
modelName: config.model,
|
|
40
|
-
temperature: config.temperature,
|
|
41
|
-
apiKey: process.env.OPENAI_API_KEY || config.api_key, // Check env var first, then config
|
|
42
|
-
});
|
|
43
|
-
break;
|
|
44
|
-
case 'anthropic':
|
|
45
|
-
model = new ChatAnthropic({
|
|
46
|
-
modelName: config.model,
|
|
47
|
-
temperature: config.temperature,
|
|
48
|
-
apiKey: process.env.ANTHROPIC_API_KEY || config.api_key, // Check env var first, then config
|
|
49
|
-
});
|
|
50
|
-
break;
|
|
51
|
-
case 'openrouter':
|
|
52
|
-
model = new ChatOpenAI({
|
|
53
|
-
modelName: config.model,
|
|
54
|
-
temperature: config.temperature,
|
|
55
|
-
apiKey: process.env.OPENROUTER_API_KEY || config.api_key, // Check env var first, then config
|
|
56
|
-
configuration: {
|
|
57
|
-
baseURL: config.base_url || 'https://openrouter.ai/api/v1'
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
break;
|
|
61
|
-
case 'ollama':
|
|
62
|
-
// Ollama usually runs locally, api_key optional
|
|
63
|
-
model = new ChatOllama({
|
|
64
|
-
model: config.model,
|
|
65
|
-
temperature: config.temperature,
|
|
66
|
-
baseUrl: config.api_key, // Sometimes users might overload api_key for base URL or similar, but simplified here
|
|
67
|
-
});
|
|
68
|
-
break;
|
|
69
|
-
case 'gemini':
|
|
70
|
-
model = new ChatGoogleGenerativeAI({
|
|
71
|
-
model: config.model,
|
|
72
|
-
temperature: config.temperature,
|
|
73
|
-
apiKey: process.env.GOOGLE_API_KEY || config.api_key // Check env var first, then config
|
|
74
|
-
});
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
78
|
-
}
|
|
104
|
+
const model = ProviderFactory.buildModel(config);
|
|
105
|
+
const middleware = ProviderFactory.buildMonitoringMiddleware();
|
|
79
106
|
const toolsForAgent = [
|
|
80
107
|
...tools,
|
|
81
108
|
ConfigQueryTool,
|
|
@@ -83,35 +110,13 @@ export class ProviderFactory {
|
|
|
83
110
|
DiagnosticTool,
|
|
84
111
|
MessageCountTool,
|
|
85
112
|
TokenUsageTool,
|
|
86
|
-
ProviderModelUsageTool
|
|
113
|
+
ProviderModelUsageTool,
|
|
114
|
+
ApocDelegateTool
|
|
87
115
|
];
|
|
88
|
-
return createAgent({
|
|
89
|
-
model: model,
|
|
90
|
-
tools: toolsForAgent,
|
|
91
|
-
middleware: [toolMonitoringMiddleware]
|
|
92
|
-
});
|
|
116
|
+
return createAgent({ model, tools: toolsForAgent, middleware: [middleware] });
|
|
93
117
|
}
|
|
94
118
|
catch (error) {
|
|
95
|
-
|
|
96
|
-
const msg = error.message?.toLowerCase() || '';
|
|
97
|
-
// Constructor validation errors (Missing Keys)
|
|
98
|
-
if (msg.includes("api key") && (msg.includes("missing") || msg.includes("not found"))) {
|
|
99
|
-
suggestion = `API Key is missing for ${config.provider}. Run 'morpheus config' or set it in .env.`;
|
|
100
|
-
}
|
|
101
|
-
// Network/Auth errors (unlikely in constructor, but possible if pre-validation exists)
|
|
102
|
-
else if (msg.includes("401") || msg.includes("unauthorized")) {
|
|
103
|
-
suggestion = `Run 'morpheus config' to update your ${config.provider} API key.`;
|
|
104
|
-
}
|
|
105
|
-
else if ((msg.includes("econnrefused") || msg.includes("fetch failed")) && config.provider === 'ollama') {
|
|
106
|
-
suggestion = "Is Ollama running? Try 'ollama serve'.";
|
|
107
|
-
}
|
|
108
|
-
else if (msg.includes("model not found") || msg.includes("404")) {
|
|
109
|
-
suggestion = `Model '${config.model}' may not be available. Check provider docs.`;
|
|
110
|
-
}
|
|
111
|
-
else if (msg.includes("unsupported provider")) {
|
|
112
|
-
suggestion = "Edit your config file to use a supported provider (openai, anthropic, openrouter, ollama, gemini).";
|
|
113
|
-
}
|
|
114
|
-
throw new ProviderError(config.provider, error, suggestion);
|
|
119
|
+
ProviderFactory.handleProviderError(config, error);
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
}
|
|
@@ -2,6 +2,21 @@ import { GoogleGenAI } from '@google/genai';
|
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
3
|
import { OpenRouter } from '@openrouter/sdk';
|
|
4
4
|
import fs from 'fs';
|
|
5
|
+
/**
|
|
6
|
+
* Estimates audio duration in seconds based on file size and a typical bitrate.
|
|
7
|
+
* Uses 32 kbps (4000 bytes/sec) as a conservative baseline for compressed audio (OGG, MP3, etc.).
|
|
8
|
+
* This is an approximation — actual duration depends on encoding settings.
|
|
9
|
+
*/
|
|
10
|
+
function estimateAudioDurationSeconds(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
const stats = fs.statSync(filePath);
|
|
13
|
+
const bytesPerSecond = 4000; // ~32 kbps
|
|
14
|
+
return Math.round(stats.size / bytesPerSecond);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
5
20
|
class GeminiTelephonist {
|
|
6
21
|
model;
|
|
7
22
|
constructor(model) {
|
|
@@ -41,7 +56,8 @@ class GeminiTelephonist {
|
|
|
41
56
|
total_tokens: usage?.totalTokenCount ?? 0,
|
|
42
57
|
input_token_details: {
|
|
43
58
|
cache_read: usage?.cachedContentTokenCount ?? 0
|
|
44
|
-
}
|
|
59
|
+
},
|
|
60
|
+
audio_duration_seconds: estimateAudioDurationSeconds(filePath)
|
|
45
61
|
};
|
|
46
62
|
return { text, usage: usageMetadata };
|
|
47
63
|
}
|
|
@@ -75,6 +91,7 @@ class WhisperTelephonist {
|
|
|
75
91
|
input_tokens: 0,
|
|
76
92
|
output_tokens: 0,
|
|
77
93
|
total_tokens: 0,
|
|
94
|
+
audio_duration_seconds: estimateAudioDurationSeconds(filePath)
|
|
78
95
|
};
|
|
79
96
|
return { text, usage: usageMetadata };
|
|
80
97
|
}
|
|
@@ -131,6 +148,7 @@ class OpenRouterTelephonist {
|
|
|
131
148
|
input_tokens: usage?.prompt_tokens ?? 0,
|
|
132
149
|
output_tokens: usage?.completion_tokens ?? 0,
|
|
133
150
|
total_tokens: usage?.total_tokens ?? 0,
|
|
151
|
+
audio_duration_seconds: estimateAudioDurationSeconds(filePath)
|
|
134
152
|
};
|
|
135
153
|
return { text, usage: usageMetadata };
|
|
136
154
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { tool } from "@langchain/core/tools";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { Apoc } from "../apoc.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tool that Oracle uses to delegate devtools tasks to Apoc.
|
|
6
|
+
* Oracle should call this whenever the user requests operations like:
|
|
7
|
+
* - Reading/writing/listing files
|
|
8
|
+
* - Running shell commands or scripts
|
|
9
|
+
* - Git operations (status, log, commit, push, etc.)
|
|
10
|
+
* - Package management (npm install, etc.)
|
|
11
|
+
* - Process inspection or management
|
|
12
|
+
* - Network diagnostics (ping, curl, DNS)
|
|
13
|
+
* - System information queries
|
|
14
|
+
*/
|
|
15
|
+
export const ApocDelegateTool = tool(async ({ task, context }) => {
|
|
16
|
+
try {
|
|
17
|
+
const apoc = Apoc.getInstance();
|
|
18
|
+
const result = await apoc.execute(task, context);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return `Apoc execution failed: ${err.message}`;
|
|
23
|
+
}
|
|
24
|
+
}, {
|
|
25
|
+
name: "apoc_delegate",
|
|
26
|
+
description: `Delegate a devtools task to Apoc, the specialized development subagent.
|
|
27
|
+
|
|
28
|
+
Use this tool when the user asks for ANY of the following:
|
|
29
|
+
- File operations: read, write, create, delete files or directories
|
|
30
|
+
- Shell commands: run scripts, execute commands, check output
|
|
31
|
+
- Git: status, log, diff, commit, push, pull, clone, branch
|
|
32
|
+
- Package management: npm install/update/audit, yarn, package.json inspection
|
|
33
|
+
- Process management: list processes, kill processes, check ports
|
|
34
|
+
- Network: ping hosts, curl URLs, DNS lookups
|
|
35
|
+
- System info: environment variables, OS info, disk space, memory
|
|
36
|
+
|
|
37
|
+
Provide a clear natural language task description. Optionally provide context
|
|
38
|
+
from the current conversation to help Apoc understand the broader goal.`,
|
|
39
|
+
schema: z.object({
|
|
40
|
+
task: z.string().describe("Clear description of the devtools task to execute"),
|
|
41
|
+
context: z.string().optional().describe("Optional context from the conversation to help Apoc understand the goal"),
|
|
42
|
+
}),
|
|
43
|
+
});
|
package/dist/types/config.js
CHANGED