openbot 0.4.0 → 0.4.2
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/app/cli.js +1 -1
- package/dist/app/config.js +10 -0
- package/dist/app/server.js +200 -3
- package/dist/harness/index.js +18 -0
- package/dist/plugins/approval/index.js +35 -20
- package/dist/plugins/bash/index.js +195 -0
- package/dist/plugins/delegation/index.js +4 -2
- package/dist/plugins/openbot/context.js +54 -9
- package/dist/plugins/openbot/history.js +47 -1
- package/dist/plugins/openbot/index.js +43 -3
- package/dist/plugins/openbot/runtime.js +91 -27
- package/dist/plugins/openbot/system-prompt.js +21 -1
- package/dist/plugins/plugin-manager/index.js +87 -3
- package/dist/plugins/shell/index.js +2 -1
- package/dist/plugins/storage/files.js +67 -0
- package/dist/plugins/storage/index.js +184 -7
- package/dist/plugins/storage/service.js +201 -44
- package/dist/plugins/ui/index.js +109 -150
- package/dist/services/abort.js +43 -0
- package/dist/services/plugins/registry.js +5 -3
- package/dist/services/plugins/service.js +66 -11
- package/docs/agents.md +5 -8
- package/docs/architecture.md +1 -1
- package/docs/plugins.md +28 -7
- package/docs/templates/AGENT.example.md +4 -4
- package/package.json +1 -1
- package/src/app/cli.ts +1 -1
- package/src/app/config.ts +13 -0
- package/src/app/server.ts +235 -3
- package/src/app/types.ts +284 -14
- package/src/harness/index.ts +21 -0
- package/src/plugins/approval/index.ts +37 -20
- package/src/plugins/bash/index.ts +232 -0
- package/src/plugins/delegation/index.ts +5 -2
- package/src/plugins/openbot/context.ts +58 -9
- package/src/plugins/openbot/history.ts +52 -1
- package/src/plugins/openbot/index.ts +45 -3
- package/src/plugins/openbot/runtime.ts +121 -27
- package/src/plugins/openbot/system-prompt.ts +21 -1
- package/src/plugins/plugin-manager/index.ts +105 -3
- package/src/plugins/storage/files.ts +81 -0
- package/src/plugins/storage/index.ts +198 -8
- package/src/plugins/storage/service.ts +267 -44
- package/src/plugins/ui/index.ts +123 -0
- package/src/services/abort.ts +46 -0
- package/src/services/plugins/domain.ts +34 -1
- package/src/services/plugins/registry.ts +5 -3
- package/src/services/plugins/service.ts +136 -45
- package/src/services/plugins/types.ts +5 -1
- package/src/plugins/shell/index.ts +0 -123
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks in-flight agent runs so they can be cancelled.
|
|
3
|
+
*
|
|
4
|
+
* Runs are grouped by `channelId:threadId`. Delegated sub-agents run in the
|
|
5
|
+
* same channel/thread as their parent, so aborting that key stops the whole
|
|
6
|
+
* chain (parent + any delegated runs) in one shot.
|
|
7
|
+
*/
|
|
8
|
+
export const abortKey = (channelId, threadId) => `${channelId}:${threadId || ''}`;
|
|
9
|
+
class AbortRegistry {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.entries = new Map();
|
|
12
|
+
}
|
|
13
|
+
/** Register interest in a run. Returns a shared signal for the key. */
|
|
14
|
+
acquire(key) {
|
|
15
|
+
let entry = this.entries.get(key);
|
|
16
|
+
if (!entry) {
|
|
17
|
+
entry = { controller: new AbortController(), refs: 0 };
|
|
18
|
+
this.entries.set(key, entry);
|
|
19
|
+
}
|
|
20
|
+
entry.refs += 1;
|
|
21
|
+
return entry.controller.signal;
|
|
22
|
+
}
|
|
23
|
+
/** Release interest. Removes the entry once no runs reference it. */
|
|
24
|
+
release(key) {
|
|
25
|
+
const entry = this.entries.get(key);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return;
|
|
28
|
+
entry.refs -= 1;
|
|
29
|
+
if (entry.refs <= 0) {
|
|
30
|
+
this.entries.delete(key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Abort all runs for the key. Returns true if something was active. */
|
|
34
|
+
abort(key) {
|
|
35
|
+
const entry = this.entries.get(key);
|
|
36
|
+
if (!entry)
|
|
37
|
+
return false;
|
|
38
|
+
entry.controller.abort();
|
|
39
|
+
this.entries.delete(key);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const abortRegistry = new AbortRegistry();
|
|
@@ -2,22 +2,24 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { openbotPlugin } from '../../plugins/openbot/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { bashPlugin } from '../../plugins/bash/index.js';
|
|
6
6
|
import { storagePlugin } from '../../plugins/storage/index.js';
|
|
7
7
|
import { approvalPlugin } from '../../plugins/approval/index.js';
|
|
8
8
|
import { memoryPlugin } from '../../plugins/memory/index.js';
|
|
9
9
|
import { delegationPlugin } from '../../plugins/delegation/index.js';
|
|
10
|
+
import { uiPlugin } from '../../plugins/ui/index.js';
|
|
10
11
|
import { pluginManagerPlugin } from '../../plugins/plugin-manager/index.js';
|
|
11
12
|
import { DEFAULT_PLUGINS_DIR, DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../../app/config.js';
|
|
12
13
|
import { invalidatePlugin as clearResolvedPluginEntry, loadedCommunityPlugins, resolvedPluginCache, } from './plugin-cache.js';
|
|
13
14
|
let pluginsDir = null;
|
|
14
15
|
const BUILT_IN = {
|
|
15
16
|
[openbotPlugin.id]: openbotPlugin,
|
|
16
|
-
[
|
|
17
|
+
[bashPlugin.id]: bashPlugin,
|
|
17
18
|
[storagePlugin.id]: storagePlugin,
|
|
18
19
|
[approvalPlugin.id]: approvalPlugin,
|
|
19
20
|
[memoryPlugin.id]: memoryPlugin,
|
|
20
21
|
[delegationPlugin.id]: delegationPlugin,
|
|
22
|
+
[uiPlugin.id]: uiPlugin,
|
|
21
23
|
[pluginManagerPlugin.id]: pluginManagerPlugin,
|
|
22
24
|
};
|
|
23
25
|
/** Normalize a dynamically imported plugin module. Supports `plugin`, `default`. */
|
|
@@ -56,7 +58,7 @@ export function initPlugins(dir) {
|
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* Resolve a Plugin by id. The id is either:
|
|
59
|
-
* - a built-in id (e.g. "openbot", "
|
|
61
|
+
* - a built-in id (e.g. "openbot", "bash"), or
|
|
60
62
|
* - an npm package name (e.g. "openbot-plugin-foo" or "@scope/foo"),
|
|
61
63
|
* in which case the folder layout is `plugins/<id>/dist/index.js`.
|
|
62
64
|
*/
|
|
@@ -7,19 +7,27 @@ import { DEFAULT_PLUGINS_DIR, DEFAULT_BASE_DIR, DEFAULT_MARKETPLACE_REGISTRY_URL
|
|
|
7
7
|
import { invalidatePlugin } from './plugin-cache.js';
|
|
8
8
|
const execAsync = promisify(exec);
|
|
9
9
|
const DEFAULT_MARKETPLACE_AGENTS = [];
|
|
10
|
+
const DEFAULT_MARKETPLACE_CHANNELS = [];
|
|
10
11
|
function isRecord(value) {
|
|
11
12
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Parses JSON from a remote registry file. Supports either
|
|
15
|
-
* `{ "agents": [ ... ] }` or a top-level array.
|
|
16
|
+
* `{ "agents": [ ... ], "channels": [ ... ] }` or a top-level array (legacy agents-only).
|
|
16
17
|
*/
|
|
17
18
|
export function parseMarketplaceRegistryJson(data) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const isLegacyArray = Array.isArray(data);
|
|
20
|
+
const rawAgents = isLegacyArray
|
|
21
|
+
? data
|
|
22
|
+
: isRecord(data) && Array.isArray(data.agents)
|
|
23
|
+
? data.agents
|
|
24
|
+
: [];
|
|
25
|
+
const rawChannels = !isLegacyArray && isRecord(data) && Array.isArray(data.channels)
|
|
26
|
+
? data.channels
|
|
27
|
+
: isRecord(data) && Array.isArray(data.templates)
|
|
28
|
+
? data.templates
|
|
29
|
+
: [];
|
|
30
|
+
const agents = (Array.isArray(rawAgents) ? rawAgents : []).map((item, i) => {
|
|
23
31
|
if (!isRecord(item)) {
|
|
24
32
|
throw new Error(`agents[${i}]: expected object`);
|
|
25
33
|
}
|
|
@@ -59,8 +67,47 @@ export function parseMarketplaceRegistryJson(data) {
|
|
|
59
67
|
}
|
|
60
68
|
return listing;
|
|
61
69
|
});
|
|
70
|
+
const channels = (Array.isArray(rawChannels) ? rawChannels : []).map((item, i) => {
|
|
71
|
+
if (!isRecord(item)) {
|
|
72
|
+
throw new Error(`channels[${i}]: expected object`);
|
|
73
|
+
}
|
|
74
|
+
const id = item.id;
|
|
75
|
+
const name = item.name;
|
|
76
|
+
const description = item.description;
|
|
77
|
+
const participants = item.participants;
|
|
78
|
+
if (typeof id !== 'string' || !id)
|
|
79
|
+
throw new Error(`channels[${i}].id must be a non-empty string`);
|
|
80
|
+
if (typeof name !== 'string')
|
|
81
|
+
throw new Error(`channels[${i}].name must be a string`);
|
|
82
|
+
if (typeof description !== 'string')
|
|
83
|
+
throw new Error(`channels[${i}].description must be a string`);
|
|
84
|
+
if (!Array.isArray(participants))
|
|
85
|
+
throw new Error(`channels[${i}].participants must be an array`);
|
|
86
|
+
const listing = {
|
|
87
|
+
id,
|
|
88
|
+
name,
|
|
89
|
+
description,
|
|
90
|
+
participants: participants.filter((p) => typeof p === 'string'),
|
|
91
|
+
};
|
|
92
|
+
if (typeof item.image === 'string')
|
|
93
|
+
listing.image = item.image;
|
|
94
|
+
if (typeof item.spec === 'string')
|
|
95
|
+
listing.spec = item.spec;
|
|
96
|
+
if (isRecord(item.initialState))
|
|
97
|
+
listing.initialState = item.initialState;
|
|
98
|
+
if (Array.isArray(item.starterPrompts)) {
|
|
99
|
+
listing.starterPrompts = item.starterPrompts.map((p, j) => {
|
|
100
|
+
if (!isRecord(p) || typeof p.label !== 'string' || typeof p.prompt !== 'string') {
|
|
101
|
+
throw new Error(`channels[${i}].starterPrompts[${j}] must have label and prompt`);
|
|
102
|
+
}
|
|
103
|
+
return { label: p.label, prompt: p.prompt };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return listing;
|
|
107
|
+
});
|
|
108
|
+
return { agents, channels };
|
|
62
109
|
}
|
|
63
|
-
async function
|
|
110
|
+
async function fetchMarketplaceRegistryFromUrl(url) {
|
|
64
111
|
const res = await fetch(url, {
|
|
65
112
|
headers: { Accept: 'application/json' },
|
|
66
113
|
signal: AbortSignal.timeout(15000),
|
|
@@ -72,19 +119,27 @@ async function fetchMarketplaceAgentsFromUrl(url) {
|
|
|
72
119
|
return parseMarketplaceRegistryJson(json);
|
|
73
120
|
}
|
|
74
121
|
/**
|
|
75
|
-
* Resolves marketplace
|
|
122
|
+
* Resolves marketplace registry (agents and channels) from configured registry URL.
|
|
76
123
|
*/
|
|
77
|
-
export async function
|
|
124
|
+
export async function resolveMarketplaceRegistry() {
|
|
78
125
|
const { marketplaceRegistryUrl } = loadConfig();
|
|
79
126
|
const registryUrl = marketplaceRegistryUrl?.trim() || DEFAULT_MARKETPLACE_REGISTRY_URL;
|
|
80
127
|
try {
|
|
81
|
-
return await
|
|
128
|
+
return await fetchMarketplaceRegistryFromUrl(registryUrl);
|
|
82
129
|
}
|
|
83
130
|
catch (err) {
|
|
84
131
|
console.warn(`[plugins] marketplace registry fetch failed (${registryUrl}), using built-in list:`, err instanceof Error ? err.message : err);
|
|
85
|
-
return DEFAULT_MARKETPLACE_AGENTS;
|
|
132
|
+
return { agents: DEFAULT_MARKETPLACE_AGENTS, channels: DEFAULT_MARKETPLACE_CHANNELS };
|
|
86
133
|
}
|
|
87
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolves marketplace agent listings from configured registry URL.
|
|
137
|
+
* @deprecated Use resolveMarketplaceRegistry instead.
|
|
138
|
+
*/
|
|
139
|
+
export async function resolveMarketplaceAgentList() {
|
|
140
|
+
const registry = await resolveMarketplaceRegistry();
|
|
141
|
+
return registry.agents;
|
|
142
|
+
}
|
|
88
143
|
const getPluginsDir = () => {
|
|
89
144
|
const config = loadConfig();
|
|
90
145
|
const baseDir = resolvePath(config.baseDir || DEFAULT_BASE_DIR);
|
package/docs/agents.md
CHANGED
|
@@ -17,8 +17,6 @@ plugins:
|
|
|
17
17
|
- id: openbot
|
|
18
18
|
config:
|
|
19
19
|
model: anthropic/claude-3-5-sonnet-20240620
|
|
20
|
-
- id: shell
|
|
21
|
-
- id: delegation
|
|
22
20
|
---
|
|
23
21
|
|
|
24
22
|
You are a web research specialist. Use the available tools to gather and
|
|
@@ -37,20 +35,19 @@ the bus). Built-in **`state`** is hidden by default.
|
|
|
37
35
|
A runtime plugin is one that handles `agent:invoke` (the LLM loop). Without
|
|
38
36
|
one, the agent will not respond to user input. Built-in runtime plugins:
|
|
39
37
|
|
|
40
|
-
- `openbot` — the standard, opinionated OpenBot agent runtime.
|
|
41
|
-
|
|
38
|
+
- `openbot` — the standard, opinionated OpenBot agent runtime. It is
|
|
39
|
+
**batteries-included** and provides inbuilt tools (bash, memory, storage,
|
|
40
|
+
delegation, and approval).
|
|
42
41
|
- `claude-code` — runs Claude inside the Claude Agent SDK with its own tools.
|
|
43
42
|
- `gemini-cli` — spawns Google's `gemini` CLI in headless mode.
|
|
44
43
|
|
|
45
44
|
`claude-code` and `gemini-cli` own their own tool loops, so attaching tool
|
|
46
|
-
plugins like `
|
|
47
|
-
`openbot`.
|
|
45
|
+
plugins like `bash` to them has no effect.
|
|
48
46
|
|
|
49
47
|
## Built-in agents
|
|
50
48
|
|
|
51
49
|
OpenBot ships a built-in **`system`** agent (the orchestrator) with the `openbot`
|
|
52
|
-
runtime
|
|
53
|
-
approval, memory, etc.). A built-in **`state`** agent backs deterministic
|
|
50
|
+
runtime. A built-in **`state`** agent backs deterministic
|
|
54
51
|
`/api/state` handling and infra events.
|
|
55
52
|
|
|
56
53
|
You can optionally persist overrides for either id at `~/.openbot/agents/system/AGENT.md` or `~/.openbot/agents/state/AGENT.md`. When present, settings are merged on top of the code defaults (`getAgentDetails`). The **`state`** agent is not listed by **`action:storage:get-agents`** (`hidden: true`); **`system`** is listed. Use **`action:storage:create-agent`** to create an overlay once, **`action:storage:update-agent`** for partial updates (creating the file if missing for `system` / `state`), and **`action:storage:delete-agent`** to remove only that `AGENT.md` and revert to defaults (other files under the folder are left untouched).
|
package/docs/architecture.md
CHANGED
|
@@ -18,7 +18,7 @@ A dynamic registry that manages all available agents. Agents can be:
|
|
|
18
18
|
- **TS Packages**: Advanced agents with custom logic in `~/.openbot/agents/*/index.ts`.
|
|
19
19
|
|
|
20
20
|
### 3. Plugin registry
|
|
21
|
-
The "capability layer" that provides tools and logic shared across the platform. Plugins (like `
|
|
21
|
+
The "capability layer" that provides tools and logic shared across the platform. Plugins (like `bash` or `file-system`) define the actions agents can perform.
|
|
22
22
|
|
|
23
23
|
### 4. Orchestration layer (Melony)
|
|
24
24
|
The underlying event bus that handles all communication. It ensures that agents can collaborate asynchronously, share context, and emit real-time updates to the UI.
|
package/docs/plugins.md
CHANGED
|
@@ -43,15 +43,36 @@ name collisions.
|
|
|
43
43
|
|
|
44
44
|
| Id | Role | Notes |
|
|
45
45
|
| --------------- | ---------- | --------------------------------------------------------- |
|
|
46
|
-
| `openbot` | Runtime |
|
|
46
|
+
| `openbot` | Runtime | Standard batteries-included OpenBot agent runtime. |
|
|
47
47
|
| `claude-code` | Runtime | Claude Agent SDK; owns its own tool loop |
|
|
48
48
|
| `gemini-cli` | Runtime | Google `gemini` CLI in headless mode |
|
|
49
|
-
| `
|
|
50
|
-
| `storage` | Tool | `create_channel`, `patch_*`, `
|
|
51
|
-
| `memory` | Tool | `remember`, `recall`, `forget`
|
|
49
|
+
| `bash` | Tool | `bash` (inbuilt in `openbot`) |
|
|
50
|
+
| `storage` | Tool | `create_channel`, `patch_*`, ... (inbuilt in `openbot`) |
|
|
51
|
+
| `memory` | Tool | `remember`, `recall`, `forget` (inbuilt in `openbot`) |
|
|
52
52
|
| `plugin-manager`| Infra | Marketplace list, npm plugin install/uninstall, agent install |
|
|
53
53
|
|
|
54
|
-
##
|
|
54
|
+
## Batteries-included: `openbot` runtime
|
|
55
|
+
|
|
56
|
+
The `openbot` plugin is the standard runtime for OpenBot agents. It is designed
|
|
57
|
+
to be isolated and self-contained, providing a core ecosystem of inbuilt tools:
|
|
58
|
+
|
|
59
|
+
- **Bash**: Stateful system tasks and file operations.
|
|
60
|
+
- **Memory**: Long-term durable fact storage.
|
|
61
|
+
- **Storage**: Channel and thread management.
|
|
62
|
+
- **Delegation**: Calling upon other specialized agents.
|
|
63
|
+
- **Approval**: Gating protected actions behind UI confirmation.
|
|
64
|
+
|
|
65
|
+
When you use the `openbot` runtime, these tools are automatically available.
|
|
66
|
+
You can configure the inbuilt `approval` plugin via the `openbot` plugin config:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
plugins:
|
|
70
|
+
- id: openbot
|
|
71
|
+
config:
|
|
72
|
+
model: openai/gpt-4o-mini
|
|
73
|
+
approval:
|
|
74
|
+
actions: [action:bash, action:create_channel]
|
|
75
|
+
```
|
|
55
76
|
|
|
56
77
|
A community plugin is just an npm package whose default export matches the
|
|
57
78
|
`Plugin` interface. Reference it by its npm package name in AGENT.md:
|
|
@@ -69,11 +90,11 @@ On first use OpenBot installs the package into
|
|
|
69
90
|
|
|
70
91
|
## Approval plugin
|
|
71
92
|
|
|
72
|
-
The `approval` plugin gates protected tool calls behind a UI confirmation widget. By default, it gates `action:
|
|
93
|
+
The `approval` plugin gates protected tool calls behind a UI confirmation widget. By default, it gates `action:bash`.
|
|
73
94
|
|
|
74
95
|
```yaml
|
|
75
96
|
plugins:
|
|
76
97
|
- id: approval
|
|
77
98
|
config:
|
|
78
|
-
actions: [action:
|
|
99
|
+
actions: [action:bash]
|
|
79
100
|
```
|
|
@@ -9,11 +9,11 @@ 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
11
|
# At least one plugin must handle `agent:invoke` (a "runtime" plugin like
|
|
12
|
-
# `openbot`, `claude-code`, or `gemini-cli`). Tool plugins like `
|
|
12
|
+
# `openbot`, `claude-code`, or `gemini-cli`). Tool plugins like `bash`,
|
|
13
13
|
# `delegation`, and `storage-tools` contribute tools to whichever runtime
|
|
14
14
|
# plugin can consume them.
|
|
15
15
|
#
|
|
16
|
-
# Built-in plugin ids: openbot, claude-code, gemini-cli,
|
|
16
|
+
# Built-in plugin ids: openbot, claude-code, gemini-cli, bash, delegation,
|
|
17
17
|
# storage-tools, approval.
|
|
18
18
|
#
|
|
19
19
|
# Community plugins are referenced by their npm package name (e.g.
|
|
@@ -23,12 +23,12 @@ plugins:
|
|
|
23
23
|
- id: openbot
|
|
24
24
|
config:
|
|
25
25
|
model: openai/gpt-4o-mini
|
|
26
|
-
- id:
|
|
26
|
+
- id: bash
|
|
27
27
|
- id: delegation
|
|
28
28
|
- id: storage
|
|
29
29
|
- id: approval
|
|
30
30
|
config:
|
|
31
|
-
actions: [action:
|
|
31
|
+
actions: [action:bash]
|
|
32
32
|
---
|
|
33
33
|
|
|
34
34
|
<!--
|
package/package.json
CHANGED
package/src/app/cli.ts
CHANGED
package/src/app/config.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface OpenBotconfig {
|
|
|
14
14
|
* {@link DEFAULT_MARKETPLACE_REGISTRY_URL} is used.
|
|
15
15
|
*/
|
|
16
16
|
marketplaceRegistryUrl?: string;
|
|
17
|
+
/** Public base URL for workspace file links (e.g. https://my-host.example). Falls back to OPENBOT_PUBLIC_URL env or http://localhost:{port}. */
|
|
18
|
+
publicUrl?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface StoredVariable {
|
|
@@ -23,6 +25,8 @@ export interface StoredVariable {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export const DEFAULT_BASE_DIR = '~/.openbot';
|
|
28
|
+
/** Default parent directory for per-channel working directories (user-facing workspace). */
|
|
29
|
+
export const DEFAULT_CHANNELS_WORKSPACE_DIR = '~/openbot';
|
|
26
30
|
export const DEFAULT_PLUGINS_DIR = 'plugins';
|
|
27
31
|
export const DEFAULT_AGENTS_DIR = 'agents';
|
|
28
32
|
export const DEFAULT_CHANNELS_DIR = 'channels';
|
|
@@ -37,6 +41,15 @@ export function resolvePath(p: string) {
|
|
|
37
41
|
return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : path.resolve(p);
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
/** Default absolute cwd for a channel when none is provided at creation time. */
|
|
45
|
+
export function getDefaultChannelCwd(channelId: string): string {
|
|
46
|
+
const id = channelId.trim();
|
|
47
|
+
if (!id) {
|
|
48
|
+
throw new Error('channelId is required');
|
|
49
|
+
}
|
|
50
|
+
return resolvePath(`${DEFAULT_CHANNELS_WORKSPACE_DIR}/${id}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
export function loadConfig(): OpenBotconfig {
|
|
41
54
|
const configPath = path.join(os.homedir(), '.openbot', CONFIG_FILE);
|
|
42
55
|
if (fs.existsSync(configPath)) {
|