openbot 0.1.26 → 0.1.28
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/dist/agents/os-agent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
2
|
import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
|
|
3
3
|
import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
4
|
+
import approvalPlugin from "../plugins/approval/index.js";
|
|
4
5
|
const DEFAULT_SYSTEM_PROMPT = `You are an OS Agent with access to the shell and file system.
|
|
5
6
|
Your job is to help the user with file operations and command execution.
|
|
6
7
|
You can read, write, list, and delete files, as well as execute shell commands.
|
|
@@ -11,12 +12,19 @@ export const osAgent = (options) => (builder) => {
|
|
|
11
12
|
builder
|
|
12
13
|
.use(shellPlugin({ cwd }))
|
|
13
14
|
.use(fileSystemPlugin({ baseDir: "/" }))
|
|
15
|
+
.use(approvalPlugin({
|
|
16
|
+
rules: [
|
|
17
|
+
{ action: "action:executeCommand", message: "The agent wants to execute a terminal command. Please review carefully." },
|
|
18
|
+
{ action: "action:writeFile", message: "The agent wants to write to a file." },
|
|
19
|
+
{ action: "action:deleteFile", message: "The agent wants to delete a file." },
|
|
20
|
+
],
|
|
21
|
+
}))
|
|
14
22
|
.use(llmPlugin({
|
|
15
23
|
model,
|
|
16
24
|
system: systemPrompt,
|
|
17
25
|
toolDefinitions: {
|
|
18
26
|
...shellToolDefinitions,
|
|
19
|
-
...fileSystemToolDefinitions
|
|
27
|
+
...fileSystemToolDefinitions
|
|
20
28
|
},
|
|
21
29
|
promptInputType: "agent:os:input",
|
|
22
30
|
actionResultInputType: "agent:os:result",
|
package/dist/cli.js
CHANGED
package/dist/open-bot.js
CHANGED
|
@@ -14,6 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
// Plugin imports for the registry
|
|
15
15
|
import { shellPlugin, shellToolDefinitions } from "./plugins/shell/index.js";
|
|
16
16
|
import { fileSystemPlugin, fileSystemToolDefinitions } from "./plugins/file-system/index.js";
|
|
17
|
+
import { approvalPlugin } from "./plugins/approval/index.js";
|
|
17
18
|
// Registry
|
|
18
19
|
import { PluginRegistry, AgentRegistry, discoverYamlAgents, loadPluginsFromDir } from "./registry/index.js";
|
|
19
20
|
/**
|
|
@@ -45,6 +46,12 @@ export async function createOpenBot(options) {
|
|
|
45
46
|
toolDefinitions: fileSystemToolDefinitions,
|
|
46
47
|
factory: () => fileSystemPlugin({ baseDir: "/" }),
|
|
47
48
|
});
|
|
49
|
+
pluginRegistry.register({
|
|
50
|
+
name: "approval",
|
|
51
|
+
description: "Require user approval for specific actions",
|
|
52
|
+
toolDefinitions: {},
|
|
53
|
+
factory: (options) => approvalPlugin(options),
|
|
54
|
+
});
|
|
48
55
|
// ─── Shared Plugins ──────────────────────────────────────────────
|
|
49
56
|
// Load community/user plugins from ~/.openbot/plugins/
|
|
50
57
|
const sharedPlugins = await loadPluginsFromDir(path.join(resolvedBaseDir, "plugins"));
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { generateId } from "melony";
|
|
2
|
+
import { ui } from "@melony/ui-kit/server";
|
|
3
|
+
/**
|
|
4
|
+
* Approval Plugin for OpenBot.
|
|
5
|
+
* Intercepts specific actions and requires user approval before proceeding.
|
|
6
|
+
* Optimized using the new melony intercept() feature.
|
|
7
|
+
*/
|
|
8
|
+
export const approvalPlugin = (options) => (builder) => {
|
|
9
|
+
const { rules = [] } = options;
|
|
10
|
+
// Register an interceptor that runs before any handlers.
|
|
11
|
+
// This is the correct way to handle HITL/Approval in Melony.
|
|
12
|
+
builder.intercept(async (event, { state, suspend }) => {
|
|
13
|
+
// Skip if already approved or if it's an internal approval event
|
|
14
|
+
// We cast event to any to access the meta property which is used for internal state tracking
|
|
15
|
+
const meta = event.meta;
|
|
16
|
+
if (meta?.approved ||
|
|
17
|
+
event.type === "action:approve" ||
|
|
18
|
+
event.type === "action:deny" ||
|
|
19
|
+
event.type === "ui" ||
|
|
20
|
+
event.type.endsWith(":status")) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const rule = rules.find(r => event.type.startsWith(r.action));
|
|
24
|
+
if (!rule)
|
|
25
|
+
return;
|
|
26
|
+
const approvalId = `approve_${generateId()}`;
|
|
27
|
+
if (!state.pendingApprovals) {
|
|
28
|
+
state.pendingApprovals = {};
|
|
29
|
+
}
|
|
30
|
+
state.pendingApprovals[approvalId] = event;
|
|
31
|
+
// Use suspend(event) to emit the UI and halt execution of any handlers for this event.
|
|
32
|
+
// This effectively "pauses" the run for user input.
|
|
33
|
+
suspend(ui.event(ui.card({
|
|
34
|
+
title: "Approval Required",
|
|
35
|
+
description: rule.message || `Approval required for: ${event.type}`,
|
|
36
|
+
}, [
|
|
37
|
+
ui.text(JSON.stringify(event.data, null, 2), { size: "xs" }),
|
|
38
|
+
ui.row({ gap: "sm" }, [
|
|
39
|
+
ui.button({
|
|
40
|
+
label: "Approve",
|
|
41
|
+
variant: "primary",
|
|
42
|
+
onClickAction: {
|
|
43
|
+
type: "action:approve",
|
|
44
|
+
data: { id: approvalId }
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
ui.button({
|
|
48
|
+
label: "Deny",
|
|
49
|
+
variant: "outline",
|
|
50
|
+
onClickAction: {
|
|
51
|
+
type: "action:deny",
|
|
52
|
+
data: { id: approvalId }
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
]),
|
|
56
|
+
])));
|
|
57
|
+
});
|
|
58
|
+
// Handle Approval response from user
|
|
59
|
+
builder.on("action:approve", async function* (event, { state }) {
|
|
60
|
+
const { id } = event.data;
|
|
61
|
+
const originalEvent = state.pendingApprovals?.[id];
|
|
62
|
+
if (originalEvent) {
|
|
63
|
+
delete state.pendingApprovals[id];
|
|
64
|
+
yield ui.event(ui.status("Action approved", "success"));
|
|
65
|
+
// Re-emit the original event with approved: true.
|
|
66
|
+
// The interceptor will see it, but bypass because of meta.approved.
|
|
67
|
+
// Then the appropriate handlers for the event will finally run.
|
|
68
|
+
yield {
|
|
69
|
+
...originalEvent,
|
|
70
|
+
meta: {
|
|
71
|
+
...originalEvent.meta,
|
|
72
|
+
approved: true,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Handle Denial response from user
|
|
78
|
+
builder.on("action:deny", async function* (event, { state }) {
|
|
79
|
+
const { id } = event.data;
|
|
80
|
+
const originalEvent = state.pendingApprovals?.[id];
|
|
81
|
+
if (originalEvent) {
|
|
82
|
+
delete state.pendingApprovals[id];
|
|
83
|
+
yield ui.event(ui.status("Action denied", "error"));
|
|
84
|
+
// If it was a tool call (action:*), return a taskResult error so the LLM knows it failed
|
|
85
|
+
if (originalEvent.data?.toolCallId) {
|
|
86
|
+
yield {
|
|
87
|
+
type: "action:taskResult",
|
|
88
|
+
data: {
|
|
89
|
+
action: originalEvent.type.replace("action:", ""),
|
|
90
|
+
toolCallId: originalEvent.data.toolCallId,
|
|
91
|
+
result: { error: "Action denied by user" },
|
|
92
|
+
success: false,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
export default approvalPlugin;
|
|
@@ -39,7 +39,7 @@ export async function ensurePluginReady(pluginDir) {
|
|
|
39
39
|
// 1. Install dependencies if node_modules is missing
|
|
40
40
|
if (!hasNodeModules) {
|
|
41
41
|
console.log(`[plugins] Installing dependencies for ${path.basename(pluginDir)}...`);
|
|
42
|
-
execSync("npm install
|
|
42
|
+
execSync("npm install", { cwd: pluginDir, stdio: "inherit" });
|
|
43
43
|
}
|
|
44
44
|
// 2. Run build if dist is missing but build script exists
|
|
45
45
|
const distPath = path.join(pluginDir, "dist");
|
package/dist/ui/settings.js
CHANGED
|
@@ -32,9 +32,9 @@ export const settingsUI = async () => {
|
|
|
32
32
|
ui.heading("Model Configuration", 4),
|
|
33
33
|
ui.col({ gap: "sm" }, [
|
|
34
34
|
ui.input("model", undefined, {
|
|
35
|
-
placeholder: "e.g. gpt-4o
|
|
35
|
+
placeholder: "provider/model (e.g. openai/gpt-4o)",
|
|
36
36
|
width: "full",
|
|
37
|
-
defaultValue: config.model || "gpt-4o-mini"
|
|
37
|
+
defaultValue: config.model || "openai/gpt-4o-mini"
|
|
38
38
|
}),
|
|
39
39
|
]),
|
|
40
40
|
]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"express": "^4.19.2",
|
|
19
19
|
"gray-matter": "^4.0.3",
|
|
20
20
|
"js-yaml": "^4.1.1",
|
|
21
|
-
"melony": "^0.2.
|
|
21
|
+
"melony": "^0.2.9",
|
|
22
22
|
"zod": "^4.3.5"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|