openbot 0.1.17 → 0.1.20

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/agent.js CHANGED
@@ -1,9 +1,12 @@
1
- import os from "node:os";
2
1
  import { melony } from "melony";
3
2
  import { shellPlugin, shellToolDefinitions } from "@melony/plugin-shell";
3
+ import { shellUIPlugin } from "@melony/plugin-shell/ui";
4
4
  import { browserPlugin, browserToolDefinitions } from "@melony/plugin-browser";
5
+ import { browserUIPlugin } from "@melony/plugin-browser/ui";
5
6
  import { fileSystemPlugin, fileSystemToolDefinitions } from "@melony/plugin-file-system";
7
+ import { fileSystemUIPlugin } from "@melony/plugin-file-system/ui";
6
8
  import { metaAgentPlugin, metaAgentToolDefinitions, buildSystemPrompt } from "@melony/plugin-meta-agent";
9
+ import { metaAgentUIPlugin } from "@melony/plugin-meta-agent/ui";
7
10
  import { aiSDKPlugin } from "@melony/plugin-ai-sdk";
8
11
  import { openai } from "@ai-sdk/openai";
9
12
  import { anthropic } from "@ai-sdk/anthropic";
@@ -77,26 +80,31 @@ export async function createOpenBot(options) {
77
80
  }
78
81
  const llmPlugin = aiSDKPlugin({
79
82
  model: model,
80
- system: (_context) => buildSystemPrompt(resolvedBaseDir),
83
+ system: (context) => buildSystemPrompt(resolvedBaseDir, context),
81
84
  toolDefinitions,
82
85
  });
83
86
  // Use a dedicated directory for the agent's browser data to avoid conflicts with your main Chrome.
84
87
  // Using the root Chrome directory causes conflicts and can log you out of your main browser.
85
- const userDataDir = path.join(os.homedir(), ".openbot", "browser-data");
88
+ const userDataDir = path.join(resolvedBaseDir, "browser-data");
86
89
  return melony()
87
90
  .use(shellPlugin({ cwd: process.cwd() }))
91
+ .use(shellUIPlugin())
88
92
  .use(browserPlugin({
89
93
  headless: true, // Set to false once to log in manually if needed
90
94
  userDataDir: userDataDir,
91
- channel: 'chrome'
95
+ channel: 'chrome',
96
+ model: model
92
97
  }))
98
+ .use(browserUIPlugin())
93
99
  .use(fileSystemPlugin({
94
100
  baseDir: "/", // Global access
95
101
  }))
102
+ .use(fileSystemUIPlugin())
96
103
  .use(metaAgentPlugin({
97
104
  baseDir: resolvedBaseDir,
98
105
  allowSoulModification: false, // Protect core values
99
106
  }))
107
+ .use(metaAgentUIPlugin())
100
108
  .use(llmPlugin)
101
109
  .on("init", initHandler);
102
110
  }
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ const program = new Command();
6
6
  program
7
7
  .name("openbot")
8
8
  .description("OpenBot CLI - Secure and easy configuration")
9
- .version("0.1.17");
9
+ .version("0.1.20");
10
10
  program
11
11
  .command("configure")
12
12
  .description("Configure OpenBot model and settings")
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
- export const DEFAULT_BASE_DIR = "~/openbot";
4
+ export const DEFAULT_BASE_DIR = "~/.openbot";
5
5
  export function loadConfig() {
6
6
  const configPath = path.join(os.homedir(), ".openbot", "config.json");
7
7
  if (fs.existsSync(configPath)) {
@@ -1,148 +1,17 @@
1
+ import { layoutUI } from "../ui/layout.js";
1
2
  /**
2
3
  * Initial application layout handler
3
4
  */
4
- export async function* initHandler(event) {
5
- const thredUI = {
6
- type: "thread",
7
- props: {
8
- placeholder: "Ask me anything about your system or projects...",
9
- welcomeTitle: "OpenBot System Agent",
10
- welcomeMessage: "I'm your global system assistant. I have access to your file system and shell. How can I help you today?",
11
- suggestions: [
12
- "What is in my current directory?",
13
- "Check system status",
14
- "Who am I?",
15
- ]
16
- },
17
- };
18
- const navigationUI = {
19
- type: "list",
20
- props: {
21
- gap: "sm",
22
- padding: "sm",
23
- },
24
- children: [
25
- {
26
- type: "listItem",
27
- props: {
28
- onClickAction: {
29
- type: "client:navigate",
30
- data: {
31
- path: "/some-url",
32
- }
33
- },
34
- },
35
- children: [
36
- {
37
- type: "icon",
38
- props: {
39
- name: "🏠"
40
- }
41
- }
42
- ]
43
- },
44
- {
45
- type: "listItem",
46
- props: {
47
- onClickAction: {
48
- type: "client:navigate",
49
- data: {
50
- path: "/some-other-url",
51
- }
52
- },
53
- },
54
- children: [
55
- {
56
- type: "icon",
57
- props: {
58
- name: "⚙️"
59
- }
60
- }
61
- ]
62
- }
63
- ]
64
- };
65
- const browserMonitorUI = {
66
- type: "card",
67
- props: {
68
- title: "Browser Manager",
69
- subtitle: "Manage open tabs & memory",
70
- padding: "sm",
71
- width: "300px",
72
- },
73
- children: [
74
- {
75
- type: "col",
76
- props: { gap: "sm" },
77
- children: [
78
- {
79
- type: "text",
80
- props: {
81
- value: "Status: Persistent session active",
82
- size: "xs",
83
- color: "muted"
84
- }
85
- },
86
- {
87
- type: "row",
88
- props: { gap: "xs", justify: "end" },
89
- children: [
90
- {
91
- type: "button",
92
- props: {
93
- label: "Refresh",
94
- size: "xs",
95
- variant: "outline",
96
- onClickAction: {
97
- type: "browser:poll_state", // Sends this event to the server
98
- }
99
- }
100
- },
101
- {
102
- type: "button",
103
- props: {
104
- label: "Clean Junk",
105
- size: "xs",
106
- variant: "danger",
107
- onClickAction: {
108
- type: "browser:cleanup", // Sends this event to the server
109
- }
110
- }
111
- }
112
- ]
113
- }
114
- ]
115
- }
116
- ]
117
- };
118
- const layoutUI = {
119
- type: "box",
120
- props: {
121
- padding: "none",
122
- radius: "md",
123
- shadow: "sm",
124
- height: "full",
125
- },
126
- children: [
127
- thredUI,
128
- {
129
- type: "float",
130
- props: {
131
- position: "top-left"
132
- },
133
- children: [
134
- navigationUI
135
- ]
136
- },
137
- // {
138
- // type: "float",
139
- // props: { position: "top-right", margin: "md" }, // Add it to the top right
140
- // children: [browserMonitorUI]
141
- // }
142
- ]
143
- };
5
+ export async function* initHandler(event, { state }) {
6
+ // Initialize path state if not already present
7
+ if (!state.cwd) {
8
+ state.cwd = process.cwd();
9
+ }
10
+ if (!state.workspaceRoot) {
11
+ state.workspaceRoot = process.cwd();
12
+ }
144
13
  yield {
145
14
  type: "ui",
146
- data: layoutUI
15
+ data: await layoutUI({ tab: event.data?.tab || "chat", sessionId: state.sessionId })
147
16
  };
148
17
  }
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  import { generateId } from "melony";
7
7
  import { createOpenBot } from "./agent.js";
8
8
  import { loadConfig } from "./config.js";
9
- import { loadSession, saveSession } from "./session.js";
9
+ import { loadSession, saveSession, logEvent, loadEvents } from "./session.js";
10
10
  const program = new Command();
11
11
  program
12
12
  .name("openbot-server")
@@ -36,18 +36,21 @@ program
36
36
  app.get("/api/init", async (req, res) => {
37
37
  const platform = req.query.platform || "web";
38
38
  const sessionId = req.query.sessionId || "default";
39
+ const tab = req.query.tab;
39
40
  const state = await loadSession(sessionId) ?? {};
40
41
  const response = await openBot.jsonResponse({
41
42
  type: "init",
42
- data: { platform }
43
+ data: { platform, tab }
43
44
  }, {
44
- state,
45
+ state: { ...state, sessionId },
45
46
  runId: generateId()
46
47
  });
47
48
  const result = await response.json();
48
- // Save state in case init handler modified it
49
- await saveSession(sessionId, state);
50
- res.json(result);
49
+ const initialEvents = await loadEvents(sessionId);
50
+ res.json({
51
+ data: result.data,
52
+ initialEvents
53
+ });
51
54
  });
52
55
  // Chat endpoint
53
56
  app.post("/api/chat", async (req, res) => {
@@ -67,6 +70,8 @@ program
67
70
  const sessionId = body.sessionId ?? "default";
68
71
  const runId = body.runId ?? `run_${generateId()}`;
69
72
  const state = await loadSession(sessionId) ?? {};
73
+ // Log the incoming event
74
+ await logEvent(sessionId, runId, body.event);
70
75
  const iterator = runtime.run(body.event, {
71
76
  runId,
72
77
  state,
@@ -81,7 +86,7 @@ program
81
86
  break;
82
87
  }
83
88
  // Log each event to the persistent file
84
- // await logEvent(sessionId, runId, chunk);
89
+ await logEvent(sessionId, runId, chunk);
85
90
  res.write(`data: ${JSON.stringify(chunk)}\n\n`);
86
91
  }
87
92
  // After the run finishes, save the final state back to disk
package/dist/session.js CHANGED
@@ -35,6 +35,12 @@ function getSessionDir(sessionId) {
35
35
  const today = new Date().toISOString().slice(0, 10);
36
36
  return path.join(SESSIONS_DIR, today, sessionId);
37
37
  }
38
+ /**
39
+ * Maximum number of messages to keep when loading a session.
40
+ * Older messages are dropped to avoid bloated context windows.
41
+ * System messages at the start are always preserved and don't count toward the limit.
42
+ */
43
+ const MAX_MESSAGES = 1000; // aiSdkPlugin defaults to latest 20 messages
38
44
  export async function loadSession(sessionId) {
39
45
  const sessionDir = getSessionDir(sessionId);
40
46
  const statePath = path.join(sessionDir, "state.json");
@@ -43,7 +49,19 @@ export async function loadSession(sessionId) {
43
49
  }
44
50
  try {
45
51
  const data = fs.readFileSync(statePath, "utf-8");
46
- return JSON.parse(data);
52
+ const state = JSON.parse(data);
53
+ if (state.messages && state.messages.length > MAX_MESSAGES) {
54
+ // Preserve system messages at the beginning, then keep the tail
55
+ const systemMessages = [];
56
+ let rest = state.messages;
57
+ while (rest.length > 0 && rest[0].role === "system") {
58
+ systemMessages.push(rest[0]);
59
+ rest = rest.slice(1);
60
+ }
61
+ const kept = rest.slice(-MAX_MESSAGES);
62
+ state.messages = [...systemMessages, ...kept];
63
+ }
64
+ return state;
47
65
  }
48
66
  catch (error) {
49
67
  console.error(`Failed to load session ${sessionId}:`, error);
@@ -71,3 +89,63 @@ export async function logEvent(sessionId, runId, event) {
71
89
  });
72
90
  fs.appendFileSync(logPath, entry + "\n", "utf-8");
73
91
  }
92
+ export async function loadEvents(sessionId) {
93
+ const sessionDir = getSessionDir(sessionId);
94
+ const logPath = path.join(sessionDir, `events.jsonl`);
95
+ if (!fs.existsSync(logPath)) {
96
+ return [];
97
+ }
98
+ try {
99
+ const data = fs.readFileSync(logPath, "utf-8");
100
+ return data
101
+ .split("\n")
102
+ .filter((line) => line.trim() !== "")
103
+ .map((line) => JSON.parse(line));
104
+ }
105
+ catch (error) {
106
+ console.error(`Failed to load events for session ${sessionId}:`, error);
107
+ return [];
108
+ }
109
+ }
110
+ export async function listSessions() {
111
+ if (!fs.existsSync(SESSIONS_DIR))
112
+ return [];
113
+ const sessions = [];
114
+ try {
115
+ const items = fs.readdirSync(SESSIONS_DIR);
116
+ for (const item of items) {
117
+ const itemPath = path.join(SESSIONS_DIR, item);
118
+ const stat = fs.statSync(itemPath);
119
+ if (stat.isDirectory()) {
120
+ // If it's a date folder (YYYY-MM-DD), look inside
121
+ if (/^\d{4}-\d{2}-\d{2}$/.test(item)) {
122
+ const subItems = fs.readdirSync(itemPath);
123
+ for (const subItem of subItems) {
124
+ const sessionPath = path.join(itemPath, subItem);
125
+ const statePath = path.join(sessionPath, "state.json");
126
+ if (fs.existsSync(statePath)) {
127
+ sessions.push({
128
+ id: subItem,
129
+ mtime: fs.statSync(statePath).birthtime, // sort by creation time
130
+ });
131
+ }
132
+ }
133
+ }
134
+ else {
135
+ // It's a legacy session folder in root
136
+ const statePath = path.join(itemPath, "state.json");
137
+ if (fs.existsSync(statePath)) {
138
+ sessions.push({
139
+ id: item,
140
+ mtime: fs.statSync(statePath).birthtime, // sort by creation time
141
+ });
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ catch (error) {
148
+ console.error("Failed to list sessions:", error);
149
+ }
150
+ return sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
151
+ }
@@ -0,0 +1,55 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ export const headerUI = (tab) => ui.box({
3
+ padding: "sm",
4
+ background: "background",
5
+ width: "full",
6
+ }, [
7
+ ui.row({ justify: "between", align: "center", width: "full" }, [
8
+ ui.row({ gap: "xs", align: "center" }, [
9
+ ui.button({
10
+ label: "OpenBot",
11
+ variant: "ghost",
12
+ size: "sm",
13
+ onClickAction: {
14
+ type: "client:navigate",
15
+ data: { path: "/" },
16
+ },
17
+ }),
18
+ ui.divider({ orientation: "vertical", margin: "xs" }),
19
+ ui.button({
20
+ label: "Chat",
21
+ variant: (tab === "chat" || !tab) ? "secondary" : "ghost",
22
+ size: "sm",
23
+ onClickAction: {
24
+ type: "client:navigate",
25
+ data: { path: "/" },
26
+ },
27
+ }),
28
+ ui.button({
29
+ label: "Skills",
30
+ variant: tab === "skills" ? "secondary" : "ghost",
31
+ size: "sm",
32
+ onClickAction: {
33
+ type: "client:navigate",
34
+ data: { path: "/?tab=skills" },
35
+ },
36
+ }),
37
+ ui.button({
38
+ label: "Settings",
39
+ variant: tab === "settings" ? "secondary" : "ghost",
40
+ size: "sm",
41
+ onClickAction: {
42
+ type: "client:navigate",
43
+ data: { path: "/?tab=settings" },
44
+ },
45
+ }),
46
+ ]),
47
+ ui.row({ gap: "sm", align: "center", width: "auto" }, [
48
+ ui.button({
49
+ label: "JD",
50
+ variant: "outline",
51
+ size: "sm",
52
+ }),
53
+ ]),
54
+ ]),
55
+ ]);
@@ -0,0 +1,25 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ import { threadUI } from "./thread.js";
3
+ import { sidebarUI } from "./sidebar.js";
4
+ import { settingsUI } from "./settings.js";
5
+ import { skillsUI } from "./skills.js";
6
+ import { listSessions } from "../session.js";
7
+ const tabs = {
8
+ chat: threadUI,
9
+ settings: settingsUI,
10
+ skills: skillsUI,
11
+ };
12
+ // export const layoutUI = (tab: string) =>
13
+ // ui.col({ height: "full", width: "full", gap: "none" }, [
14
+ // headerUI(tab),
15
+ // ui.box({ flex: 1, width: "full", overflow: "auto" }, [
16
+ // tabs[tab as keyof typeof tabs],
17
+ // ]),
18
+ // ]);
19
+ export const layoutUI = async ({ tab, sessionId }) => {
20
+ const sessions = await listSessions();
21
+ return ui.row({ height: "full" }, [
22
+ sidebarUI({ sessions, sessionId }),
23
+ tabs[tab]
24
+ ]);
25
+ };
@@ -0,0 +1,13 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ const listItemProps = (path) => ({
3
+ onClickAction: {
4
+ type: "client:navigate",
5
+ data: { path },
6
+ },
7
+ });
8
+ export const navigationUI = ui.box({ width: "full" }, [ui.list({
9
+ gap: "none",
10
+ }, [
11
+ ui.listItem(listItemProps("/"), [ui.text("Chat", { size: "sm" })]),
12
+ ui.listItem(listItemProps("/?tab=skills"), [ui.text("Skills", { size: "sm" })]),
13
+ ])]);
@@ -0,0 +1,44 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ export const settingsUI = ui.box({
3
+ width: "full",
4
+ height: "full",
5
+ padding: "xl",
6
+ background: "background",
7
+ }, [
8
+ ui.col({ gap: "xl", width: "full" }, [
9
+ ui.col({ gap: "xs" }, [
10
+ ui.heading("Settings", 2),
11
+ ui.text("Manage your OpenBot configuration", { color: "mutedForeground" }),
12
+ ]),
13
+ ui.divider(),
14
+ ui.col({ gap: "md" }, [
15
+ ui.heading("Model Configuration", 4),
16
+ ui.row({ align: "center", gap: "md" }, [
17
+ ui.box({ flex: 1 }, [
18
+ ui.node("label", { value: "Provider" }),
19
+ ui.text("OpenAI (GPT-4o)", { size: "sm", color: "mutedForeground" }),
20
+ ]),
21
+ ui.button({ label: "Change", variant: "outline", size: "sm" })
22
+ ]),
23
+ ]),
24
+ ui.col({ gap: "md" }, [
25
+ ui.heading("API Keys", 4),
26
+ ui.col({ gap: "sm" }, [
27
+ ui.node("label", { value: "OpenAI API Key" }),
28
+ ui.row({ gap: "sm" }, [
29
+ ui.input("openai_api_key", undefined, {
30
+ placeholder: "sk-...",
31
+ inputType: "password",
32
+ defaultValue: "••••••••••••••••",
33
+ width: "full"
34
+ }),
35
+ ui.button({ label: "Save", size: "sm" })
36
+ ])
37
+ ])
38
+ ]),
39
+ ui.col({ gap: "md" }, [
40
+ ui.heading("Theme", 4),
41
+ ui.themeToggle(),
42
+ ])
43
+ ])
44
+ ]);
@@ -0,0 +1,57 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ import { navigationUI } from "./navigation.js";
3
+ export const sidebarUI = ({ sessions, sessionId }) => ui.box({
4
+ width: 280,
5
+ height: "full",
6
+ overflow: "hidden",
7
+ background: "muted/20",
8
+ }, [
9
+ ui.col({ width: "full", height: "full" }, [
10
+ ui.box({ padding: "xs" }, [
11
+ ui.row({ justify: "between", align: "center", width: "full" }, [
12
+ ui.button({
13
+ label: "OpenBot",
14
+ variant: "ghost",
15
+ size: "sm",
16
+ onClickAction: {
17
+ type: "client:navigate",
18
+ data: { path: "/" },
19
+ },
20
+ }),
21
+ ]),
22
+ ]),
23
+ ui.col({ width: "full", gap: "sm", padding: "sm" }, [
24
+ navigationUI,
25
+ ]),
26
+ ui.col({ width: "full", flex: 1, padding: "sm", overflow: "auto" }, [
27
+ ui.list({
28
+ gap: "none",
29
+ width: "full",
30
+ }, [
31
+ ...sessions.map((session) => ui.listItem({
32
+ padding: "sm",
33
+ onClickAction: {
34
+ type: "client:navigate",
35
+ data: { path: `/?sessionId=${session.id}` },
36
+ },
37
+ background: session.id === sessionId ? "muted" : "transparent",
38
+ }, [ui.text(session.id.slice(0, 10), { size: "sm" })]))
39
+ ]),
40
+ ]),
41
+ ui.spacer({ size: "xs" }),
42
+ ui.box({ padding: "sm", width: "full" }, [
43
+ ui.list({
44
+ gap: "sm",
45
+ width: "full",
46
+ }, [
47
+ ui.listItem({
48
+ padding: "sm",
49
+ onClickAction: {
50
+ type: "client:navigate",
51
+ data: { path: "/?tab=settings" },
52
+ },
53
+ }, [ui.text("Settings", { size: "sm" })]),
54
+ ]),
55
+ ]),
56
+ ]),
57
+ ]);
@@ -0,0 +1,7 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ export const skillsUI = ui.box({
3
+ padding: "md",
4
+ }, [
5
+ ui.heading("Skills", 2),
6
+ ui.text("The skills for the OpenBot", { size: "sm", color: "mutedForeground" }),
7
+ ]);
@@ -0,0 +1,14 @@
1
+ import { ui } from "@melony/ui-kit";
2
+ export const threadUI = ui.box({
3
+ width: "full",
4
+ height: "full",
5
+ }, [ui.thread({
6
+ placeholder: "Ask me anything about your system or projects...",
7
+ welcomeTitle: "OpenBot",
8
+ welcomeMessage: "Hey there! I'm your trusty system sidekick with superpowers over your files and terminal. Let's make some magic happen!",
9
+ suggestions: [
10
+ "What is in my current directory?",
11
+ "Check system status",
12
+ "Who am I?",
13
+ ]
14
+ })]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.1.17",
3
+ "version": "0.1.20",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,12 +16,13 @@
16
16
  "dotenv": "^16.4.5",
17
17
  "express": "^4.19.2",
18
18
  "zod": "^4.3.5",
19
- "@melony/plugin-ai-sdk": "0.1.4",
20
- "@melony/plugin-file-system": "0.1.1",
21
- "@melony/plugin-browser": "0.1.3",
22
- "@melony/plugin-shell": "0.1.1",
23
- "@melony/plugin-meta-agent": "0.1.2",
24
- "melony": "0.2.6"
19
+ "@melony/plugin-ai-sdk": "0.1.5",
20
+ "@melony/plugin-file-system": "0.1.3",
21
+ "@melony/plugin-browser": "0.1.5",
22
+ "@melony/plugin-shell": "0.1.2",
23
+ "melony": "0.2.7",
24
+ "@melony/ui-kit": "0.1.3",
25
+ "@melony/plugin-meta-agent": "0.1.3"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/express": "^4.17.21",