openbot 0.1.18 → 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";
@@ -53,7 +56,7 @@ export async function createOpenBot(options) {
53
56
  const baseDir = config.baseDir || DEFAULT_BASE_DIR;
54
57
  const resolvedBaseDir = resolvePath(baseDir);
55
58
  // Parse model configuration
56
- const { provider, modelId } = parseModelString(config.model || "gpt-5-nano");
59
+ const { provider, modelId } = parseModelString(config.model || "gpt-4o-mini");
57
60
  // Tool definitions shared by both providers
58
61
  const toolDefinitions = {
59
62
  ...shellToolDefinitions,
@@ -77,27 +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
95
  channel: 'chrome',
92
96
  model: model
93
97
  }))
98
+ .use(browserUIPlugin())
94
99
  .use(fileSystemPlugin({
95
100
  baseDir: "/", // Global access
96
101
  }))
102
+ .use(fileSystemUIPlugin())
97
103
  .use(metaAgentPlugin({
98
104
  baseDir: resolvedBaseDir,
99
105
  allowSoulModification: false, // Protect core values
100
106
  }))
107
+ .use(metaAgentUIPlugin())
101
108
  .use(llmPlugin)
102
109
  .on("init", initHandler);
103
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.18");
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)) {
@@ -2,9 +2,16 @@ import { layoutUI } from "../ui/layout.js";
2
2
  /**
3
3
  * Initial application layout handler
4
4
  */
5
- export async function* initHandler(event) {
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
+ }
6
13
  yield {
7
14
  type: "ui",
8
- data: layoutUI
15
+ data: await layoutUI({ tab: event.data?.tab || "chat", sessionId: state.sessionId })
9
16
  };
10
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
+ ]);
package/dist/ui/layout.js CHANGED
@@ -1,4 +1,25 @@
1
1
  import { ui } from "@melony/ui-kit";
2
2
  import { threadUI } from "./thread.js";
3
3
  import { sidebarUI } from "./sidebar.js";
4
- export const layoutUI = ui.row({ height: "full" }, [sidebarUI, threadUI]);
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
+ };
@@ -1,16 +1,13 @@
1
1
  import { ui } from "@melony/ui-kit";
2
2
  const listItemProps = (path) => ({
3
- padding: 'sm',
4
3
  onClickAction: {
5
4
  type: "client:navigate",
6
5
  data: { path },
7
6
  },
8
7
  });
9
- export const navigationUI = ui.box({ paddingHorizontal: "sm" }, [ui.list({
8
+ export const navigationUI = ui.box({ width: "full" }, [ui.list({
10
9
  gap: "none",
11
- padding: "none",
12
10
  }, [
13
- ui.listItem(listItemProps("/"), [ui.text("Home", { size: "sm" })]),
11
+ ui.listItem(listItemProps("/"), [ui.text("Chat", { size: "sm" })]),
14
12
  ui.listItem(listItemProps("/?tab=skills"), [ui.text("Skills", { size: "sm" })]),
15
- ui.listItem(listItemProps("/?tab=settings"), [ui.text("Settings", { size: "sm" })]),
16
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
+ ]);
@@ -1,9 +1,57 @@
1
1
  import { ui } from "@melony/ui-kit";
2
2
  import { navigationUI } from "./navigation.js";
3
- export const sidebarUI = ui.box({
4
- width: 240,
3
+ export const sidebarUI = ({ sessions, sessionId }) => ui.box({
4
+ width: 280,
5
5
  height: "full",
6
+ overflow: "hidden",
7
+ background: "muted/20",
6
8
  }, [
7
- ui.box({ paddingVertical: "sm", paddingHorizontal: "md" }, [ui.text("OpenBot", { size: "sm", weight: "bold" })]),
8
- navigationUI,
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
+ ]),
9
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
+ ]);
package/dist/ui/thread.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { ui } from "@melony/ui-kit";
2
- export const threadUI = ui.thread({
3
- placeholder: "Ask me anything about your system or projects...",
4
- welcomeTitle: "OpenBot System Agent",
5
- welcomeMessage: "I'm your global system assistant. I have access to your file system and shell. How can I help you today?",
6
- suggestions: [
7
- "What is in my current directory?",
8
- "Check system status",
9
- "Who am I?",
10
- ]
11
- });
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.18",
3
+ "version": "0.1.20",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,13 +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-browser": "0.1.4",
21
- "@melony/plugin-file-system": "0.1.2",
22
- "@melony/plugin-meta-agent": "0.1.2",
23
- "@melony/plugin-shell": "0.1.1",
24
- "@melony/ui-kit": "0.1.2",
25
- "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"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/express": "^4.17.21",