openbot 0.4.7 → 0.5.5
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/.claude/agents/runtime-security-reviewer.md +32 -0
- package/.claude/hooks/format.sh +18 -0
- package/.claude/hooks/protect-secrets.sh +23 -0
- package/.claude/settings.json +26 -0
- package/.claude/skills/deploy/SKILL.md +26 -0
- package/.claude/skills/new-plugin/SKILL.md +46 -0
- package/.dockerignore +9 -0
- package/.mcp.json +8 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +51 -0
- package/Dockerfile +41 -0
- package/deploy/README.md +59 -0
- package/deploy/entrypoint.sh +6 -0
- package/deploy/fly.toml +36 -0
- package/dist/app/bootstrap.js +29 -0
- package/dist/app/cli.js +5 -1
- package/dist/app/cloud-mode.js +15 -0
- package/dist/app/config.js +61 -16
- package/dist/app/ensure-default-stack.js +54 -0
- package/dist/app/openbot-plugin.js +4 -0
- package/dist/app/server.js +37 -6
- package/dist/harness/index.js +3 -0
- package/dist/plugins/approval/index.js +1 -1
- package/dist/plugins/bash/index.js +386 -164
- package/dist/plugins/memory/service.js +2 -5
- package/dist/plugins/openbot/context.js +17 -1
- package/dist/plugins/openbot/index.js +23 -7
- package/dist/plugins/openbot/model.js +20 -0
- package/dist/plugins/openbot/runtime.js +45 -57
- package/dist/plugins/openbot/system-prompt.js +43 -8
- package/dist/plugins/preview/index.js +267 -0
- package/dist/plugins/storage/index.js +3 -325
- package/dist/plugins/storage/service.js +61 -18
- package/dist/plugins/todo/index.js +131 -38
- package/dist/plugins/todo/service.js +93 -0
- package/dist/plugins/ui/index.js +8 -1
- package/dist/services/plugins/host.js +21 -0
- package/dist/services/plugins/model-registry.js +126 -0
- package/dist/services/plugins/registry.js +28 -42
- package/dist/services/plugins/service.js +2 -6
- package/docs/agents.md +20 -2
- package/docs/architecture.md +1 -1
- package/docs/plugins.md +13 -3
- package/docs/templates/AGENT.example.md +8 -14
- package/package.json +5 -2
- package/pnpm-workspace.yaml +2 -0
- package/src/app/bootstrap.ts +43 -0
- package/src/app/cli.ts +5 -1
- package/src/app/cloud-mode.ts +26 -0
- package/src/app/config.ts +71 -19
- package/src/app/ensure-default-stack.ts +63 -0
- package/src/app/openbot-plugin.ts +5 -0
- package/src/app/server.ts +42 -6
- package/src/app/types.ts +158 -32
- package/src/harness/index.ts +4 -0
- package/src/plugins/memory/service.ts +2 -5
- package/src/plugins/storage/index.ts +3 -371
- package/src/plugins/storage/service.ts +81 -22
- package/src/services/plugins/domain.ts +2 -0
- package/src/services/plugins/host.ts +36 -0
- package/src/services/plugins/model-registry.ts +178 -0
- package/src/services/plugins/registry.ts +30 -43
- package/src/services/plugins/service.ts +8 -17
- package/src/services/plugins/types.ts +36 -2
- package/src/services/todo/types.ts +12 -0
- package/dist/agents/openbot/index.js +0 -76
- package/dist/agents/openbot/middleware/approval.js +0 -132
- package/dist/agents/openbot/runtime.js +0 -289
- package/dist/agents/openbot/system-prompt.js +0 -32
- package/dist/agents/openbot/tools/delegation.js +0 -78
- package/dist/agents/openbot/tools/mcp.js +0 -99
- package/dist/agents/openbot/tools/shell.js +0 -91
- package/dist/agents/openbot/tools/storage.js +0 -75
- package/dist/agents/openbot/tools/ui.js +0 -176
- package/dist/agents/system.js +0 -33
- package/dist/bus/agent-package.js +0 -1
- package/dist/bus/plugin.js +0 -1
- package/dist/bus/services.js +0 -754
- package/dist/harness/agent-harness.js +0 -45
- package/dist/harness/agent-invoke-run.js +0 -44
- package/dist/harness/agent-turn.js +0 -99
- package/dist/harness/channel-participants.js +0 -40
- package/dist/harness/constants.js +0 -2
- package/dist/harness/context-meter.js +0 -97
- package/dist/harness/context.js +0 -363
- package/dist/harness/dispatch.js +0 -144
- package/dist/harness/dispatcher.js +0 -156
- package/dist/harness/event-normalizer.js +0 -59
- package/dist/harness/history.js +0 -177
- package/dist/harness/mcp.js +0 -61
- package/dist/harness/orchestration.js +0 -88
- package/dist/harness/orchestrator.js +0 -149
- package/dist/harness/participants.js +0 -22
- package/dist/harness/process.js +0 -29
- package/dist/harness/queue-processor.js +0 -187
- package/dist/harness/run-harness.js +0 -154
- package/dist/harness/run.js +0 -98
- package/dist/harness/runtime-factory.js +0 -73
- package/dist/harness/runtime.js +0 -57
- package/dist/harness/todo-advance.js +0 -93
- package/dist/harness/todo-dispatch.js +0 -51
- package/dist/harness/todos.js +0 -5
- package/dist/harness/turn.js +0 -79
- package/dist/harness/types.js +0 -1
- package/dist/plugins/ai-sdk/index.js +0 -34
- package/dist/plugins/ai-sdk/runtime.js +0 -395
- package/dist/plugins/ai-sdk/system-prompt.js +0 -18
- package/dist/plugins/ai-sdk.js +0 -331
- package/dist/plugins/approval.js +0 -163
- package/dist/plugins/delegation.js +0 -108
- package/dist/plugins/mcp/index.js +0 -108
- package/dist/plugins/mcp.js +0 -140
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell.js +0 -123
- package/dist/plugins/storage-tools/index.js +0 -76
- package/dist/plugins/storage.js +0 -737
- package/dist/plugins/thread-namer/index.js +0 -72
- package/dist/plugins/threads/index.js +0 -114
- package/dist/plugins/ui.js +0 -211
- package/dist/plugins/workflow/index.js +0 -65
- package/dist/registry/agents.js +0 -138
- package/dist/registry/plugins.js +0 -110
- package/dist/services/agent-packages.js +0 -103
- package/dist/services/memory.js +0 -152
- package/dist/services/plugins.js +0 -98
- package/dist/services/storage.js +0 -1028
- package/dist/workflow/service.js +0 -106
- package/dist/workflow/types.js +0 -3
- package/src/plugins/approval/index.ts +0 -147
- package/src/plugins/bash/index.ts +0 -232
- package/src/plugins/delegation/index.ts +0 -153
- package/src/plugins/memory/index.ts +0 -182
- package/src/plugins/openbot/context.ts +0 -121
- package/src/plugins/openbot/history.ts +0 -158
- package/src/plugins/openbot/index.ts +0 -80
- package/src/plugins/openbot/runtime.ts +0 -526
- package/src/plugins/openbot/system-prompt.ts +0 -22
- package/src/plugins/ui/index.ts +0 -123
- /package/dist/{bus → services/todo}/types.js +0 -0
package/docs/plugins.md
CHANGED
|
@@ -39,6 +39,14 @@ same agent into a single map and passes it to every plugin via `context.tools`.
|
|
|
39
39
|
Runtime plugins read it; tool plugins ignore it. First plugin wins on tool
|
|
40
40
|
name collisions.
|
|
41
41
|
|
|
42
|
+
### Tool results
|
|
43
|
+
|
|
44
|
+
When a tool handler finishes, yield `action:<tool>:result` with
|
|
45
|
+
`data.output: string`. The OpenBot runtime feeds that string back into the
|
|
46
|
+
model as the tool result (`history.ts`). Structured fields (e.g. `list`,
|
|
47
|
+
`success`) are fine alongside it, but without `output` the model only sees
|
|
48
|
+
`"No output"`.
|
|
49
|
+
|
|
42
50
|
## Built-in plugins
|
|
43
51
|
|
|
44
52
|
| Id | Role | Notes |
|
|
@@ -49,6 +57,7 @@ name collisions.
|
|
|
49
57
|
| `bash` | Tool | `bash` (inbuilt in `openbot`) |
|
|
50
58
|
| `storage` | Tool | `create_channel`, `patch_*`, ... (inbuilt in `openbot`) |
|
|
51
59
|
| `memory` | Tool | `remember`, `recall`, `forget` (inbuilt in `openbot`) |
|
|
60
|
+
| `todo` | Tool | `todo_write`, `todo_read` (inbuilt in `openbot`) |
|
|
52
61
|
| `plugin-manager`| Infra | Marketplace list, npm plugin install/uninstall, agent install |
|
|
53
62
|
|
|
54
63
|
## Batteries-included: `openbot` runtime
|
|
@@ -58,6 +67,7 @@ to be isolated and self-contained, providing a core ecosystem of inbuilt tools:
|
|
|
58
67
|
|
|
59
68
|
- **Bash**: Stateful system tasks and file operations.
|
|
60
69
|
- **Memory**: Long-term durable fact storage.
|
|
70
|
+
- **Todo**: Thread-scoped multi-step task checklist.
|
|
61
71
|
- **Storage**: Channel and thread management.
|
|
62
72
|
- **Delegation**: Calling upon other specialized agents.
|
|
63
73
|
- **Approval**: Gating protected actions behind UI confirmation.
|
|
@@ -71,7 +81,7 @@ plugins:
|
|
|
71
81
|
config:
|
|
72
82
|
model: openai/gpt-4o-mini
|
|
73
83
|
approval:
|
|
74
|
-
actions: [action:
|
|
84
|
+
actions: [action:shell_exec, action:create_channel]
|
|
75
85
|
```
|
|
76
86
|
|
|
77
87
|
A community plugin is just an npm package whose default export matches the
|
|
@@ -90,11 +100,11 @@ On first use OpenBot installs the package into
|
|
|
90
100
|
|
|
91
101
|
## Approval plugin
|
|
92
102
|
|
|
93
|
-
The `approval` plugin gates protected tool calls behind a UI confirmation widget. By default, it gates `action:
|
|
103
|
+
The `approval` plugin gates protected tool calls behind a UI confirmation widget. By default, it gates `action:shell_exec`.
|
|
94
104
|
|
|
95
105
|
```yaml
|
|
96
106
|
plugins:
|
|
97
107
|
- id: approval
|
|
98
108
|
config:
|
|
99
|
-
actions: [action:
|
|
109
|
+
actions: [action:shell_exec]
|
|
100
110
|
```
|
|
@@ -8,27 +8,21 @@ name: Example Agent
|
|
|
8
8
|
description: One-line description shown in agent pickers and lists.
|
|
9
9
|
|
|
10
10
|
# Plugins compose the agent. Order matters for tool collisions (first wins).
|
|
11
|
-
# At least one plugin must handle `agent:invoke
|
|
12
|
-
# `
|
|
13
|
-
#
|
|
14
|
-
# plugin can consume them.
|
|
11
|
+
# At least one plugin must handle `agent:invoke`. Use the monolithic runtime:
|
|
12
|
+
# `@meetopenbot/openbot` (batteries-included: shell, memory, todo, storage tools,
|
|
13
|
+
# delegation, approval, preview).
|
|
15
14
|
#
|
|
16
|
-
#
|
|
17
|
-
# storage-tools, approval.
|
|
15
|
+
# Core built-in plugin ids: storage, plugin-manager.
|
|
18
16
|
#
|
|
19
17
|
# Community plugins are referenced by their npm package name (e.g.
|
|
20
|
-
# `openbot-plugin-search` or `@
|
|
18
|
+
# `openbot-plugin-search` or `@meetopenbot/openbot`) and are auto-installed
|
|
21
19
|
# on first use into ~/.openbot/plugins/<id>/.
|
|
22
20
|
plugins:
|
|
23
|
-
- id: openbot
|
|
21
|
+
- id: '@meetopenbot/openbot'
|
|
24
22
|
config:
|
|
25
23
|
model: openai/gpt-4o-mini
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- id: storage
|
|
29
|
-
- id: approval
|
|
30
|
-
config:
|
|
31
|
-
actions: [action:bash]
|
|
24
|
+
approval:
|
|
25
|
+
actions: [action:shell_exec]
|
|
32
26
|
---
|
|
33
27
|
|
|
34
28
|
<!--
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "tsx watch src/app/cli.ts start",
|
|
11
11
|
"build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
|
|
12
|
-
"start": "node dist/app/cli.js start"
|
|
12
|
+
"start": "node dist/app/cli.js start",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.5"
|
|
13
15
|
},
|
|
14
16
|
"bin": {
|
|
15
17
|
"openbot": "./dist/app/cli.js"
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
"@types/express": "^4.17.21",
|
|
35
37
|
"@types/js-yaml": "^4.0.9",
|
|
36
38
|
"@types/node": "^20.10.1",
|
|
39
|
+
"prettier": "^3.4.2",
|
|
37
40
|
"tsx": "^4.21.0",
|
|
38
41
|
"typescript": "^5.9.3"
|
|
39
42
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
CONFIG_FILE,
|
|
5
|
+
DEFAULT_AGENTS_DIR,
|
|
6
|
+
DEFAULT_CHANNELS_DIR,
|
|
7
|
+
DEFAULT_PLUGINS_DIR,
|
|
8
|
+
getBaseDir,
|
|
9
|
+
getChannelsWorkspaceRoot,
|
|
10
|
+
getConfigDir,
|
|
11
|
+
type OpenBotconfig,
|
|
12
|
+
} from './config.js';
|
|
13
|
+
|
|
14
|
+
/** Ensure data directories and a minimal config exist (first boot on a Fly volume). */
|
|
15
|
+
export async function bootstrap(): Promise<void> {
|
|
16
|
+
const baseDir = getBaseDir();
|
|
17
|
+
const configDir = getConfigDir();
|
|
18
|
+
|
|
19
|
+
await fs.mkdir(path.join(baseDir, DEFAULT_AGENTS_DIR), { recursive: true });
|
|
20
|
+
await fs.mkdir(path.join(baseDir, DEFAULT_PLUGINS_DIR), { recursive: true });
|
|
21
|
+
await fs.mkdir(path.join(baseDir, DEFAULT_CHANNELS_DIR), { recursive: true });
|
|
22
|
+
await fs.mkdir(getChannelsWorkspaceRoot(), { recursive: true });
|
|
23
|
+
await fs.mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
24
|
+
|
|
25
|
+
const configPath = path.join(configDir, CONFIG_FILE);
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(configPath);
|
|
28
|
+
} catch {
|
|
29
|
+
const initial: OpenBotconfig = { baseDir };
|
|
30
|
+
|
|
31
|
+
const publicUrl = process.env.OPENBOT_PUBLIC_URL?.trim();
|
|
32
|
+
if (publicUrl) {
|
|
33
|
+
initial.publicUrl = publicUrl.replace(/\/$/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const workspaceDir = process.env.OPENBOT_CHANNELS_WORKSPACE_DIR?.trim();
|
|
37
|
+
if (workspaceDir) {
|
|
38
|
+
initial.channelsWorkspaceDir = getChannelsWorkspaceRoot();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await fs.writeFile(configPath, JSON.stringify(initial, null, 2), { mode: 0o600 });
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/app/cli.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { bootstrap } from './bootstrap.js';
|
|
4
|
+
import { ensureDefaultStack } from './ensure-default-stack.js';
|
|
3
5
|
import { startServer } from './server.js';
|
|
4
6
|
|
|
5
7
|
const program = new Command();
|
|
@@ -25,13 +27,15 @@ function checkNodeVersion() {
|
|
|
25
27
|
|
|
26
28
|
checkNodeVersion();
|
|
27
29
|
|
|
28
|
-
program.name('openbot').description('OpenBot CLI').version('0.
|
|
30
|
+
program.name('openbot').description('OpenBot CLI').version('0.5.5');
|
|
29
31
|
|
|
30
32
|
program
|
|
31
33
|
.command('start')
|
|
32
34
|
.description('Start the OpenBot harness')
|
|
33
35
|
.option('-p, --port <number>', 'Port to listen on')
|
|
34
36
|
.action(async (options) => {
|
|
37
|
+
await bootstrap();
|
|
38
|
+
await ensureDefaultStack();
|
|
35
39
|
await startServer(options);
|
|
36
40
|
});
|
|
37
41
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ORCHESTRATOR_AGENT_ID } from './agent-ids.js';
|
|
2
|
+
|
|
3
|
+
/** Platform-managed OpenBot (Fly). Self-hosted when unset. */
|
|
4
|
+
export const isCloudMode = (): boolean => process.env.OPENBOT_CLOUD_MODE === '1';
|
|
5
|
+
|
|
6
|
+
/** How the cloud system agent authenticates to LLM providers. */
|
|
7
|
+
export type OpenbotAuthMode = 'credits' | 'byok';
|
|
8
|
+
|
|
9
|
+
/** Default cloud openbot plugin auth mode. */
|
|
10
|
+
export const DEFAULT_CLOUD_OPENBOT_AUTH_MODE: OpenbotAuthMode = 'credits';
|
|
11
|
+
|
|
12
|
+
export function isCloudSystemAgent(agentId: string): boolean {
|
|
13
|
+
return isCloudMode() && agentId === ORCHESTRATOR_AGENT_ID;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseOpenbotAuthMode(value: unknown): OpenbotAuthMode {
|
|
17
|
+
return value === 'byok' ? 'byok' : 'credits';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function assertCloudSystemAgentPluginsMutable(
|
|
21
|
+
agentId: string,
|
|
22
|
+
plugins: unknown,
|
|
23
|
+
): void {
|
|
24
|
+
void agentId;
|
|
25
|
+
void plugins;
|
|
26
|
+
}
|
package/src/app/config.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface OpenBotconfig {
|
|
|
8
8
|
model?: string;
|
|
9
9
|
image?: string;
|
|
10
10
|
baseDir?: string;
|
|
11
|
+
/** Parent directory for per-channel working directories (user-facing workspace). */
|
|
12
|
+
channelsWorkspaceDir?: string;
|
|
11
13
|
port?: number;
|
|
12
14
|
/**
|
|
13
15
|
* Overrides the default public marketplace registry URL. If omitted or blank,
|
|
@@ -41,52 +43,102 @@ export function resolvePath(p: string) {
|
|
|
41
43
|
return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : path.resolve(p);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
function readConfigFile(configDir: string): OpenBotconfig {
|
|
47
|
+
const configPath = path.join(configDir, CONFIG_FILE);
|
|
48
|
+
if (fs.existsSync(configPath)) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8')) as OpenBotconfig;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`Warning: Failed to parse config at ${configPath}`, error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Directory that holds `config.json` (env `OPENBOT_BASE_DIR` or `~/.openbot`). */
|
|
59
|
+
export function getConfigDir(): string {
|
|
60
|
+
const envBase = process.env.OPENBOT_BASE_DIR?.trim();
|
|
61
|
+
if (envBase) {
|
|
62
|
+
return resolvePath(envBase);
|
|
63
|
+
}
|
|
64
|
+
return path.join(os.homedir(), '.openbot');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Resolved OpenBot data root (agents, channels, plugins, variables). */
|
|
68
|
+
export function getBaseDir(): string {
|
|
69
|
+
const envBase = process.env.OPENBOT_BASE_DIR?.trim();
|
|
70
|
+
if (envBase) {
|
|
71
|
+
return resolvePath(envBase);
|
|
72
|
+
}
|
|
73
|
+
const fileConfig = readConfigFile(getConfigDir());
|
|
74
|
+
return resolvePath(fileConfig.baseDir || DEFAULT_BASE_DIR);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Resolved parent directory for per-channel workspace folders. */
|
|
78
|
+
export function getChannelsWorkspaceRoot(): string {
|
|
79
|
+
const env = process.env.OPENBOT_CHANNELS_WORKSPACE_DIR?.trim();
|
|
80
|
+
if (env) {
|
|
81
|
+
return resolvePath(env);
|
|
82
|
+
}
|
|
83
|
+
const fileConfig = readConfigFile(getConfigDir());
|
|
84
|
+
if (fileConfig.channelsWorkspaceDir) {
|
|
85
|
+
return resolvePath(fileConfig.channelsWorkspaceDir);
|
|
86
|
+
}
|
|
87
|
+
return resolvePath(DEFAULT_CHANNELS_WORKSPACE_DIR);
|
|
88
|
+
}
|
|
89
|
+
|
|
44
90
|
/** Default absolute cwd for a channel when none is provided at creation time. */
|
|
45
91
|
export function getDefaultChannelCwd(channelId: string): string {
|
|
46
92
|
const id = channelId.trim();
|
|
47
93
|
if (!id) {
|
|
48
94
|
throw new Error('channelId is required');
|
|
49
95
|
}
|
|
50
|
-
return
|
|
96
|
+
return path.join(getChannelsWorkspaceRoot(), id);
|
|
51
97
|
}
|
|
52
98
|
|
|
53
99
|
export function loadConfig(): OpenBotconfig {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
const fileConfig = readConfigFile(getConfigDir());
|
|
101
|
+
const merged: OpenBotconfig = { ...fileConfig };
|
|
102
|
+
|
|
103
|
+
merged.baseDir = getBaseDir();
|
|
104
|
+
|
|
105
|
+
const envPublicUrl = process.env.OPENBOT_PUBLIC_URL?.trim();
|
|
106
|
+
if (envPublicUrl) {
|
|
107
|
+
merged.publicUrl = envPublicUrl.replace(/\/$/, '');
|
|
108
|
+
} else if (merged.publicUrl) {
|
|
109
|
+
merged.publicUrl = merged.publicUrl.replace(/\/$/, '');
|
|
61
110
|
}
|
|
62
|
-
|
|
111
|
+
|
|
112
|
+
merged.channelsWorkspaceDir = getChannelsWorkspaceRoot();
|
|
113
|
+
|
|
114
|
+
const envPort = process.env.PORT?.trim();
|
|
115
|
+
if (envPort && !Number.isNaN(Number(envPort))) {
|
|
116
|
+
merged.port = Number(envPort);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return merged;
|
|
63
120
|
}
|
|
64
121
|
|
|
65
122
|
export function saveConfig(config: Partial<OpenBotconfig>) {
|
|
66
|
-
const configDir =
|
|
123
|
+
const configDir = getConfigDir();
|
|
67
124
|
const configPath = path.join(configDir, CONFIG_FILE);
|
|
68
125
|
|
|
69
126
|
if (!fs.existsSync(configDir)) {
|
|
70
127
|
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
71
128
|
}
|
|
72
129
|
|
|
73
|
-
const currentConfig =
|
|
130
|
+
const currentConfig = readConfigFile(configDir);
|
|
74
131
|
const newConfig = { ...currentConfig, ...config };
|
|
75
132
|
|
|
76
133
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), { mode: 0o600 });
|
|
77
134
|
}
|
|
78
135
|
|
|
79
136
|
export function isConfigured(): boolean {
|
|
80
|
-
|
|
81
|
-
return fs.existsSync(configPath);
|
|
137
|
+
return fs.existsSync(path.join(getConfigDir(), CONFIG_FILE));
|
|
82
138
|
}
|
|
83
139
|
|
|
84
140
|
export function loadVariables(): { version: number; variables: StoredVariable[] } {
|
|
85
|
-
const
|
|
86
|
-
const variablesPath = path.join(
|
|
87
|
-
resolvePath(config.baseDir || DEFAULT_BASE_DIR),
|
|
88
|
-
VARIABLES_FILE,
|
|
89
|
-
);
|
|
141
|
+
const variablesPath = path.join(getBaseDir(), VARIABLES_FILE);
|
|
90
142
|
if (fs.existsSync(variablesPath)) {
|
|
91
143
|
return JSON.parse(fs.readFileSync(variablesPath, 'utf-8')) as {
|
|
92
144
|
version: number;
|
|
@@ -94,4 +146,4 @@ export function loadVariables(): { version: number; variables: StoredVariable[]
|
|
|
94
146
|
};
|
|
95
147
|
}
|
|
96
148
|
return { version: 1, variables: [] };
|
|
97
|
-
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import { ORCHESTRATOR_AGENT_ID } from './agent-ids.js';
|
|
6
|
+
import { DEFAULT_SYSTEM_AGENT_MODEL, OPENBOT_PLUGIN_ID } from './openbot-plugin.js';
|
|
7
|
+
import { DEFAULT_CLOUD_OPENBOT_AUTH_MODE, isCloudMode } from './cloud-mode.js';
|
|
8
|
+
import { DEFAULT_AGENTS_DIR, getBaseDir } from './config.js';
|
|
9
|
+
import { pluginService } from '../services/plugins/service.js';
|
|
10
|
+
|
|
11
|
+
function systemAgentMdPath(): string {
|
|
12
|
+
return path.join(getBaseDir(), DEFAULT_AGENTS_DIR, ORCHESTRATOR_AGENT_ID, 'AGENT.md');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function defaultSystemPluginConfig(): Record<string, unknown> {
|
|
16
|
+
const config: Record<string, unknown> = {
|
|
17
|
+
model: DEFAULT_SYSTEM_AGENT_MODEL,
|
|
18
|
+
};
|
|
19
|
+
if (isCloudMode()) {
|
|
20
|
+
config.authMode = DEFAULT_CLOUD_OPENBOT_AUTH_MODE;
|
|
21
|
+
}
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function ensureOpenbotPluginInstalled(): Promise<void> {
|
|
26
|
+
if (await pluginService.isInstalled(OPENBOT_PLUGIN_ID)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`[bootstrap] Installing ${OPENBOT_PLUGIN_ID} from npm`);
|
|
31
|
+
await pluginService.install({ packageName: OPENBOT_PLUGIN_ID });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function ensureSystemAgentMaterialized(): Promise<void> {
|
|
35
|
+
const agentMdPath = systemAgentMdPath();
|
|
36
|
+
if (existsSync(agentMdPath)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const agentDir = path.dirname(agentMdPath);
|
|
41
|
+
await fs.mkdir(agentDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const frontmatter = {
|
|
44
|
+
name: 'OpenBot',
|
|
45
|
+
description: 'First-party orchestration agent for OpenBot.',
|
|
46
|
+
plugins: [
|
|
47
|
+
{
|
|
48
|
+
id: OPENBOT_PLUGIN_ID,
|
|
49
|
+
config: defaultSystemPluginConfig(),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const content = matter.stringify('', frontmatter);
|
|
55
|
+
await fs.writeFile(agentMdPath, content, 'utf-8');
|
|
56
|
+
console.log(`[bootstrap] Materialized system agent at ${agentMdPath}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Install the default agent plugin from npm and materialize the system agent on first boot. */
|
|
60
|
+
export async function ensureDefaultStack(): Promise<void> {
|
|
61
|
+
await ensureOpenbotPluginInstalled();
|
|
62
|
+
await ensureSystemAgentMaterialized();
|
|
63
|
+
}
|
package/src/app/server.ts
CHANGED
|
@@ -8,7 +8,8 @@ import { createRequire } from 'module';
|
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
const pkg = require('../../package.json');
|
|
10
10
|
import { generateId } from 'melony';
|
|
11
|
-
import {
|
|
11
|
+
import { getBaseDir, loadConfig } from '../app/config.js';
|
|
12
|
+
import { isCloudMode } from './cloud-mode.js';
|
|
12
13
|
import { ActiveRunsSnapshotEvent, OpenBotEvent, OpenBotState } from './types.js';
|
|
13
14
|
import { processService } from '../services/process.js';
|
|
14
15
|
import { runAgent, STATE_AGENT_ID, ORCHESTRATOR_AGENT_ID } from '../harness/index.js';
|
|
@@ -46,8 +47,11 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
46
47
|
const config = loadConfig();
|
|
47
48
|
processService.syncWorkspaceVariablesToProcessEnv();
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
if (isCloudMode()) {
|
|
51
|
+
console.log('[server] Cloud mode enabled');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const openBotDir = getBaseDir();
|
|
51
55
|
const PORT = Number(options.port ?? config.port ?? process.env.PORT ?? 4132);
|
|
52
56
|
const app = express();
|
|
53
57
|
const clients: Map<string, express.Response[]> = new Map();
|
|
@@ -247,6 +251,23 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
247
251
|
|
|
248
252
|
app.use(cors());
|
|
249
253
|
|
|
254
|
+
const gatewayToken = process.env.OPENBOT_GATEWAY_TOKEN?.trim();
|
|
255
|
+
if (gatewayToken) {
|
|
256
|
+
app.use((req, res, next) => {
|
|
257
|
+
if (req.path === '/api/health' || req.method === 'OPTIONS') {
|
|
258
|
+
next();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const got = req.get('x-openbot-gateway-token');
|
|
263
|
+
if (got !== gatewayToken) {
|
|
264
|
+
res.status(401).json({ error: 'unauthorized' });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
next();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
250
271
|
const resolvePublicBaseUrl = () => getPublicBaseUrl(PORT, config.publicUrl);
|
|
251
272
|
|
|
252
273
|
app.use((req, res, next) => {
|
|
@@ -264,7 +285,7 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
264
285
|
});
|
|
265
286
|
|
|
266
287
|
app.get('/api/health', (req, res) => {
|
|
267
|
-
res.json({ status: 'ok', version: pkg.version });
|
|
288
|
+
res.json({ status: 'ok', version: pkg.version, apiVersion: 1 });
|
|
268
289
|
});
|
|
269
290
|
|
|
270
291
|
app.get('/api/events', (req, res) => {
|
|
@@ -683,10 +704,25 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
683
704
|
}
|
|
684
705
|
});
|
|
685
706
|
|
|
686
|
-
|
|
687
|
-
|
|
707
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
708
|
+
const server = app.listen(PORT, HOST, () => {
|
|
709
|
+
console.log(`\x1b[32mOpenBot server listening at http://${HOST}:${PORT}\x1b[0m`);
|
|
688
710
|
console.log(
|
|
689
711
|
`🌐 Visit \x1b[96m\x1b[1mhttps://openbot.one\x1b[0m to connect to this runtime and manage everything from there. ✨`,
|
|
690
712
|
);
|
|
691
713
|
});
|
|
714
|
+
|
|
715
|
+
const shutdown = (signal: string) => {
|
|
716
|
+
console.log(`\n[server] Received ${signal}, shutting down...`);
|
|
717
|
+
server.close(() => {
|
|
718
|
+
process.exit(0);
|
|
719
|
+
});
|
|
720
|
+
setTimeout(() => {
|
|
721
|
+
console.error('[server] Forced shutdown after timeout');
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}, 10_000).unref();
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
727
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
692
728
|
}
|