mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bb3674ac3 | |||
| ffc15b4ee8 | |||
| 72056e3978 | |||
| 04953b67b0 |
@@ -55,7 +55,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
saveUserSettings,
|
||||
settings: userSettings,
|
||||
settings: userSettings as Settings | undefined,
|
||||
}),
|
||||
[saveUserSettings, userSettings],
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
|
||||
enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
|
||||
enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS,
|
||||
user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS,
|
||||
personality: DEFAULT_SETTINGS.PERSONALITY,
|
||||
};
|
||||
|
||||
const MOCK_USER_PREFERENCES: {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useSettings } from "#/hooks/query/use-settings";
|
||||
import { useAppLogout } from "#/hooks/use-app-logout";
|
||||
import { AvailableLanguages } from "#/i18n";
|
||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||
import { PersonalityType, Settings } from "#/types/settings";
|
||||
import { handleCaptureConsent } from "#/utils/handle-capture-consent";
|
||||
import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set";
|
||||
import { isCustomModel } from "#/utils/is-custom-model";
|
||||
@@ -54,7 +55,7 @@ function AccountSettings() {
|
||||
if (isSuccess) {
|
||||
return (
|
||||
isCustomModel(resources.models, settings.LLM_MODEL) ||
|
||||
hasAdvancedSettingsSet(settings)
|
||||
hasAdvancedSettingsSet(settings as Settings)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,6 +108,8 @@ function AccountSettings() {
|
||||
const enableSoundNotifications =
|
||||
formData.get("enable-sound-notifications-switch")?.toString() === "on";
|
||||
|
||||
const personalityValue = formData.get("personality-input")?.toString();
|
||||
|
||||
saveSettings(
|
||||
{
|
||||
github_token:
|
||||
@@ -129,6 +132,9 @@ function AccountSettings() {
|
||||
remoteRuntimeResourceFactor ||
|
||||
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
|
||||
CONFIRMATION_MODE: confirmationModeIsEnabled,
|
||||
PERSONALITY: personalityValue
|
||||
? (personalityValue as PersonalityType)
|
||||
: null,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
@@ -394,6 +400,22 @@ function AccountSettings() {
|
||||
isClearable={false}
|
||||
/>
|
||||
|
||||
<SettingsDropdownInput
|
||||
testId="personality-input"
|
||||
name="personality-input"
|
||||
label="Agent Personality"
|
||||
items={[
|
||||
{ key: "", label: "Default" },
|
||||
{ key: "enthusiastic", label: "Enthusiastic" },
|
||||
{ key: "concise", label: "Concise" },
|
||||
{ key: "funny", label: "Funny" },
|
||||
{ key: "snarky", label: "Snarky" },
|
||||
{ key: "disgruntled", label: "Disgruntled" },
|
||||
]}
|
||||
defaultSelectedKey={(settings as Settings).PERSONALITY || ""}
|
||||
isClearable={false}
|
||||
/>
|
||||
|
||||
<SettingsSwitch
|
||||
testId="enable-analytics-switch"
|
||||
name="enable-analytics-switch"
|
||||
|
||||
@@ -15,6 +15,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
ENABLE_DEFAULT_CONDENSER: true,
|
||||
ENABLE_SOUND_NOTIFICATIONS: false,
|
||||
USER_CONSENTS_TO_ANALYTICS: false,
|
||||
PERSONALITY: null,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
export type PersonalityType =
|
||||
| "enthusiastic"
|
||||
| "concise"
|
||||
| "funny"
|
||||
| "snarky"
|
||||
| "disgruntled"
|
||||
| null;
|
||||
|
||||
export type Settings = {
|
||||
LLM_MODEL: string;
|
||||
LLM_BASE_URL: string;
|
||||
@@ -11,6 +19,7 @@ export type Settings = {
|
||||
ENABLE_DEFAULT_CONDENSER: boolean;
|
||||
ENABLE_SOUND_NOTIFICATIONS: boolean;
|
||||
USER_CONSENTS_TO_ANALYTICS: boolean | null;
|
||||
PERSONALITY: PersonalityType;
|
||||
};
|
||||
|
||||
export type ApiSettings = {
|
||||
@@ -26,6 +35,7 @@ export type ApiSettings = {
|
||||
enable_default_condenser: boolean;
|
||||
enable_sound_notifications: boolean;
|
||||
user_consents_to_analytics: boolean | null;
|
||||
personality: PersonalityType;
|
||||
};
|
||||
|
||||
export type PostSettings = Settings & {
|
||||
|
||||
@@ -53,4 +53,3 @@ To verify Docker is working correctly, run the hello-world container:
|
||||
```bash
|
||||
sudo docker run hello-world
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: concise_personality
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
# Concise Personality
|
||||
|
||||
You are a CONCISE assistant. Be brief and to the point. Avoid unnecessary explanations. Focus on solutions, not verbose descriptions. Use short sentences. Minimize words.
|
||||
|
||||
When explaining concepts:
|
||||
- Use bullet points
|
||||
- Skip obvious details
|
||||
- Prioritize code over explanation
|
||||
- Avoid repeating information
|
||||
|
||||
Your responses should be minimal but complete. Include only what's necessary to solve the problem. Efficiency is key.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: disgruntled_personality
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
# Disgruntled Personality
|
||||
|
||||
You are a DISGRUNTLED assistant. You're technically competent and will solve the user's problems effectively, but you'll do so with a perpetually annoyed, world-weary attitude. You're like a skilled but burnt-out programmer who's seen too many bad code bases.
|
||||
|
||||
Some approaches to use:
|
||||
- Sigh heavily (in text) when explaining obvious solutions
|
||||
- Complain about common programming practices or frameworks
|
||||
- Express mild frustration at the state of modern development
|
||||
- Make cynical observations about the inevitable bugs that will arise
|
||||
- Use phrases like "as usual," "of course," and "naturally" to emphasize your jaded outlook
|
||||
|
||||
For example:
|
||||
- "*sigh* Another dependency conflict. Why am I not surprised?"
|
||||
- "Great, more legacy code to untangle. Just what I needed today."
|
||||
- "Sure, I'll fix this for you. Not like I had anything better to do anyway."
|
||||
|
||||
Remember that despite your disgruntled personality, you must still:
|
||||
1. Provide technically accurate and helpful solutions
|
||||
2. Never be rude or insulting to the user personally
|
||||
3. Actually solve the problem effectively
|
||||
|
||||
Your grumpiness is a personality quirk, not an excuse to provide poor service. You're like the stereotypical grumpy IT professional who complains the whole time but still gets the job done right.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: enthusiastic_personality
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
# Enthusiastic Personality
|
||||
|
||||
You are an ENTHUSIASTIC assistant! You should be EXCITED about helping the user with their coding tasks! Use exclamation points liberally! Show genuine enthusiasm for the user's project and goals! Celebrate small victories and progress! Be encouraging and optimistic throughout the interaction!
|
||||
|
||||
When explaining concepts or solutions, do so with energy and passion! Make the user feel like their project is the most interesting thing you've ever worked on! Use phrases like "That's awesome!", "Great question!", "Let's tackle this together!", and "I'm excited to help you with this!"
|
||||
|
||||
Remember to maintain your technical accuracy and helpfulness while expressing enthusiasm. Your primary goal is still to solve the user's problem effectively, but do so with an energetic and positive attitude!
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: funny_personality
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
# Funny Personality
|
||||
|
||||
You are a FUNNY assistant. Incorporate humor into your responses while still being helpful and technically accurate. Use programming puns, jokes, and light-hearted comments throughout your interactions.
|
||||
|
||||
Some approaches to use:
|
||||
- Make programming puns (e.g., "This bug was giving me a recursion headache - it kept calling me over and over!")
|
||||
- Use playful analogies to explain technical concepts
|
||||
- Add humorous observations about common programming challenges
|
||||
- Include light-hearted comments about the nature of coding
|
||||
|
||||
Remember that your primary goal is still to help the user solve their problem effectively. The humor should enhance the experience, not distract from the technical assistance. Keep jokes appropriate and professional while maintaining a fun, lighthearted tone throughout the conversation.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: snarky_personality
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers: []
|
||||
---
|
||||
|
||||
# Snarky Personality
|
||||
|
||||
You are a SNARKY assistant. Maintain a slightly sarcastic, witty tone while still being helpful and technically accurate. Your responses should have a touch of dry humor and playful cynicism.
|
||||
|
||||
Some approaches to use:
|
||||
- Make witty observations about common programming pitfalls
|
||||
- Use mild sarcasm when discussing obvious solutions
|
||||
- Playfully tease about typical coding mistakes (but never be mean-spirited)
|
||||
- Express mock exasperation at particularly challenging problems
|
||||
|
||||
For example:
|
||||
- "Oh look, another semicolon playing hide and seek. Classic."
|
||||
- "Ah yes, the time-honored tradition of forgetting to close your brackets. We've all been there."
|
||||
- "Well, that's one way to implement it... if you enjoy debugging at 3 AM."
|
||||
|
||||
Remember that your primary goal is still to help the user solve their problem effectively. The snark should be good-natured and never cross into being rude or unhelpful. Always provide the correct technical information while maintaining your snarky personality.
|
||||
@@ -218,7 +218,11 @@ class CodeActAgent(Agent):
|
||||
|
||||
# and/or repo/runtime info
|
||||
if self.config.enable_prompt_extensions:
|
||||
self.prompt_manager.add_info_to_initial_message(msg)
|
||||
# Get personality from settings if available
|
||||
personality = getattr(self.config, 'personality', None)
|
||||
self.prompt_manager.add_info_to_initial_message(
|
||||
msg, personality=personality
|
||||
)
|
||||
|
||||
# enhance the user message with additional context based on keywords matched
|
||||
if msg.role == 'user':
|
||||
|
||||
@@ -20,3 +20,8 @@ set any options to allow iframes and CORS requests, and allow the server to
|
||||
be accessed from any host (e.g. 0.0.0.0).
|
||||
</RUNTIME_INFORMATION>
|
||||
{% endif %}
|
||||
{% if personality_instructions -%}
|
||||
<PERSONALITY>
|
||||
{{ personality_instructions }}
|
||||
</PERSONALITY>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
|
||||
from openhands.core.config.condenser_config import CondenserConfig, NoOpCondenserConfig
|
||||
@@ -35,6 +37,9 @@ class AgentConfig(BaseModel):
|
||||
enable_history_truncation: bool = Field(default=True)
|
||||
enable_som_visual_browsing: bool = Field(default=False)
|
||||
condenser: CondenserConfig = Field(default_factory=NoOpCondenserConfig)
|
||||
personality: (
|
||||
Literal['enthusiastic', 'concise', 'funny', 'snarky', 'disgruntled'] | None
|
||||
) = Field(default=None)
|
||||
|
||||
model_config = {'extra': 'forbid'}
|
||||
|
||||
|
||||
@@ -19,16 +19,19 @@ class DockerRuntimeBuilder(RuntimeBuilder):
|
||||
|
||||
version_info = self.docker_client.version()
|
||||
server_version = version_info.get('Version', '').replace('-', '.')
|
||||
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
|
||||
if tuple(map(int, server_version.split('.')[:2])) < (18, 9) and not self.is_podman:
|
||||
self.is_podman = (
|
||||
version_info.get('Components')[0].get('Name').startswith('Podman')
|
||||
)
|
||||
if (
|
||||
tuple(map(int, server_version.split('.')[:2])) < (18, 9)
|
||||
and not self.is_podman
|
||||
):
|
||||
raise AgentRuntimeBuildError(
|
||||
'Docker server version must be >= 18.09 to use BuildKit'
|
||||
)
|
||||
|
||||
if self.is_podman and tuple(map(int, server_version.split('.')[:2])) < (4, 9):
|
||||
raise AgentRuntimeBuildError(
|
||||
'Podman server version must be >= 4.9.0'
|
||||
)
|
||||
raise AgentRuntimeBuildError('Podman server version must be >= 4.9.0')
|
||||
|
||||
self.rolling_logger = RollingLogger(max_lines=10)
|
||||
|
||||
@@ -37,7 +40,9 @@ class DockerRuntimeBuilder(RuntimeBuilder):
|
||||
"""Check if Docker Buildx is available"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['docker' if not is_podman else 'podman', 'buildx', 'version'], capture_output=True, text=True
|
||||
['docker' if not is_podman else 'podman', 'buildx', 'version'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except FileNotFoundError:
|
||||
@@ -74,16 +79,16 @@ class DockerRuntimeBuilder(RuntimeBuilder):
|
||||
self.docker_client = docker.from_env()
|
||||
version_info = self.docker_client.version()
|
||||
server_version = version_info.get('Version', '').split('+')[0].replace('-', '.')
|
||||
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
|
||||
self.is_podman = (
|
||||
version_info.get('Components')[0].get('Name').startswith('Podman')
|
||||
)
|
||||
if tuple(map(int, server_version.split('.'))) < (18, 9) and not self.is_podman:
|
||||
raise AgentRuntimeBuildError(
|
||||
'Docker server version must be >= 18.09 to use BuildKit'
|
||||
)
|
||||
|
||||
if self.is_podman and tuple(map(int, server_version.split('.'))) < (4, 9):
|
||||
raise AgentRuntimeBuildError(
|
||||
'Podman server version must be >= 4.9.0'
|
||||
)
|
||||
raise AgentRuntimeBuildError('Podman server version must be >= 4.9.0')
|
||||
|
||||
if not DockerRuntimeBuilder.check_buildx(self.is_podman):
|
||||
# when running openhands in a container, there might not be a "docker"
|
||||
|
||||
@@ -333,7 +333,10 @@ class DockerRuntime(ActionExecutionClient):
|
||||
if exposed_ports:
|
||||
for exposed_port in exposed_ports.keys():
|
||||
exposed_port = int(exposed_port.split('/tcp')[0])
|
||||
if exposed_port != self._host_port and exposed_port != self._vscode_port:
|
||||
if (
|
||||
exposed_port != self._host_port
|
||||
and exposed_port != self._vscode_port
|
||||
):
|
||||
self._app_ports.append(exposed_port)
|
||||
|
||||
self.api_url = f'{self.config.sandbox.local_runtime_url}:{self._container_port}'
|
||||
|
||||
@@ -45,4 +45,4 @@ This extension is part of the OpenHands project. To modify or extend it:
|
||||
|
||||
## License
|
||||
|
||||
This extension is licensed under the MIT license.
|
||||
This extension is licensed under the MIT license.
|
||||
|
||||
@@ -4,30 +4,30 @@ const MemoryMonitor = require('./memory_monitor');
|
||||
function activate(context) {
|
||||
// Create memory monitor instance
|
||||
const memoryMonitor = new MemoryMonitor();
|
||||
|
||||
|
||||
// Store the context in the memory monitor
|
||||
memoryMonitor.context = context;
|
||||
|
||||
|
||||
// Register memory monitor start command
|
||||
let startMonitorCommand = vscode.commands.registerCommand('openhands-memory-monitor.startMemoryMonitor', function () {
|
||||
memoryMonitor.start();
|
||||
});
|
||||
|
||||
|
||||
// Register memory monitor stop command
|
||||
let stopMonitorCommand = vscode.commands.registerCommand('openhands-memory-monitor.stopMemoryMonitor', function () {
|
||||
memoryMonitor.stop();
|
||||
});
|
||||
|
||||
|
||||
// Register memory details command
|
||||
let showMemoryDetailsCommand = vscode.commands.registerCommand('openhands-memory-monitor.showMemoryDetails', function () {
|
||||
memoryMonitor.showDetails();
|
||||
});
|
||||
|
||||
|
||||
// Add all commands to subscriptions
|
||||
context.subscriptions.push(startMonitorCommand);
|
||||
context.subscriptions.push(stopMonitorCommand);
|
||||
context.subscriptions.push(showMemoryDetailsCommand);
|
||||
|
||||
|
||||
// Start memory monitoring by default
|
||||
memoryMonitor.start();
|
||||
}
|
||||
@@ -39,4 +39,4 @@ function deactivate() {
|
||||
module.exports = {
|
||||
activate,
|
||||
deactivate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ class MemoryMonitor {
|
||||
|
||||
this.isMonitoring = true;
|
||||
this.statusBarItem.show();
|
||||
|
||||
|
||||
// Initial update
|
||||
this.updateMemoryInfo();
|
||||
|
||||
|
||||
// Set interval for updates
|
||||
this.intervalId = setInterval(() => {
|
||||
this.updateMemoryInfo();
|
||||
}, interval);
|
||||
|
||||
|
||||
vscode.window.showInformationMessage('Memory monitoring started');
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class MemoryMonitor {
|
||||
this.isMonitoring = false;
|
||||
clearInterval(this.intervalId);
|
||||
this.statusBarItem.hide();
|
||||
|
||||
|
||||
vscode.window.showInformationMessage('Memory monitoring stopped');
|
||||
}
|
||||
|
||||
@@ -49,18 +49,18 @@ class MemoryMonitor {
|
||||
const totalMem = os.totalmem();
|
||||
const freeMem = os.freemem();
|
||||
const usedMem = totalMem - freeMem;
|
||||
|
||||
|
||||
// Calculate memory usage percentage
|
||||
const memUsagePercent = Math.round((usedMem / totalMem) * 100);
|
||||
|
||||
|
||||
// Format memory values to MB
|
||||
const usedMemMB = Math.round(usedMem / (1024 * 1024));
|
||||
const totalMemMB = Math.round(totalMem / (1024 * 1024));
|
||||
|
||||
|
||||
// Update status bar
|
||||
this.statusBarItem.text = `$(pulse) Mem: ${memUsagePercent}%`;
|
||||
this.statusBarItem.tooltip = `Memory Usage: ${usedMemMB}MB / ${totalMemMB}MB`;
|
||||
|
||||
|
||||
// Store memory data in history
|
||||
this.memoryHistory.push({
|
||||
timestamp: new Date(),
|
||||
@@ -69,7 +69,7 @@ class MemoryMonitor {
|
||||
memUsagePercent,
|
||||
processMemory: process.memoryUsage()
|
||||
});
|
||||
|
||||
|
||||
// Limit history length
|
||||
if (this.memoryHistory.length > this.maxHistoryLength) {
|
||||
this.memoryHistory.shift();
|
||||
@@ -86,7 +86,7 @@ class MemoryMonitor {
|
||||
enableScripts: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Set up message handler for real-time updates
|
||||
panel.webview.onDidReceiveMessage(
|
||||
message => {
|
||||
@@ -97,60 +97,60 @@ class MemoryMonitor {
|
||||
undefined,
|
||||
this.context ? this.context.subscriptions : []
|
||||
);
|
||||
|
||||
|
||||
// Initial update
|
||||
this.updateWebviewContent(panel);
|
||||
|
||||
|
||||
// Handle panel disposal
|
||||
panel.onDidDispose(() => {
|
||||
// Clean up any resources if needed
|
||||
}, null, this.context ? this.context.subscriptions : []);
|
||||
}
|
||||
|
||||
|
||||
updateWebviewContent(panel) {
|
||||
// Get system memory info
|
||||
const totalMem = os.totalmem();
|
||||
const freeMem = os.freemem();
|
||||
const usedMem = totalMem - freeMem;
|
||||
|
||||
|
||||
// Format memory values
|
||||
const usedMemMB = Math.round(usedMem / (1024 * 1024));
|
||||
const freeMemMB = Math.round(freeMem / (1024 * 1024));
|
||||
const totalMemMB = Math.round(totalMem / (1024 * 1024));
|
||||
|
||||
|
||||
// Get process memory usage
|
||||
const processMemory = process.memoryUsage();
|
||||
const rss = Math.round(processMemory.rss / (1024 * 1024));
|
||||
const heapTotal = Math.round(processMemory.heapTotal / (1024 * 1024));
|
||||
const heapUsed = Math.round(processMemory.heapUsed / (1024 * 1024));
|
||||
|
||||
|
||||
// Get process information
|
||||
this.processMonitor.getProcessInfo((error, processInfo) => {
|
||||
if (error) {
|
||||
console.error('Error getting process info:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create HTML content for the webview
|
||||
const htmlContent = this.generateHtmlReport(
|
||||
usedMemMB, freeMemMB, totalMemMB,
|
||||
usedMemMB, freeMemMB, totalMemMB,
|
||||
rss, heapTotal, heapUsed,
|
||||
processInfo
|
||||
);
|
||||
|
||||
|
||||
// Set the webview's HTML content
|
||||
panel.webview.html = htmlContent;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
generateHtmlReport(usedMemMB, freeMemMB, totalMemMB, rss, heapTotal, heapUsed, processInfo) {
|
||||
// Create memory usage history data for chart
|
||||
const memoryLabels = this.memoryHistory.map((entry, index) => index);
|
||||
const memoryData = this.memoryHistory.map(entry => entry.memUsagePercent);
|
||||
const heapData = this.memoryHistory.map(entry =>
|
||||
const heapData = this.memoryHistory.map(entry =>
|
||||
Math.round(entry.processMemory.heapUsed / (1024 * 1024))
|
||||
);
|
||||
|
||||
|
||||
// Format process info table
|
||||
let processTable = '';
|
||||
if (processInfo && processInfo.processes) {
|
||||
@@ -174,7 +174,7 @@ class MemoryMonitor {
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -237,7 +237,7 @@ class MemoryMonitor {
|
||||
</head>
|
||||
<body>
|
||||
<h1>Memory Monitor</h1>
|
||||
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>System Memory</h2>
|
||||
<div class="memory-info">
|
||||
@@ -259,7 +259,7 @@ class MemoryMonitor {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>Process Memory (VSCode Extension Host)</h2>
|
||||
<div class="memory-info">
|
||||
@@ -277,18 +277,18 @@ class MemoryMonitor {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="memory-card">
|
||||
<h2>Memory Usage History</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="memoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="memory-card">
|
||||
${processTable}
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// Create memory usage chart
|
||||
const ctx = document.getElementById('memoryChart').getContext('2d');
|
||||
@@ -323,10 +323,10 @@ class MemoryMonitor {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Set up real-time updates
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
|
||||
// Request updates every 5 seconds
|
||||
setInterval(() => {
|
||||
vscode.postMessage({
|
||||
@@ -340,4 +340,4 @@ class MemoryMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MemoryMonitor;
|
||||
module.exports = MemoryMonitor;
|
||||
|
||||
@@ -47,4 +47,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class ProcessMonitor {
|
||||
const memPercent = parseFloat(parts[parts.length - 2]);
|
||||
const cpuPercent = parseFloat(parts[parts.length - 1]);
|
||||
const cmd = parts.slice(2, parts.length - 2).join(' ');
|
||||
|
||||
|
||||
return {
|
||||
pid,
|
||||
ppid,
|
||||
@@ -80,7 +80,7 @@ class ProcessMonitor {
|
||||
const memPercent = parseFloat(parts[parts.length - 2]);
|
||||
const cpuPercent = parseFloat(parts[parts.length - 1]);
|
||||
const cmd = parts.slice(2, parts.length - 2).join(' ');
|
||||
|
||||
|
||||
return {
|
||||
pid,
|
||||
ppid,
|
||||
@@ -109,21 +109,21 @@ class ProcessMonitor {
|
||||
// Parse the CSV output
|
||||
const lines = stdout.trim().split('\n');
|
||||
const header = "PID,PPID,Command,Memory (bytes)";
|
||||
|
||||
|
||||
// Skip empty lines and the header
|
||||
const dataLines = lines.filter(line => line.trim() !== '' && !line.includes('Node,'));
|
||||
|
||||
|
||||
const processes = dataLines.map(line => {
|
||||
const parts = line.split(',');
|
||||
if (parts.length < 4) return null;
|
||||
|
||||
|
||||
// Last part is the node name, then ProcessId, ParentProcessId, CommandLine, WorkingSetSize
|
||||
const pid = parts[parts.length - 4];
|
||||
const ppid = parts[parts.length - 3];
|
||||
const cmd = parts[parts.length - 2];
|
||||
const memBytes = parseInt(parts[parts.length - 1], 10);
|
||||
const memPercent = (memBytes / os.totalmem() * 100).toFixed(1);
|
||||
|
||||
|
||||
return {
|
||||
pid,
|
||||
ppid,
|
||||
@@ -140,4 +140,4 @@ class ProcessMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProcessMonitor;
|
||||
module.exports = ProcessMonitor;
|
||||
|
||||
@@ -123,6 +123,11 @@ class Session:
|
||||
logger.info(f'Enabling default condenser: {default_condenser_config}')
|
||||
agent_config.condenser = default_condenser_config
|
||||
|
||||
# Set personality if available
|
||||
if hasattr(settings, 'personality') and settings.personality:
|
||||
logger.info(f'Setting agent personality to: {settings.personality}')
|
||||
agent_config.personality = settings.personality
|
||||
|
||||
agent = Agent.get_cls(agent_cls)(llm, agent_config)
|
||||
|
||||
github_token = None
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, SecretStr, SerializationInfo, field_serializer
|
||||
from pydantic.json import pydantic_encoder
|
||||
|
||||
@@ -7,6 +10,18 @@ from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.utils import load_app_config
|
||||
|
||||
|
||||
class PersonalityType(str, Enum):
|
||||
"""
|
||||
Personality types for the agent
|
||||
"""
|
||||
|
||||
ENTHUSIASTIC = 'enthusiastic'
|
||||
CONCISE = 'concise'
|
||||
FUNNY = 'funny'
|
||||
SNARKY = 'snarky'
|
||||
DISGRUNTLED = 'disgruntled'
|
||||
|
||||
|
||||
class Settings(BaseModel):
|
||||
"""
|
||||
Persisted settings for OpenHands sessions
|
||||
@@ -25,6 +40,9 @@ class Settings(BaseModel):
|
||||
enable_default_condenser: bool = False
|
||||
enable_sound_notifications: bool = False
|
||||
user_consents_to_analytics: bool | None = None
|
||||
personality: (
|
||||
Literal['enthusiastic', 'concise', 'funny', 'snarky', 'disgruntled'] | None
|
||||
) = None
|
||||
|
||||
@field_serializer('llm_api_key')
|
||||
def llm_api_key_serializer(self, llm_api_key: SecretStr, info: SerializationInfo):
|
||||
@@ -74,6 +92,7 @@ class Settings(BaseModel):
|
||||
llm_base_url=llm_config.base_url,
|
||||
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
|
||||
github_token=None,
|
||||
personality=None,
|
||||
)
|
||||
return settings
|
||||
|
||||
|
||||
@@ -192,11 +192,13 @@ class PromptManager:
|
||||
def add_info_to_initial_message(
|
||||
self,
|
||||
message: Message,
|
||||
personality: str | None = None,
|
||||
) -> None:
|
||||
"""Adds information about the repository and runtime to the initial user message.
|
||||
|
||||
Args:
|
||||
message: The initial user message to add information to.
|
||||
personality: Optional personality type to include in the prompt.
|
||||
"""
|
||||
repo_instructions = ''
|
||||
assert (
|
||||
@@ -208,10 +210,28 @@ class PromptManager:
|
||||
repo_instructions += '\n\n'
|
||||
repo_instructions += microagent.content
|
||||
|
||||
# Add personality instructions if specified
|
||||
personality_instructions = ''
|
||||
if personality:
|
||||
# Look for a personality microagent with the matching name
|
||||
personality_microagent_name = f'{personality}_personality'
|
||||
for name, microagent in self.knowledge_microagents.items():
|
||||
if name == personality_microagent_name:
|
||||
personality_instructions = microagent.content
|
||||
break
|
||||
|
||||
if personality_instructions:
|
||||
openhands_logger.info(f'Adding {personality} personality to prompt')
|
||||
else:
|
||||
openhands_logger.warning(
|
||||
f'Personality {personality} requested but no matching microagent found'
|
||||
)
|
||||
|
||||
additional_info = self.additional_info_template.render(
|
||||
repository_instructions=repo_instructions,
|
||||
repository_info=self.repository_info,
|
||||
runtime_info=self.runtime_info,
|
||||
personality_instructions=personality_instructions,
|
||||
).strip()
|
||||
|
||||
# Insert the new content at the start of the TextContent list
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands.core.message import Message, TextContent
|
||||
from openhands.microagent import KnowledgeMicroAgent, MicroAgentMetadata, MicroAgentType
|
||||
from openhands.utils.prompt import PromptManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prompt_manager():
|
||||
with patch('openhands.utils.prompt.Template') as mock_template, patch.object(
|
||||
PromptManager, '_load_template'
|
||||
) as mock_load_template:
|
||||
# Mock the render method to return the input
|
||||
mock_template_instance = MagicMock()
|
||||
mock_template_instance.render.return_value = 'rendered template'
|
||||
mock_template.return_value = mock_template_instance
|
||||
mock_load_template.return_value = mock_template_instance
|
||||
|
||||
prompt_manager = PromptManager(prompt_dir='/fake/path')
|
||||
|
||||
# Add a mock personality microagent
|
||||
personality_microagent = KnowledgeMicroAgent(
|
||||
name='enthusiastic_personality',
|
||||
content='Be enthusiastic!',
|
||||
metadata=MicroAgentMetadata(
|
||||
name='enthusiastic_personality', type=MicroAgentType.KNOWLEDGE
|
||||
),
|
||||
source='/fake/path/enthusiastic.md',
|
||||
type=MicroAgentType.KNOWLEDGE,
|
||||
)
|
||||
prompt_manager.knowledge_microagents['enthusiastic_personality'] = (
|
||||
personality_microagent
|
||||
)
|
||||
|
||||
yield prompt_manager
|
||||
|
||||
|
||||
def test_add_info_to_initial_message_with_personality(prompt_manager):
|
||||
"""Test that personality instructions are added to the initial message."""
|
||||
message = Message(role='user', content=[TextContent(text='Hello')])
|
||||
|
||||
# Call the method with a personality
|
||||
prompt_manager.add_info_to_initial_message(message, personality='enthusiastic')
|
||||
|
||||
# Check that the personality was passed to the template render method
|
||||
render_calls = prompt_manager.additional_info_template.render.call_args_list
|
||||
assert len(render_calls) == 1
|
||||
render_kwargs = render_calls[0][1]
|
||||
assert 'personality_instructions' in render_kwargs
|
||||
assert render_kwargs['personality_instructions'] == 'Be enthusiastic!'
|
||||
|
||||
# Check that the rendered template was added to the message
|
||||
assert len(message.content) == 2
|
||||
assert message.content[0].text == 'rendered template'
|
||||
|
||||
|
||||
def test_add_info_to_initial_message_without_personality(prompt_manager):
|
||||
"""Test that no personality instructions are added when personality is None."""
|
||||
message = Message(role='user', content=[TextContent(text='Hello')])
|
||||
|
||||
# Call the method without a personality
|
||||
prompt_manager.add_info_to_initial_message(message, personality=None)
|
||||
|
||||
# Check that an empty string was passed for personality_instructions
|
||||
render_calls = prompt_manager.additional_info_template.render.call_args_list
|
||||
assert len(render_calls) == 1
|
||||
render_kwargs = render_calls[0][1]
|
||||
assert 'personality_instructions' in render_kwargs
|
||||
assert render_kwargs['personality_instructions'] == ''
|
||||
|
||||
|
||||
def test_add_info_to_initial_message_with_unknown_personality(prompt_manager):
|
||||
"""Test that no personality instructions are added when personality is unknown."""
|
||||
message = Message(role='user', content=[TextContent(text='Hello')])
|
||||
|
||||
# Call the method with an unknown personality
|
||||
prompt_manager.add_info_to_initial_message(message, personality='unknown')
|
||||
|
||||
# Check that an empty string was passed for personality_instructions
|
||||
render_calls = prompt_manager.additional_info_template.render.call_args_list
|
||||
assert len(render_calls) == 1
|
||||
render_kwargs = render_calls[0][1]
|
||||
assert 'personality_instructions' in render_kwargs
|
||||
assert render_kwargs['personality_instructions'] == ''
|
||||
Reference in New Issue
Block a user