openbot 0.2.11 → 0.2.13

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/AGENTS.md +68 -0
  3. package/CONTRIBUTING.md +74 -0
  4. package/LICENSE +21 -0
  5. package/README.md +117 -14
  6. package/dist/agents/system.js +106 -0
  7. package/dist/app/cli.js +27 -0
  8. package/dist/app/config.js +64 -0
  9. package/dist/app/server.js +237 -0
  10. package/dist/app/utils.js +35 -0
  11. package/dist/harness/agent-harness.js +45 -0
  12. package/dist/harness/mcp.js +61 -0
  13. package/dist/harness/orchestrator.js +273 -0
  14. package/dist/harness/process.js +7 -0
  15. package/dist/plugins/ai-sdk.js +141 -0
  16. package/dist/plugins/delegation.js +52 -0
  17. package/dist/plugins/mcp.js +140 -0
  18. package/dist/plugins/storage.js +502 -0
  19. package/dist/plugins/ui.js +47 -0
  20. package/dist/registry/plugins.js +73 -0
  21. package/dist/services/storage.js +724 -0
  22. package/docs/README.md +7 -0
  23. package/docs/agents.md +83 -0
  24. package/docs/architecture.md +34 -0
  25. package/docs/plugins.md +77 -0
  26. package/logo-black.png +0 -0
  27. package/{dist/assets/logo.js → logo-black.svg} +24 -24
  28. package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
  29. package/package.json +10 -9
  30. package/src/agents/system.ts +112 -0
  31. package/src/app/cli.ts +38 -0
  32. package/src/app/config.ts +104 -0
  33. package/src/app/server.ts +284 -0
  34. package/src/app/types.ts +476 -0
  35. package/src/app/utils.ts +43 -0
  36. package/src/assets/icon.svg +1 -0
  37. package/src/harness/agent-harness.ts +58 -0
  38. package/src/harness/mcp.ts +78 -0
  39. package/src/harness/orchestrator.ts +342 -0
  40. package/src/harness/process.ts +9 -0
  41. package/src/harness/types.ts +34 -0
  42. package/src/plugins/ai-sdk.ts +197 -0
  43. package/src/plugins/delegation.ts +60 -0
  44. package/src/plugins/mcp.ts +154 -0
  45. package/src/plugins/storage.ts +725 -0
  46. package/src/plugins/ui.ts +57 -0
  47. package/src/registry/plugins.ts +85 -0
  48. package/src/services/storage.ts +957 -0
  49. package/tsconfig.json +18 -0
  50. package/dist/agents/agent-creator.js +0 -74
  51. package/dist/agents/browser-agent.js +0 -31
  52. package/dist/agents/os-agent.js +0 -32
  53. package/dist/agents/planner-agent.js +0 -32
  54. package/dist/agents/topic-agent.js +0 -46
  55. package/dist/architecture/execution-engine.js +0 -151
  56. package/dist/architecture/intent-classifier.js +0 -26
  57. package/dist/architecture/planner.js +0 -106
  58. package/dist/automation-worker.js +0 -121
  59. package/dist/automations.js +0 -52
  60. package/dist/cli.js +0 -275
  61. package/dist/config.js +0 -53
  62. package/dist/core/agents.js +0 -41
  63. package/dist/core/delegation.js +0 -230
  64. package/dist/core/manager.js +0 -96
  65. package/dist/core/plugins.js +0 -74
  66. package/dist/core/router.js +0 -191
  67. package/dist/handlers/init.js +0 -29
  68. package/dist/handlers/session-change.js +0 -21
  69. package/dist/handlers/settings.js +0 -47
  70. package/dist/handlers/tab-change.js +0 -14
  71. package/dist/installers.js +0 -156
  72. package/dist/marketplace.js +0 -80
  73. package/dist/model-catalog.js +0 -132
  74. package/dist/model-defaults.js +0 -25
  75. package/dist/models.js +0 -47
  76. package/dist/open-bot.js +0 -51
  77. package/dist/orchestrator/direct-invocation.js +0 -13
  78. package/dist/orchestrator/events.js +0 -36
  79. package/dist/orchestrator/state.js +0 -54
  80. package/dist/orchestrator.js +0 -422
  81. package/dist/plugins/agent/index.js +0 -81
  82. package/dist/plugins/approval/index.js +0 -100
  83. package/dist/plugins/brain/identity.js +0 -77
  84. package/dist/plugins/brain/index.js +0 -204
  85. package/dist/plugins/brain/memory.js +0 -120
  86. package/dist/plugins/brain/prompt.js +0 -46
  87. package/dist/plugins/brain/types.js +0 -45
  88. package/dist/plugins/brain/ui.js +0 -7
  89. package/dist/plugins/browser/index.js +0 -629
  90. package/dist/plugins/browser/ui.js +0 -13
  91. package/dist/plugins/file-system/index.js +0 -171
  92. package/dist/plugins/file-system/ui.js +0 -6
  93. package/dist/plugins/llm/context-budget.js +0 -139
  94. package/dist/plugins/llm/context-shaping.js +0 -177
  95. package/dist/plugins/llm/index.js +0 -380
  96. package/dist/plugins/memory/index.js +0 -220
  97. package/dist/plugins/memory/memory.js +0 -122
  98. package/dist/plugins/memory/prompt.js +0 -55
  99. package/dist/plugins/memory/types.js +0 -45
  100. package/dist/plugins/meta-agent/index.js +0 -570
  101. package/dist/plugins/meta-agent/ui.js +0 -11
  102. package/dist/plugins/shell/index.js +0 -100
  103. package/dist/plugins/shell/ui.js +0 -6
  104. package/dist/plugins/skills/index.js +0 -286
  105. package/dist/plugins/skills/types.js +0 -50
  106. package/dist/plugins/skills/ui.js +0 -12
  107. package/dist/registry/agent-registry.js +0 -35
  108. package/dist/registry/index.js +0 -2
  109. package/dist/registry/plugin-loader.js +0 -499
  110. package/dist/registry/plugin-registry.js +0 -44
  111. package/dist/registry/ts-agent-loader.js +0 -82
  112. package/dist/registry/yaml-agent-loader.js +0 -246
  113. package/dist/runtime/execution-trace.js +0 -41
  114. package/dist/runtime/intent-routing.js +0 -26
  115. package/dist/runtime/openbot-runtime.js +0 -354
  116. package/dist/server.js +0 -890
  117. package/dist/session.js +0 -179
  118. package/dist/ui/block.js +0 -12
  119. package/dist/ui/header.js +0 -52
  120. package/dist/ui/layout.js +0 -26
  121. package/dist/ui/navigation.js +0 -15
  122. package/dist/ui/settings.js +0 -106
  123. package/dist/ui/skills.js +0 -7
  124. package/dist/ui/thread.js +0 -16
  125. package/dist/ui/widgets/action-list.js +0 -2
  126. package/dist/ui/widgets/approval-card.js +0 -9
  127. package/dist/ui/widgets/code-snippet.js +0 -2
  128. package/dist/ui/widgets/data-block.js +0 -2
  129. package/dist/ui/widgets/data-table.js +0 -2
  130. package/dist/ui/widgets/delegation.js +0 -29
  131. package/dist/ui/widgets/empty-state.js +0 -2
  132. package/dist/ui/widgets/index.js +0 -23
  133. package/dist/ui/widgets/inquiry.js +0 -7
  134. package/dist/ui/widgets/key-value.js +0 -2
  135. package/dist/ui/widgets/progress-step.js +0 -2
  136. package/dist/ui/widgets/resource-card.js +0 -2
  137. package/dist/ui/widgets/status.js +0 -2
  138. package/dist/ui/widgets/todo-list.js +0 -2
  139. package/dist/version.js +0 -62
  140. /package/dist/{types.js → app/types.js} +0 -0
  141. /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
@@ -1,96 +0,0 @@
1
- import { z } from "zod";
2
- import { memoryPlugin, memoryToolDefinitions, createMemoryPromptBuilder } from "../plugins/memory/index.js";
3
- import { topicAgent } from "../agents/topic-agent.js";
4
- import { llmPlugin } from "../plugins/llm/index.js";
5
- export function createManagerPlugin(model, resolvedModelId, resolvedBaseDir, registry) {
6
- const agentIds = registry.getAgentIds();
7
- const allAgents = registry.getAgents();
8
- const buildMemoryPrompt = createMemoryPromptBuilder(resolvedBaseDir);
9
- const agentDescriptions = allAgents
10
- .map((a) => {
11
- const tools = a.capabilities
12
- ? Object.entries(a.capabilities)
13
- .map(([name, desc]) => ` - ${name}: ${desc}`)
14
- .join("\n")
15
- : "";
16
- return `<agent id="${a.id}" name="${a.name}">
17
- <description>${a.description}</description>
18
- ${tools ? ` <capabilities>\n${tools}\n </capabilities>` : ""}
19
- </agent>`;
20
- })
21
- .join("\n\n");
22
- console.log("agentIds", agentIds);
23
- console.log("agentDescriptions", agentDescriptions);
24
- return (builder) => {
25
- builder
26
- .use(memoryPlugin({
27
- baseDir: resolvedBaseDir,
28
- }))
29
- .use(topicAgent({ model: model }))
30
- .use(llmPlugin({
31
- model: model,
32
- modelId: resolvedModelId,
33
- usageScope: "manager",
34
- system: async (context) => {
35
- const memoryPrompt = await buildMemoryPrompt(context);
36
- const state = context.state;
37
- // Deterministically inject any custom state keys into the prompt
38
- const standardKeys = ["messages", "agentStates", "usage", "cwd", "workspaceRoot", "title", "sessionId", "pendingAgentTasks"];
39
- const customState = {};
40
- for (const key of Object.keys(state)) {
41
- if (!standardKeys.includes(key)) {
42
- customState[key] = state[key];
43
- }
44
- }
45
- const statePrompt = Object.keys(customState).length > 0
46
- ? `\n\n<session_state>\n${JSON.stringify(customState, null, 2)}\n</session_state>`
47
- : "";
48
- const finalSystemPrompt = `
49
-
50
- <orchestrator>
51
- Your goal is to solve user requests by delegating tasks to expert sub-agents.
52
-
53
- **Directives**:
54
- 1. **Delegate**: Use \`delegateTask\` for any task matching an agent's description.
55
- 3. **Context**: Provide a clear, detailed task for the sub-agent. Pass any relevant user attachments.
56
- 4. **Report**: Summarize the sub-agent's work concisely for the user.
57
- 5. **Memory**: Use your memory tools (\`remember\`, \`recall\`) to maintain context across sessions.
58
- 6. **State**: Use \`updateSessionState\` to modify any value in the <session_state>.
59
- </orchestrator>
60
-
61
- ${statePrompt}
62
-
63
- <agents>
64
- ${agentDescriptions}
65
- </agents>${memoryPrompt}`;
66
- // console.log("finalSystemPrompt:::::", finalSystemPrompt);
67
- return finalSystemPrompt;
68
- },
69
- toolDefinitions: {
70
- ...memoryToolDefinitions,
71
- delegateTask: {
72
- description: `Delegate a task to a specialized expert agent.`,
73
- inputSchema: z.object({
74
- agentId: z.enum(agentIds).describe("The ID of the agent to use"),
75
- task: z.string().describe("The task for the agent to perform"),
76
- stateKey: z.string().optional().describe("Optional key to store structured JSON result in the session state"),
77
- attachments: z.array(z.object({
78
- id: z.string(),
79
- name: z.string(),
80
- mimeType: z.string(),
81
- size: z.number(),
82
- url: z.string(),
83
- })).optional().describe("Attachments to pass through to the agent"),
84
- }),
85
- },
86
- updateSessionState: {
87
- description: "Update a value in the session state using a JSON path.",
88
- inputSchema: z.object({
89
- path: z.string().describe("The JSON path to the value (e.g. 'project_plan.todos.0.status')"),
90
- value: z.any().describe("The new value to set"),
91
- }),
92
- },
93
- },
94
- }));
95
- };
96
- }
@@ -1,74 +0,0 @@
1
- import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
2
- import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
3
- import { approvalPlugin } from "../plugins/approval/index.js";
4
- import { osAgent } from "../agents/os-agent.js";
5
- import { agentCreatorAgent } from "../agents/agent-creator.js";
6
- import { PluginRegistry, discoverPlugins } from "../registry/index.js";
7
- import path from "node:path";
8
- /**
9
- * Build the unified plugin registry.
10
- *
11
- * Registers built-in tools and agents, then discovers community
12
- * plugins (tools + agents) from ~/.openbot/plugins/.
13
- */
14
- export async function setupPluginRegistry(resolvedBaseDir, model, options) {
15
- const registry = new PluginRegistry();
16
- // ── Built-in tools ───────────────────────────────────────────────
17
- registry.register({
18
- id: "shell",
19
- name: "shell",
20
- description: "Execute shell commands",
21
- type: "tool",
22
- toolDefinitions: shellToolDefinitions,
23
- plugin: () => shellPlugin({ cwd: process.cwd() }),
24
- isBuiltIn: true,
25
- });
26
- registry.register({
27
- id: "file-system",
28
- name: "file-system",
29
- description: "Read, write, list, and delete files",
30
- type: "tool",
31
- toolDefinitions: fileSystemToolDefinitions,
32
- plugin: () => fileSystemPlugin({ baseDir: "/" }),
33
- isBuiltIn: true,
34
- });
35
- registry.register({
36
- id: "approval",
37
- name: "approval",
38
- description: "Require user approval for specific actions",
39
- type: "tool",
40
- toolDefinitions: {},
41
- plugin: (opts) => approvalPlugin(opts),
42
- isBuiltIn: true,
43
- });
44
- // ── Built-in agents ──────────────────────────────────────────────
45
- registry.register({
46
- id: "os",
47
- name: "os",
48
- description: "Handles shell commands and file system operations",
49
- type: "agent",
50
- capabilities: {
51
- ...Object.fromEntries(Object.entries(shellToolDefinitions).map(([k, v]) => [k, v.description])),
52
- ...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
53
- },
54
- plugin: osAgent({ model }),
55
- isBuiltIn: true,
56
- });
57
- registry.register({
58
- id: "agent-creator",
59
- name: "agent-creator",
60
- description: "Helps the user create and update custom OpenBot agents via natural language.",
61
- type: "agent",
62
- capabilities: {
63
- ...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
64
- },
65
- plugin: agentCreatorAgent({ model }),
66
- isBuiltIn: true,
67
- });
68
- // ── Custom agents and plugins ────────────────────────────────────
69
- const agentsDir = path.join(resolvedBaseDir, "agents");
70
- const pluginsDir = path.join(resolvedBaseDir, "plugins");
71
- await discoverPlugins(pluginsDir, registry, model, options);
72
- await discoverPlugins(agentsDir, registry, model, options);
73
- return registry;
74
- }
@@ -1,191 +0,0 @@
1
- function summarizeAgentEventValue(event) {
2
- if (!event)
3
- return undefined;
4
- const value = event?.data?.result ?? event?.data?.content ?? event?.data?.message ?? event?.data;
5
- if (typeof value === "string")
6
- return value;
7
- if (typeof value === "object" && value !== null)
8
- return JSON.stringify(value, null, 2);
9
- return undefined;
10
- }
11
- export async function* runOpenBot(event, context, managerRuntime, agentRuntimes, registry) {
12
- const { state } = context;
13
- const allAgents = registry.getAgents();
14
- // Initialize state
15
- if (!state.messages)
16
- state.messages = [];
17
- if (!state.agentStates)
18
- state.agentStates = {};
19
- // 1. Route non-user events directly (pluggable).
20
- // If an event is tagged with meta.agentName, send it to that agent runtime.
21
- // Otherwise, pass it to manager runtime unchanged.
22
- if (event.type !== "agent:input") {
23
- const targetAgent = event.meta?.agentName;
24
- if (targetAgent) {
25
- const runtime = agentRuntimes.get(targetAgent);
26
- if (runtime) {
27
- if (!state.agentStates[targetAgent])
28
- state.agentStates[targetAgent] = {};
29
- let resumedOutput = "";
30
- for await (const agentChunk of runtime.run(event, {
31
- runId: context.runId,
32
- state: state.agentStates[targetAgent],
33
- })) {
34
- // Preserve sub-agent attribution when resuming after approval/deny
35
- // so UI does not fall back to manager identity.
36
- yield {
37
- ...agentChunk,
38
- meta: {
39
- ...agentChunk?.meta,
40
- ...(event.meta?.delegationId ? { delegationId: event.meta.delegationId } : {}),
41
- agentName: targetAgent,
42
- },
43
- };
44
- if (agentChunk.type === "agent:output" || agentChunk.type === "action:result") {
45
- const summary = summarizeAgentEventValue(agentChunk);
46
- if (summary) {
47
- if (resumedOutput)
48
- resumedOutput += "\n\n";
49
- resumedOutput += summary;
50
- }
51
- }
52
- }
53
- // Resolve pending delegated tool call after approval follow-up.
54
- const maybeApprovalId = event?.data?.id;
55
- const shouldResolvePending = (event.type === "action:approve" || event.type === "action:deny")
56
- && typeof maybeApprovalId === "string";
57
- if (shouldResolvePending) {
58
- const pending = state.pendingAgentTasks?.[maybeApprovalId];
59
- if (pending) {
60
- const wasDenied = event.type === "action:deny";
61
- const delegateResult = wasDenied
62
- ? { error: "Action denied by user", denied: true }
63
- : (resumedOutput || "Task completed with no output.");
64
- delete state.pendingAgentTasks[maybeApprovalId];
65
- yield {
66
- type: "delegation:end",
67
- meta: { delegationId: pending.delegationId, agentName: pending.agentName },
68
- data: {
69
- agent: pending.agentName,
70
- result: wasDenied ? "Action denied by user." : (resumedOutput || "Task completed."),
71
- },
72
- };
73
- yield* managerRuntime.run({
74
- type: "action:result",
75
- data: {
76
- action: "delegateTask",
77
- result: delegateResult,
78
- toolCallId: pending.toolCallId,
79
- success: !wasDenied,
80
- halt: wasDenied,
81
- },
82
- }, {
83
- runId: context.runId,
84
- state: state,
85
- });
86
- }
87
- }
88
- return;
89
- }
90
- }
91
- yield* managerRuntime.run(event, {
92
- runId: context.runId,
93
- state: state,
94
- });
95
- return;
96
- }
97
- // 2. Direct agent routing for user input (e.g. "@os list files" or "@Codex Agent list files")
98
- if (event.type === "agent:input") {
99
- const content = event.data.content;
100
- if (content?.trim().startsWith("@")) {
101
- const trimmedContent = content.trim();
102
- const afterAt = trimmedContent.slice(1);
103
- // Find the longest matching agent (by ID or Name) at the start of the message
104
- // This handles agent names with spaces like "Codex Agent"
105
- let bestMatch;
106
- for (const agent of allAgents) {
107
- const idMatches = afterAt.toLowerCase().startsWith(agent.id.toLowerCase());
108
- const nameMatches = afterAt.toLowerCase().startsWith(agent.name.toLowerCase());
109
- if (idMatches || nameMatches) {
110
- const matchPrefix = idMatches ? agent.id : agent.name;
111
- const prefixLength = matchPrefix.length;
112
- // Next char must be space, end of string, or the match length must be at least
113
- // the current best match length (prefer longer names like "Codex Agent" over "Codex")
114
- const nextChar = afterAt[prefixLength];
115
- if (!nextChar || nextChar === " ") {
116
- if (!bestMatch || prefixLength > bestMatch.prefixLength) {
117
- bestMatch = { id: agent.id, name: agent.name, prefixLength };
118
- }
119
- }
120
- }
121
- }
122
- if (bestMatch) {
123
- const targetAgent = bestMatch.id;
124
- const remaining = afterAt.slice(bestMatch.prefixLength).trim();
125
- const runtime = agentRuntimes.get(targetAgent);
126
- if (runtime) {
127
- if (!state.agentStates[targetAgent])
128
- state.agentStates[targetAgent] = {};
129
- const agentEvent = {
130
- ...event,
131
- data: {
132
- content: remaining || "Hello",
133
- attachments: event.data.attachments,
134
- },
135
- };
136
- let lastAgentOutput = "";
137
- for await (const agentChunk of runtime.run(agentEvent, {
138
- runId: context.runId,
139
- state: state.agentStates[targetAgent],
140
- })) {
141
- if (agentChunk.type === "agent:output" || agentChunk.type === "agent:output-delta") {
142
- const summary = summarizeAgentEventValue(agentChunk);
143
- if (summary)
144
- lastAgentOutput = summary;
145
- }
146
- yield {
147
- ...agentChunk,
148
- meta: {
149
- ...agentChunk?.meta,
150
- agentName: targetAgent,
151
- },
152
- };
153
- }
154
- // Direct "@agent" routing bypasses manager handlers entirely.
155
- // Trigger manager-side post-processing (e.g. topic/title generation)
156
- // without producing a manager reply.
157
- if (!state.title) {
158
- for await (const _ of managerRuntime.run({
159
- type: "agent:output",
160
- meta: { agentName: targetAgent },
161
- data: { content: lastAgentOutput || "" },
162
- }, {
163
- runId: context.runId,
164
- state: state,
165
- })) {
166
- // side-effects only
167
- }
168
- }
169
- return;
170
- }
171
- }
172
- else {
173
- // If the user used @ but the agent wasn't found, stop here to avoid
174
- // falling back to the manager and burning tokens for a failed routing attempt.
175
- const agentPrefixMatch = afterAt.split(" ")[0];
176
- yield {
177
- type: "agent:output",
178
- data: {
179
- content: `Agent "@${agentPrefixMatch}" not found. Available agents:\n${allAgents.map(a => `- ${a.name} (@${a.id})`).join("\n")}`
180
- },
181
- };
182
- return;
183
- }
184
- }
185
- }
186
- // 3. Default routing: translate user input to manager input
187
- yield* managerRuntime.run({ ...event, type: "agent:input" }, {
188
- runId: context.runId,
189
- state: state,
190
- });
191
- }
@@ -1,29 +0,0 @@
1
- import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
2
- /**
3
- * Initial application layout handler
4
- */
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
- }
13
- const tab = event.data?.tab || "chat";
14
- // For init, return both sidebar and content
15
- yield {
16
- type: "ui",
17
- meta: {
18
- type: "sidebar",
19
- },
20
- data: await sidebarOnlyUI({ sessionId: state.sessionId })
21
- };
22
- yield {
23
- type: "ui",
24
- meta: {
25
- type: "content",
26
- },
27
- data: await tabOnlyUI({ tab })
28
- };
29
- }
@@ -1,21 +0,0 @@
1
- import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
2
- /**
3
- * Session change handler
4
- */
5
- export async function* sessionChangeHandler(event, { state }) {
6
- const tab = event.data?.tab || "chat";
7
- yield {
8
- type: "ui",
9
- meta: {
10
- type: "sidebar",
11
- },
12
- data: await sidebarOnlyUI({ sessionId: state.sessionId })
13
- };
14
- yield {
15
- type: "ui",
16
- meta: {
17
- type: "content",
18
- },
19
- data: await tabOnlyUI({ tab })
20
- };
21
- }
@@ -1,47 +0,0 @@
1
- import { saveConfig } from "../config.js";
2
- import { tabOnlyUI } from "../ui/layout.js";
3
- import { exec } from "node:child_process";
4
- import os from "node:os";
5
- /**
6
- * Handle settings updates (like API keys)
7
- */
8
- export async function* updateSettingsHandler(event, { state }) {
9
- const { openai_api_key, anthropic_api_key, model } = event.data;
10
- const updates = {};
11
- if (model) {
12
- updates.model = model.trim();
13
- }
14
- if (openai_api_key && openai_api_key !== "••••••••••••••••") {
15
- updates.openaiApiKey = openai_api_key.trim();
16
- }
17
- if (anthropic_api_key && anthropic_api_key !== "••••••••••••••••") {
18
- updates.anthropicApiKey = anthropic_api_key.trim();
19
- }
20
- if (Object.keys(updates).length > 0) {
21
- saveConfig(updates);
22
- // Refresh the settings UI to show the "saved" state (masking the keys)
23
- yield {
24
- type: "ui",
25
- meta: {
26
- type: "content",
27
- },
28
- data: await tabOnlyUI({ tab: "settings" })
29
- };
30
- }
31
- }
32
- /**
33
- * Handle opening an agent's folder in the native file explorer
34
- */
35
- export async function* openAgentFolderHandler(event) {
36
- const { folder } = event.data;
37
- if (folder) {
38
- const command = os.platform() === "win32" ? `explorer "${folder}"` :
39
- os.platform() === "darwin" ? `open "${folder}"` :
40
- `xdg-open "${folder}"`;
41
- exec(command, (error) => {
42
- if (error) {
43
- console.error(`Failed to open folder: ${error.message}`);
44
- }
45
- });
46
- }
47
- }
@@ -1,14 +0,0 @@
1
- import { tabOnlyUI } from "../ui/layout.js";
2
- /**
3
- * Tab change handler
4
- */
5
- export async function* tabChangeHandler(event, { state }) {
6
- const tab = event.data?.tab || "chat";
7
- yield {
8
- type: "ui",
9
- meta: {
10
- type: "content",
11
- },
12
- data: await tabOnlyUI({ tab })
13
- };
14
- }
@@ -1,156 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
- import { execFileSync } from "node:child_process";
4
- import { tmpdir } from "node:os";
5
- import { resolvePath, DEFAULT_BASE_DIR, loadConfig } from "./config.js";
6
- import { readAgentConfig, ensurePluginReady } from "./registry/plugin-loader.js";
7
- const BUILT_IN_PLUGIN_NAMES = new Set(["shell", "file-system", "approval"]);
8
- function run(command, args, options) {
9
- execFileSync(command, args, {
10
- cwd: options?.cwd,
11
- stdio: options?.quiet ? "ignore" : "inherit",
12
- });
13
- }
14
- function log(message, quiet) {
15
- if (!quiet)
16
- console.log(message);
17
- }
18
- function getBaseDir() {
19
- const cfg = loadConfig();
20
- const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
21
- return resolvePath(baseDir);
22
- }
23
- async function directoryExists(targetPath) {
24
- return fs.access(targetPath).then(() => true).catch(() => false);
25
- }
26
- export function checkGitHubRepo(repo) {
27
- try {
28
- const url = `https://github.com/${repo}.git`;
29
- run("git", ["ls-remote", url], { quiet: true });
30
- return true;
31
- }
32
- catch {
33
- return false;
34
- }
35
- }
36
- export function checkNpmPackage(pkg) {
37
- try {
38
- run("npm", ["show", pkg, "version"], { quiet: true });
39
- return true;
40
- }
41
- catch {
42
- return false;
43
- }
44
- }
45
- export function parseSource(source) {
46
- const normalized = source.trim();
47
- const isGithub = (normalized.includes("/") || normalized.startsWith("github:"))
48
- && !normalized.startsWith("/")
49
- && !normalized.startsWith(".");
50
- const isNpm = normalized.startsWith("@") || normalized.startsWith("npm:");
51
- if (isGithub) {
52
- return {
53
- type: "github",
54
- value: normalized.startsWith("github:") ? normalized.slice(7) : normalized,
55
- };
56
- }
57
- if (isNpm) {
58
- return {
59
- type: "npm",
60
- value: normalized.startsWith("npm:") ? normalized.slice(4) : normalized,
61
- };
62
- }
63
- return { type: "local", value: path.resolve(normalized) };
64
- }
65
- export async function installExtension(type, source, options = {}) {
66
- const quiet = !!options.quiet;
67
- const baseDir = getBaseDir();
68
- const targetRoot = path.join(baseDir, type === "agent" ? "agents" : "plugins");
69
- await fs.mkdir(targetRoot, { recursive: true });
70
- // 1. Determine the folder name (the "id") - ALWAYS based on source or explicit id
71
- let id = options.id;
72
- if (!id) {
73
- if (source.type === "github") {
74
- id = path.basename(source.value); // e.g. "agent-browser"
75
- }
76
- else if (source.type === "npm") {
77
- id = source.value.split("/").pop(); // e.g. "@melony/plugin-test" -> "plugin-test"
78
- }
79
- else {
80
- id = path.basename(source.value);
81
- }
82
- }
83
- const targetDir = path.join(targetRoot, id);
84
- const tempDir = path.join(tmpdir(), `openbot-install-${Date.now()}-${id}`);
85
- try {
86
- log(`📦 Installing ${type} "${id}" from ${source.type}...`, quiet);
87
- if (source.type === "github") {
88
- const url = `https://github.com/${source.value}.git`;
89
- run("git", ["clone", "--depth", "1", url, tempDir], { quiet });
90
- }
91
- else if (source.type === "npm") {
92
- await fs.mkdir(tempDir, { recursive: true });
93
- run("npm", ["install", source.value, "--prefix", tempDir], { quiet });
94
- const pkgFolder = path.join(tempDir, "node_modules", source.value);
95
- const moveTemp = path.join(tmpdir(), `openbot-npm-move-${Date.now()}`);
96
- await fs.rename(pkgFolder, moveTemp);
97
- await fs.rm(tempDir, { recursive: true, force: true });
98
- await fs.rename(moveTemp, tempDir);
99
- }
100
- else {
101
- await fs.mkdir(tempDir, { recursive: true });
102
- await fs.cp(source.value, tempDir, { recursive: true });
103
- }
104
- if (await directoryExists(targetDir)) {
105
- log(`⚠️ Removing existing folder: ${targetDir}`, quiet);
106
- await fs.rm(targetDir, { recursive: true, force: true });
107
- }
108
- await fs.rename(tempDir, targetDir);
109
- log(`✅ Installed to: ${targetDir}`, quiet);
110
- // Prepare dependencies and build
111
- await ensurePluginReady(targetDir);
112
- // If it's an agent, check for missing plugins
113
- if (type === "agent") {
114
- await installMissingPluginsFromAgent(targetDir, { quiet });
115
- }
116
- log(`🎉 Successfully installed ${type}: ${id}`, quiet);
117
- return id;
118
- }
119
- catch (error) {
120
- await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
121
- throw error;
122
- }
123
- }
124
- // Backward compatibility aliases
125
- export const installPluginFromSource = (source, opts) => installExtension("plugin", source, opts);
126
- export const installAgentFromSource = (source, opts) => installExtension("agent", source, opts);
127
- export const parsePluginInstallSource = parseSource;
128
- export const parseAgentInstallSource = parseSource;
129
- export async function installMissingPluginsFromAgent(agentFolder, options = {}) {
130
- const quiet = !!options.quiet;
131
- const config = await readAgentConfig(agentFolder);
132
- const baseDir = getBaseDir();
133
- for (const pluginItem of config.plugins || []) {
134
- const pluginName = typeof pluginItem === "string" ? pluginItem : pluginItem.name;
135
- if (!pluginName || BUILT_IN_PLUGIN_NAMES.has(pluginName))
136
- continue;
137
- // Check if it already exists as a folder in plugins/
138
- const pluginPath = path.join(baseDir, "plugins", pluginName);
139
- const prefixedPluginPath = path.join(baseDir, "plugins", `plugin-${pluginName}`);
140
- if (await (directoryExists(pluginPath)) || await (directoryExists(prefixedPluginPath))) {
141
- continue;
142
- }
143
- log(`🔍 Agent needs plugin "${pluginName}". Searching...`, quiet);
144
- const ghRepo = `meetopenbot/plugin-${pluginName}`;
145
- if (checkGitHubRepo(ghRepo)) {
146
- await installExtension("plugin", { type: "github", value: ghRepo }, { quiet });
147
- continue;
148
- }
149
- const npmPkg = `@melony/plugin-${pluginName}`;
150
- if (checkNpmPackage(npmPkg)) {
151
- await installExtension("plugin", { type: "npm", value: npmPkg }, { quiet });
152
- continue;
153
- }
154
- log(`⚠️ Could not find plugin "${pluginName}" for this agent.`, quiet);
155
- }
156
- }