openbot 0.3.6 → 0.4.0
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/README.md +15 -16
- package/dist/app/agent-ids.js +4 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/config.js +0 -19
- package/dist/app/server.js +8 -14
- package/dist/bus/services.js +34 -124
- package/dist/harness/agent-invoke-run.js +44 -0
- package/dist/harness/agent-turn.js +99 -0
- package/dist/harness/channel-participants.js +40 -0
- package/dist/harness/constants.js +2 -0
- package/dist/harness/context-meter.js +97 -0
- package/dist/harness/context.js +95 -47
- package/dist/harness/dispatch.js +144 -0
- package/dist/harness/dispatcher.js +45 -156
- package/dist/harness/history.js +177 -0
- package/dist/harness/index.js +91 -0
- package/dist/harness/orchestration.js +88 -0
- package/dist/harness/participants.js +22 -0
- package/dist/harness/run-harness.js +154 -0
- package/dist/harness/run.js +98 -0
- package/dist/harness/runtime-factory.js +0 -34
- package/dist/harness/runtime.js +57 -0
- package/dist/harness/todo-dispatch.js +51 -0
- package/dist/harness/todos.js +5 -0
- package/dist/harness/turn.js +79 -0
- package/dist/plugins/approval/index.js +105 -149
- package/dist/plugins/delegation/index.js +119 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +80 -0
- package/dist/plugins/openbot/history.js +98 -0
- package/dist/plugins/openbot/index.js +31 -0
- package/dist/plugins/openbot/runtime.js +317 -0
- package/dist/plugins/openbot/system-prompt.js +5 -0
- package/dist/plugins/plugin-manager/index.js +105 -0
- package/dist/plugins/storage/index.js +573 -0
- package/dist/plugins/storage/service.js +1159 -0
- package/dist/plugins/storage-tools/index.js +2 -2
- package/dist/plugins/thread-namer/index.js +72 -0
- package/dist/plugins/thread-naming/generate-title.js +44 -0
- package/dist/plugins/thread-naming/index.js +103 -0
- package/dist/plugins/threads/index.js +114 -0
- package/dist/plugins/todo/index.js +24 -25
- package/dist/plugins/ui/index.js +2 -32
- package/dist/registry/plugins.js +3 -9
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +110 -0
- package/dist/services/plugins/service.js +177 -0
- package/dist/services/plugins/types.js +1 -0
- package/dist/services/process.js +29 -0
- package/dist/services/storage.js +11 -10
- package/dist/services/thread-naming.js +81 -0
- package/docs/agents.md +16 -10
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +6 -15
- package/docs/templates/AGENT.example.md +7 -13
- package/package.json +1 -2
- package/src/app/agent-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/config.ts +1 -31
- package/src/app/server.ts +8 -16
- package/src/app/types.ts +63 -189
- package/src/harness/index.ts +145 -0
- package/src/plugins/approval/index.ts +91 -189
- package/src/plugins/delegation/index.ts +136 -39
- package/src/plugins/memory/index.ts +112 -15
- package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
- package/src/plugins/openbot/context.ts +91 -0
- package/src/plugins/openbot/history.ts +107 -0
- package/src/plugins/openbot/index.ts +37 -0
- package/src/plugins/openbot/runtime.ts +384 -0
- package/src/plugins/openbot/system-prompt.ts +7 -0
- package/src/plugins/plugin-manager/index.ts +122 -0
- package/src/plugins/shell/index.ts +1 -1
- package/src/plugins/storage/index.ts +633 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +224 -67
- package/src/{bus/types.ts → services/plugins/domain.ts} +16 -7
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +25 -27
- package/src/services/{plugins.ts → plugins/service.ts} +96 -2
- package/src/{bus/plugin.ts → services/plugins/types.ts} +3 -3
- package/src/bus/services.ts +0 -954
- package/src/harness/context.ts +0 -365
- package/src/harness/dispatcher.ts +0 -379
- package/src/harness/mcp.ts +0 -78
- package/src/harness/runtime-factory.ts +0 -129
- package/src/harness/todo-advance.ts +0 -128
- package/src/plugins/ai-sdk/index.ts +0 -41
- package/src/plugins/ai-sdk/runtime.ts +0 -468
- package/src/plugins/ai-sdk/system-prompt.ts +0 -18
- package/src/plugins/mcp/index.ts +0 -128
- package/src/plugins/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/plugins/ui/index.ts +0 -227
- /package/src/{harness → services}/process.ts +0 -0
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
* Resolve a scope alias to a concrete scope string. Aliases let tools accept
|
|
4
|
+
* `agent`/`channel`/`global` without knowing the active ids; the bus rewrites
|
|
5
|
+
* them using `context.state`.
|
|
6
|
+
*/
|
|
7
|
+
function resolveMemoryScope(alias, state) {
|
|
8
|
+
switch (alias) {
|
|
9
|
+
case 'agent':
|
|
10
|
+
return `agent:${state.agentId}`;
|
|
11
|
+
case 'channel':
|
|
12
|
+
return `channel:${state.channelId}`;
|
|
13
|
+
case 'global':
|
|
14
|
+
case undefined:
|
|
15
|
+
return 'global';
|
|
16
|
+
default:
|
|
17
|
+
return 'global';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveMemoryScopeFilter(alias, state) {
|
|
21
|
+
if (alias === 'all' || alias === undefined) {
|
|
22
|
+
return ['global', `agent:${state.agentId}`, `channel:${state.channelId}`];
|
|
23
|
+
}
|
|
24
|
+
return [resolveMemoryScope(alias, state)];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `memory` — exposes the global memory store as agent tools and provides
|
|
28
|
+
* platform-level memory handlers.
|
|
15
29
|
*/
|
|
16
30
|
const memoryToolDefinitions = {
|
|
17
31
|
remember: {
|
|
@@ -64,8 +78,83 @@ export const memoryPlugin = {
|
|
|
64
78
|
name: 'Memory',
|
|
65
79
|
description: 'Global long-term memory: remember/recall/forget facts across runs and agents.',
|
|
66
80
|
toolDefinitions: memoryToolDefinitions,
|
|
67
|
-
factory: () => () => {
|
|
68
|
-
|
|
81
|
+
factory: ({ storage }) => (builder) => {
|
|
82
|
+
builder.on('action:remember', async function* (event, context) {
|
|
83
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
84
|
+
try {
|
|
85
|
+
const { content, scope, tags } = event.data;
|
|
86
|
+
const record = await storage.appendMemory({
|
|
87
|
+
scope: resolveMemoryScope(scope, context.state),
|
|
88
|
+
content,
|
|
89
|
+
tags,
|
|
90
|
+
});
|
|
91
|
+
yield {
|
|
92
|
+
type: 'action:remember:result',
|
|
93
|
+
data: { success: true, record },
|
|
94
|
+
meta: resultMeta,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
yield {
|
|
99
|
+
type: 'action:remember:result',
|
|
100
|
+
data: {
|
|
101
|
+
success: false,
|
|
102
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
103
|
+
},
|
|
104
|
+
meta: resultMeta,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
builder.on('action:recall', async function* (event, context) {
|
|
109
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
110
|
+
try {
|
|
111
|
+
const { query, tag, scope, limit } = event.data;
|
|
112
|
+
const records = await storage.listMemories({
|
|
113
|
+
scopes: resolveMemoryScopeFilter(scope, context.state),
|
|
114
|
+
query,
|
|
115
|
+
tag,
|
|
116
|
+
limit,
|
|
117
|
+
});
|
|
118
|
+
yield {
|
|
119
|
+
type: 'action:recall:result',
|
|
120
|
+
data: { success: true, records },
|
|
121
|
+
meta: resultMeta,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
yield {
|
|
126
|
+
type: 'action:recall:result',
|
|
127
|
+
data: {
|
|
128
|
+
success: false,
|
|
129
|
+
records: [],
|
|
130
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
131
|
+
},
|
|
132
|
+
meta: resultMeta,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
builder.on('action:forget', async function* (event, context) {
|
|
137
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
138
|
+
try {
|
|
139
|
+
const deleted = await storage.deleteMemory({ id: event.data.id });
|
|
140
|
+
yield {
|
|
141
|
+
type: 'action:forget:result',
|
|
142
|
+
data: { success: true, deleted },
|
|
143
|
+
meta: resultMeta,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
yield {
|
|
148
|
+
type: 'action:forget:result',
|
|
149
|
+
data: {
|
|
150
|
+
success: false,
|
|
151
|
+
deleted: false,
|
|
152
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
153
|
+
},
|
|
154
|
+
meta: resultMeta,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
69
158
|
},
|
|
70
159
|
};
|
|
71
160
|
export default memoryPlugin;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../../app/config.js';
|
|
5
|
+
const DEFAULT_LIMIT = 50;
|
|
6
|
+
const MAX_LIMIT = 500;
|
|
7
|
+
const getMemoryDir = () => {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
return path.join(resolvePath(config.baseDir || DEFAULT_BASE_DIR), 'memory');
|
|
10
|
+
};
|
|
11
|
+
const getLogPath = () => path.join(getMemoryDir(), 'log.jsonl');
|
|
12
|
+
const ensureDir = async () => {
|
|
13
|
+
await fs.mkdir(getMemoryDir(), { recursive: true });
|
|
14
|
+
};
|
|
15
|
+
const readLog = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(getLogPath(), 'utf-8');
|
|
18
|
+
return raw
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.map((line) => line.trim())
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((line) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(line);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.filter((e) => !!e);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e?.code === 'ENOENT')
|
|
34
|
+
return [];
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const replay = (entries) => {
|
|
39
|
+
const out = new Map();
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (entry.op === 'add') {
|
|
42
|
+
out.set(entry.record.id, entry.record);
|
|
43
|
+
}
|
|
44
|
+
else if (entry.op === 'delete') {
|
|
45
|
+
out.delete(entry.id);
|
|
46
|
+
}
|
|
47
|
+
else if (entry.op === 'update') {
|
|
48
|
+
const existing = out.get(entry.id);
|
|
49
|
+
if (!existing)
|
|
50
|
+
continue;
|
|
51
|
+
out.set(entry.id, {
|
|
52
|
+
...existing,
|
|
53
|
+
...entry.patch,
|
|
54
|
+
id: existing.id,
|
|
55
|
+
updatedAt: entry.at,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
const appendEntry = async (entry) => {
|
|
62
|
+
await ensureDir();
|
|
63
|
+
await fs.appendFile(getLogPath(), `${JSON.stringify(entry)}\n`, 'utf-8');
|
|
64
|
+
};
|
|
65
|
+
const matchesQuery = (record, query, tag) => {
|
|
66
|
+
if (tag) {
|
|
67
|
+
if (!record.tags || !record.tags.includes(tag))
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (query) {
|
|
71
|
+
const q = query.toLowerCase();
|
|
72
|
+
if (!record.content.toLowerCase().includes(q))
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
export const memoryService = {
|
|
78
|
+
appendMemory: async (args) => {
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
const record = {
|
|
81
|
+
id: crypto.randomUUID(),
|
|
82
|
+
scope: args.scope,
|
|
83
|
+
content: args.content,
|
|
84
|
+
tags: args.tags?.length ? args.tags : undefined,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now,
|
|
87
|
+
};
|
|
88
|
+
await appendEntry({ op: 'add', record });
|
|
89
|
+
return record;
|
|
90
|
+
},
|
|
91
|
+
updateMemory: async (args) => {
|
|
92
|
+
const entries = await readLog();
|
|
93
|
+
const map = replay(entries);
|
|
94
|
+
if (!map.has(args.id))
|
|
95
|
+
return false;
|
|
96
|
+
const at = new Date().toISOString();
|
|
97
|
+
const patch = {};
|
|
98
|
+
if (args.content !== undefined)
|
|
99
|
+
patch.content = args.content;
|
|
100
|
+
if (args.tags !== undefined)
|
|
101
|
+
patch.tags = args.tags.length ? args.tags : undefined;
|
|
102
|
+
if (Object.keys(patch).length === 0)
|
|
103
|
+
return true;
|
|
104
|
+
await appendEntry({ op: 'update', id: args.id, patch, at });
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
deleteMemory: async (args) => {
|
|
108
|
+
const entries = await readLog();
|
|
109
|
+
const map = replay(entries);
|
|
110
|
+
if (!map.has(args.id))
|
|
111
|
+
return false;
|
|
112
|
+
await appendEntry({ op: 'delete', id: args.id, at: new Date().toISOString() });
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
listMemories: async (args = {}) => {
|
|
116
|
+
const entries = await readLog();
|
|
117
|
+
const map = replay(entries);
|
|
118
|
+
const limit = Math.min(Math.max(args.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
119
|
+
const scopeSet = (() => {
|
|
120
|
+
if (args.scope)
|
|
121
|
+
return new Set([args.scope]);
|
|
122
|
+
if (args.scopes && args.scopes.length > 0)
|
|
123
|
+
return new Set(args.scopes);
|
|
124
|
+
return null;
|
|
125
|
+
})();
|
|
126
|
+
const filtered = [];
|
|
127
|
+
for (const record of map.values()) {
|
|
128
|
+
if (scopeSet && !scopeSet.has(record.scope))
|
|
129
|
+
continue;
|
|
130
|
+
if (!matchesQuery(record, args.query, args.tag))
|
|
131
|
+
continue;
|
|
132
|
+
filtered.push(record);
|
|
133
|
+
}
|
|
134
|
+
filtered.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
|
135
|
+
return filtered.slice(0, limit);
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Compact the log into a single `add` per surviving record. Cheap to call
|
|
139
|
+
* occasionally; not required for correctness.
|
|
140
|
+
*/
|
|
141
|
+
compact: async () => {
|
|
142
|
+
const entries = await readLog();
|
|
143
|
+
const map = replay(entries);
|
|
144
|
+
const surviving = Array.from(map.values());
|
|
145
|
+
await ensureDir();
|
|
146
|
+
const tmp = `${getLogPath()}.tmp`;
|
|
147
|
+
const body = surviving.map((record) => JSON.stringify({ op: 'add', record })).join('\n');
|
|
148
|
+
await fs.writeFile(tmp, body ? `${body}\n` : '', 'utf-8');
|
|
149
|
+
await fs.rename(tmp, getLogPath());
|
|
150
|
+
return surviving.length;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const DEFAULT_CONTEXT_BUDGET = 8000;
|
|
2
|
+
/**
|
|
3
|
+
* Returns the known context window budget (in tokens) for a given model string.
|
|
4
|
+
*/
|
|
5
|
+
export const getContextBudgetForModel = (modelString) => {
|
|
6
|
+
const budgets = {
|
|
7
|
+
'openai/gpt-4o': 128000,
|
|
8
|
+
'openai/gpt-4o-mini': 128000,
|
|
9
|
+
'openai/o1-preview': 128000,
|
|
10
|
+
'openai/o1-mini': 128000,
|
|
11
|
+
'anthropic/claude-3-5-sonnet-20240620': 200000,
|
|
12
|
+
'anthropic/claude-3-5-sonnet-latest': 200000,
|
|
13
|
+
'anthropic/claude-3-opus-20240229': 200000,
|
|
14
|
+
'anthropic/claude-3-sonnet-20240229': 200000,
|
|
15
|
+
'anthropic/claude-3-haiku-20240307': 200000,
|
|
16
|
+
};
|
|
17
|
+
return budgets[modelString] || DEFAULT_CONTEXT_BUDGET;
|
|
18
|
+
};
|
|
19
|
+
/** Built-in orchestrator agent id. */
|
|
20
|
+
export const ORCHESTRATOR_AGENT_ID = 'system';
|
|
21
|
+
/**
|
|
22
|
+
* Check if a channel is a solo DM (only the agent is present).
|
|
23
|
+
*/
|
|
24
|
+
export function isDmSoloChannel(participants, agentId) {
|
|
25
|
+
return participants.length === 0 || (participants.length === 1 && participants[0] === agentId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Simplified context builder for MVP.
|
|
29
|
+
*/
|
|
30
|
+
export async function buildContext(state, storage) {
|
|
31
|
+
const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
|
|
32
|
+
const participants = channelDetails?.participants || [];
|
|
33
|
+
const isDm = isDmSoloChannel(participants, agentId);
|
|
34
|
+
const sections = [];
|
|
35
|
+
// 1. Environment
|
|
36
|
+
let env = '## ENVIRONMENT\n';
|
|
37
|
+
if (isDm) {
|
|
38
|
+
env += '- Mode: Direct Message (Solo)\n';
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const channelName = channelDetails?.name || channelId;
|
|
42
|
+
env += `- Mode: Channel (#${channelName})\n`;
|
|
43
|
+
if (threadId) {
|
|
44
|
+
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
45
|
+
}
|
|
46
|
+
const peerIds = participants.filter((id) => id !== agentId);
|
|
47
|
+
if (peerIds.length > 0) {
|
|
48
|
+
env += `- Participants: ${peerIds.join(', ')}\n`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
sections.push(env);
|
|
52
|
+
// 2. Channel Spec
|
|
53
|
+
const spec = channelDetails?.spec?.trim();
|
|
54
|
+
if (spec) {
|
|
55
|
+
sections.push(`## CHANNEL SPECIFICATION\n${spec}`);
|
|
56
|
+
}
|
|
57
|
+
// 3. Agent Instructions
|
|
58
|
+
if (agentDetails?.instructions) {
|
|
59
|
+
sections.push(`## AGENT: ${agentDetails?.name}\n${agentDetails.instructions}`);
|
|
60
|
+
}
|
|
61
|
+
// 4. Memories
|
|
62
|
+
if (storage?.listMemories) {
|
|
63
|
+
try {
|
|
64
|
+
const scopes = ['global', `agent:${agentId}`];
|
|
65
|
+
if (channelId)
|
|
66
|
+
scopes.push(`channel:${channelId}`);
|
|
67
|
+
const records = await storage.listMemories({ scopes, limit: 20 });
|
|
68
|
+
if (records.length > 0) {
|
|
69
|
+
const formatted = records
|
|
70
|
+
.map((r) => `- (${r.scope}) ${r.content}`)
|
|
71
|
+
.join('\n');
|
|
72
|
+
sections.push(`## MEMORIES\n${formatted}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.warn('[context] Failed to fetch memories:', error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return sections.join('\n\n');
|
|
80
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a raw event log into a valid chain of ModelMessages for the AI SDK.
|
|
3
|
+
*
|
|
4
|
+
* This is a basic implementation that maps events to messages and filters out
|
|
5
|
+
* events from sub-processes (delegation) to avoid duplication in history.
|
|
6
|
+
*/
|
|
7
|
+
export function eventsToModelMessages(events) {
|
|
8
|
+
const messages = [];
|
|
9
|
+
for (const event of events) {
|
|
10
|
+
// Skip events that belong to a sub-process (like delegation)
|
|
11
|
+
// so they don't pollute the main conversation history.
|
|
12
|
+
if (event.meta?.parentToolCallId) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
switch (event.type) {
|
|
16
|
+
case 'agent:output': {
|
|
17
|
+
const last = messages[messages.length - 1];
|
|
18
|
+
if (last && last.role === 'assistant' && typeof last.content === 'string') {
|
|
19
|
+
last.content += event.data.content;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
messages.push({ role: 'assistant', content: event.data.content });
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'agent:invoke': {
|
|
27
|
+
const invokeEvent = event;
|
|
28
|
+
if (invokeEvent.data?.content && invokeEvent.data?.role) {
|
|
29
|
+
messages.push({
|
|
30
|
+
role: invokeEvent.data.role,
|
|
31
|
+
content: invokeEvent.data.content
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
default:
|
|
37
|
+
// Handle tool calls (action:*)
|
|
38
|
+
if (event.type.startsWith('action:') && !event.type.endsWith(':result')) {
|
|
39
|
+
const toolName = event.type.slice(7);
|
|
40
|
+
const toolCallId = event.meta?.toolCallId;
|
|
41
|
+
if (!toolCallId)
|
|
42
|
+
break;
|
|
43
|
+
const toolCall = {
|
|
44
|
+
type: 'tool-call',
|
|
45
|
+
toolCallId,
|
|
46
|
+
toolName,
|
|
47
|
+
input: event.data,
|
|
48
|
+
};
|
|
49
|
+
const last = messages[messages.length - 1];
|
|
50
|
+
if (last && last.role === 'assistant') {
|
|
51
|
+
if (typeof last.content === 'string') {
|
|
52
|
+
last.content = [
|
|
53
|
+
{ type: 'text', text: last.content },
|
|
54
|
+
toolCall,
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
else if (Array.isArray(last.content)) {
|
|
58
|
+
last.content.push(toolCall);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
messages.push({
|
|
63
|
+
role: 'assistant',
|
|
64
|
+
content: [toolCall],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Handle tool results (action:*:result)
|
|
69
|
+
else if (event.type.startsWith('action:') && event.type.endsWith(':result')) {
|
|
70
|
+
const toolName = event.type.slice(7, -7);
|
|
71
|
+
const toolCallId = event.meta?.toolCallId;
|
|
72
|
+
if (!toolCallId)
|
|
73
|
+
break;
|
|
74
|
+
const toolResult = {
|
|
75
|
+
type: 'tool-result',
|
|
76
|
+
toolCallId,
|
|
77
|
+
toolName,
|
|
78
|
+
output: {
|
|
79
|
+
type: 'text',
|
|
80
|
+
value: event?.data?.output || "No output", // ?.output is from delegation result
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const last = messages[messages.length - 1];
|
|
84
|
+
if (last && last.role === 'tool' && Array.isArray(last.content)) {
|
|
85
|
+
last.content.push(toolResult);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
messages.push({
|
|
89
|
+
role: 'tool',
|
|
90
|
+
content: [toolResult],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return messages;
|
|
98
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { openbotRuntime } from './runtime.js';
|
|
2
|
+
/**
|
|
3
|
+
* `openbot` — the standard, opinionated OpenBot agent runtime.
|
|
4
|
+
*
|
|
5
|
+
* This is the canonical execution loop for OpenBot agents. It handles
|
|
6
|
+
* `agent:invoke`, manages short-term memory, assembles context, and
|
|
7
|
+
* orchestrates tool calls.
|
|
8
|
+
*/
|
|
9
|
+
export const openbotPlugin = {
|
|
10
|
+
id: 'openbot',
|
|
11
|
+
name: 'OpenBot Agent',
|
|
12
|
+
description: 'The standard, opinionated OpenBot agent runtime. Handles the core execution loop and tool orchestration.',
|
|
13
|
+
configSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
model: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Provider model string, e.g. openai/gpt-4o-mini, anthropic/claude-3-5-sonnet-20240620',
|
|
19
|
+
default: 'openai/gpt-4o-mini',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
factory: ({ config, storage, tools }) => {
|
|
24
|
+
return openbotRuntime({
|
|
25
|
+
model: config?.model,
|
|
26
|
+
storage,
|
|
27
|
+
toolDefinitions: tools,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default openbotPlugin;
|