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/dist/harness/dispatch.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
import { ORCHESTRATOR_AGENT_ID } from './constants.js';
|
|
4
|
-
import { runTurn } from './run.js';
|
|
5
|
-
const stopRequests = [];
|
|
6
|
-
const STOP_REQUEST_TTL_MS = 30 * 60 * 1000;
|
|
7
|
-
const pruneStopRequests = () => {
|
|
8
|
-
const now = Date.now();
|
|
9
|
-
for (let i = stopRequests.length - 1; i >= 0; i -= 1) {
|
|
10
|
-
if (now - stopRequests[i].requestedAt > STOP_REQUEST_TTL_MS) {
|
|
11
|
-
stopRequests.splice(i, 1);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
const findStopRequest = (target) => {
|
|
16
|
-
pruneStopRequests();
|
|
17
|
-
return stopRequests.find((r) => {
|
|
18
|
-
if (r.runId !== target.runId)
|
|
19
|
-
return false;
|
|
20
|
-
if (r.agentId && r.agentId !== target.agentId)
|
|
21
|
-
return false;
|
|
22
|
-
if (r.channelId && r.channelId !== target.channelId)
|
|
23
|
-
return false;
|
|
24
|
-
if (r.threadId && r.threadId !== target.threadId)
|
|
25
|
-
return false;
|
|
26
|
-
return true;
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
export async function dispatch(options) {
|
|
30
|
-
const { runId, channelId, onEvent } = options;
|
|
31
|
-
let { threadId } = options;
|
|
32
|
-
const { event } = options;
|
|
33
|
-
ensureEventId(event);
|
|
34
|
-
if (event.type === 'action:agent_run_stop') {
|
|
35
|
-
await handleStop(event, options);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const agentId = options.agentId || ORCHESTRATOR_AGENT_ID;
|
|
39
|
-
let turnEvent = event;
|
|
40
|
-
if (event.type === 'user:input' || event.type === 'agent:invoke') {
|
|
41
|
-
turnEvent = await normalizeUserInput(event, { runId, channelId, threadId, onEvent });
|
|
42
|
-
if (event.type === 'user:input') {
|
|
43
|
-
threadId = turnEvent.meta?.threadId || threadId || event.id;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const target = { runId, agentId, channelId, threadId };
|
|
47
|
-
const preStop = findStopRequest(target);
|
|
48
|
-
if (preStop) {
|
|
49
|
-
const state = await storageService.getOpenBotState({ ...target, event: turnEvent });
|
|
50
|
-
await onEvent({ type: 'agent:run:stopped', data: { ...target, reason: preStop.reason } }, state);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
let state;
|
|
54
|
-
try {
|
|
55
|
-
state = await storageService.getOpenBotState({ ...target, event: turnEvent });
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
if (error.code === 'AGENT_NOT_FOUND') {
|
|
59
|
-
const fallback = await storageService.getOpenBotState({
|
|
60
|
-
...target,
|
|
61
|
-
agentId: ORCHESTRATOR_AGENT_ID,
|
|
62
|
-
event: turnEvent,
|
|
63
|
-
});
|
|
64
|
-
await onEvent({
|
|
65
|
-
type: 'agent:output',
|
|
66
|
-
data: {
|
|
67
|
-
content: `⚠️ Agent **${agentId}** does not exist. Use participant ids without an @ prefix.`,
|
|
68
|
-
},
|
|
69
|
-
meta: { agentId: ORCHESTRATOR_AGENT_ID, threadId },
|
|
70
|
-
}, fallback);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
const turn = runTurn({ runId, channelId, threadId, agentId, event: turnEvent });
|
|
76
|
-
let next = await turn.next();
|
|
77
|
-
while (!next.done) {
|
|
78
|
-
const chunk = next.value;
|
|
79
|
-
const stop = findStopRequest(target);
|
|
80
|
-
if (stop) {
|
|
81
|
-
await onEvent({ type: 'agent:run:stopped', data: { ...target, reason: stop.reason } }, state);
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
if (chunk.type === 'action:create_thread:result' && chunk.data.success) {
|
|
85
|
-
threadId = chunk.data.threadId || threadId;
|
|
86
|
-
}
|
|
87
|
-
await onEvent(chunk, state);
|
|
88
|
-
next = await turn.next();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function normalizeUserInput(event, ctx) {
|
|
92
|
-
const rawContent = event.data?.content || '';
|
|
93
|
-
const userFacing = {
|
|
94
|
-
type: 'agent:invoke',
|
|
95
|
-
id: event.id,
|
|
96
|
-
data: { content: rawContent, role: 'user' },
|
|
97
|
-
meta: {
|
|
98
|
-
agentId: ORCHESTRATOR_AGENT_ID,
|
|
99
|
-
userId: event.meta?.userId,
|
|
100
|
-
userName: event.meta?.userName,
|
|
101
|
-
userAvatarUrl: event.meta?.userAvatarUrl,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
const initialState = await storageService.getOpenBotState({
|
|
105
|
-
runId: ctx.runId,
|
|
106
|
-
agentId: ORCHESTRATOR_AGENT_ID,
|
|
107
|
-
channelId: ctx.channelId,
|
|
108
|
-
threadId: ctx.threadId,
|
|
109
|
-
event: userFacing,
|
|
110
|
-
});
|
|
111
|
-
await ctx.onEvent(userFacing, initialState);
|
|
112
|
-
return {
|
|
113
|
-
...event,
|
|
114
|
-
type: 'agent:invoke',
|
|
115
|
-
data: { ...(event.data || {}), content: rawContent, role: 'user' },
|
|
116
|
-
meta: {
|
|
117
|
-
...(event.meta || {}),
|
|
118
|
-
threadId: ctx.threadId || event.id,
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
async function handleStop(stopEvent, options) {
|
|
123
|
-
const { runId, channelId, threadId, onEvent } = options;
|
|
124
|
-
stopRequests.push({
|
|
125
|
-
runId: stopEvent.data.runId,
|
|
126
|
-
agentId: stopEvent.data.agentId,
|
|
127
|
-
channelId: stopEvent.data.channelId || channelId,
|
|
128
|
-
threadId: stopEvent.data.threadId || threadId,
|
|
129
|
-
reason: stopEvent.data.reason,
|
|
130
|
-
requestedAt: Date.now(),
|
|
131
|
-
});
|
|
132
|
-
const state = await storageService.getOpenBotState({
|
|
133
|
-
runId,
|
|
134
|
-
agentId: options.agentId || ORCHESTRATOR_AGENT_ID,
|
|
135
|
-
channelId,
|
|
136
|
-
threadId,
|
|
137
|
-
event: stopEvent,
|
|
138
|
-
});
|
|
139
|
-
await onEvent({
|
|
140
|
-
type: 'action:agent_run_stop:result',
|
|
141
|
-
data: { success: true, message: `Stop requested for run ${stopEvent.data.runId}.` },
|
|
142
|
-
meta: stopEvent.meta,
|
|
143
|
-
}, state);
|
|
144
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
import { ORCHESTRATOR_AGENT_ID } from './context.js';
|
|
4
|
-
import { resolveMessageTargetAgent } from './channel-participants.js';
|
|
5
|
-
import { runTurn } from './turn.js';
|
|
6
|
-
const stopRequests = [];
|
|
7
|
-
const STOP_REQUEST_TTL_MS = 30 * 60 * 1000;
|
|
8
|
-
const pruneStopRequests = () => {
|
|
9
|
-
const now = Date.now();
|
|
10
|
-
for (let i = stopRequests.length - 1; i >= 0; i -= 1) {
|
|
11
|
-
if (now - stopRequests[i].requestedAt > STOP_REQUEST_TTL_MS) {
|
|
12
|
-
stopRequests.splice(i, 1);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
const findStopRequest = (target) => {
|
|
17
|
-
pruneStopRequests();
|
|
18
|
-
return stopRequests.find((r) => {
|
|
19
|
-
if (r.runId !== target.runId)
|
|
20
|
-
return false;
|
|
21
|
-
if (r.agentId && r.agentId !== target.agentId)
|
|
22
|
-
return false;
|
|
23
|
-
if (r.channelId && r.channelId !== target.channelId)
|
|
24
|
-
return false;
|
|
25
|
-
if (r.threadId && r.threadId !== target.threadId)
|
|
26
|
-
return false;
|
|
27
|
-
return true;
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
export async function dispatch(options) {
|
|
31
|
-
const { event } = options;
|
|
32
|
-
ensureEventId(event);
|
|
33
|
-
if (event.type === 'action:agent_run_stop') {
|
|
34
|
-
await handleStop(event, options);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const ctx = {
|
|
38
|
-
runId: options.runId,
|
|
39
|
-
channelId: options.channelId,
|
|
40
|
-
threadId: options.threadId,
|
|
41
|
-
onEvent: options.onEvent,
|
|
42
|
-
};
|
|
43
|
-
if (event.type === 'user:input' || event.type === 'agent:invoke') {
|
|
44
|
-
const { invoke, targetAgentId } = await normalizeUserInput(event, ctx, options.agentId);
|
|
45
|
-
await executeTurn(targetAgentId, invoke, ctx);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
await executeTurn(options.agentId || ORCHESTRATOR_AGENT_ID, event, ctx, { lifecycle: false });
|
|
49
|
-
}
|
|
50
|
-
async function executeTurn(agentId, event, ctx, opts = {}) {
|
|
51
|
-
const target = {
|
|
52
|
-
runId: ctx.runId,
|
|
53
|
-
agentId,
|
|
54
|
-
channelId: ctx.channelId,
|
|
55
|
-
threadId: ctx.threadId,
|
|
56
|
-
};
|
|
57
|
-
const preStop = opts.lifecycle !== false ? findStopRequest(target) : undefined;
|
|
58
|
-
if (preStop) {
|
|
59
|
-
const state = await storageService.getOpenBotState({ ...target, event });
|
|
60
|
-
await ctx.onEvent({ type: 'agent:run:stopped', data: { ...target, reason: preStop.reason } }, state);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
let state;
|
|
64
|
-
try {
|
|
65
|
-
state = await storageService.getOpenBotState({ ...target, event });
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
if (error.code === 'AGENT_NOT_FOUND') {
|
|
69
|
-
const fallback = await storageService.getOpenBotState({
|
|
70
|
-
...target,
|
|
71
|
-
agentId: ORCHESTRATOR_AGENT_ID,
|
|
72
|
-
event,
|
|
73
|
-
});
|
|
74
|
-
await ctx.onEvent({
|
|
75
|
-
type: 'agent:output',
|
|
76
|
-
data: {
|
|
77
|
-
content: `⚠️ Agent **${agentId}** does not exist. Use participant ids without an @ prefix.`,
|
|
78
|
-
},
|
|
79
|
-
meta: { agentId: ORCHESTRATOR_AGENT_ID, threadId: ctx.threadId },
|
|
80
|
-
}, fallback);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
const turn = runTurn({
|
|
86
|
-
...target,
|
|
87
|
-
event,
|
|
88
|
-
lifecycle: opts.lifecycle !== false,
|
|
89
|
-
shouldStop: () => findStopRequest(target),
|
|
90
|
-
onThreadId: (threadId) => {
|
|
91
|
-
ctx.threadId = threadId;
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
let next = await turn.next();
|
|
95
|
-
while (!next.done) {
|
|
96
|
-
await ctx.onEvent(next.value, state);
|
|
97
|
-
next = await turn.next();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function normalizeUserInput(event, ctx, requestedAgentId) {
|
|
101
|
-
const rawContent = event.data?.content || '';
|
|
102
|
-
const previewState = await storageService.getOpenBotState({
|
|
103
|
-
runId: ctx.runId,
|
|
104
|
-
agentId: ORCHESTRATOR_AGENT_ID,
|
|
105
|
-
channelId: ctx.channelId,
|
|
106
|
-
threadId: ctx.threadId,
|
|
107
|
-
event,
|
|
108
|
-
});
|
|
109
|
-
const participants = previewState.channelDetails?.participants ?? [];
|
|
110
|
-
const targetAgentId = resolveMessageTargetAgent(participants, ORCHESTRATOR_AGENT_ID, requestedAgentId);
|
|
111
|
-
const userFacing = {
|
|
112
|
-
type: 'agent:invoke',
|
|
113
|
-
id: event.id,
|
|
114
|
-
data: { content: rawContent, role: 'user' },
|
|
115
|
-
meta: {
|
|
116
|
-
agentId: targetAgentId,
|
|
117
|
-
userId: event.meta?.userId,
|
|
118
|
-
userName: event.meta?.userName,
|
|
119
|
-
userAvatarUrl: event.meta?.userAvatarUrl,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
await ctx.onEvent(userFacing, previewState);
|
|
123
|
-
const invoke = {
|
|
124
|
-
...event,
|
|
125
|
-
type: 'agent:invoke',
|
|
126
|
-
data: { ...(event.data || {}), content: rawContent, role: 'user' },
|
|
127
|
-
meta: {
|
|
128
|
-
...(event.meta || {}),
|
|
129
|
-
threadId: ctx.threadId || event.id,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
return { invoke, targetAgentId };
|
|
133
|
-
}
|
|
134
|
-
async function handleStop(stopEvent, options) {
|
|
135
|
-
const { runId, channelId, threadId, onEvent } = options;
|
|
136
|
-
stopRequests.push({
|
|
137
|
-
runId: stopEvent.data.runId,
|
|
138
|
-
agentId: stopEvent.data.agentId,
|
|
139
|
-
channelId: stopEvent.data.channelId || channelId,
|
|
140
|
-
threadId: stopEvent.data.threadId || threadId,
|
|
141
|
-
reason: stopEvent.data.reason,
|
|
142
|
-
requestedAt: Date.now(),
|
|
143
|
-
});
|
|
144
|
-
const state = await storageService.getOpenBotState({
|
|
145
|
-
runId,
|
|
146
|
-
agentId: options.agentId || ORCHESTRATOR_AGENT_ID,
|
|
147
|
-
channelId,
|
|
148
|
-
threadId,
|
|
149
|
-
event: stopEvent,
|
|
150
|
-
});
|
|
151
|
-
await onEvent({
|
|
152
|
-
type: 'action:agent_run_stop:result',
|
|
153
|
-
data: { success: true, message: `Stop requested for run ${stopEvent.data.runId}.` },
|
|
154
|
-
meta: stopEvent.meta,
|
|
155
|
-
}, state);
|
|
156
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
export const EventNormalizer = {
|
|
4
|
-
/**
|
|
5
|
-
* Normalizes incoming events, converting raw inputs like user:input to agent:invoke.
|
|
6
|
-
* Also handles initial state storage and event bus propagation for user inputs.
|
|
7
|
-
*/
|
|
8
|
-
normalize: async (event, options) => {
|
|
9
|
-
const { runId, agentId, channelId, threadId, onEvent } = options;
|
|
10
|
-
// 0. Ensure the incoming event has a unique ID immediately
|
|
11
|
-
ensureEventId(event);
|
|
12
|
-
let finalAgentId = agentId || 'system';
|
|
13
|
-
let finalEvent = event;
|
|
14
|
-
// 1. Convert user:input (or other raw inputs) to agent:invoke
|
|
15
|
-
const rawContent = event.data?.content || '';
|
|
16
|
-
if (event.type === 'user:input' || event.type === 'agent:invoke') {
|
|
17
|
-
const normalizedInvokeEvent = {
|
|
18
|
-
type: 'agent:invoke',
|
|
19
|
-
id: event.id,
|
|
20
|
-
data: {
|
|
21
|
-
content: rawContent,
|
|
22
|
-
role: 'user',
|
|
23
|
-
},
|
|
24
|
-
meta: {
|
|
25
|
-
agentId: 'system',
|
|
26
|
-
userId: event.meta?.userId,
|
|
27
|
-
userName: event.meta?.userName,
|
|
28
|
-
userAvatarUrl: event.meta?.userAvatarUrl,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
finalEvent = normalizedInvokeEvent;
|
|
32
|
-
// 1. Store the user's input in the current context (main channel or existing thread)
|
|
33
|
-
const initialState = await storageService.getOpenBotState({
|
|
34
|
-
runId,
|
|
35
|
-
agentId: 'system',
|
|
36
|
-
channelId,
|
|
37
|
-
threadId: threadId,
|
|
38
|
-
event: finalEvent,
|
|
39
|
-
});
|
|
40
|
-
// 2. Propagate the user's input to the event bus
|
|
41
|
-
await onEvent(finalEvent, initialState);
|
|
42
|
-
// 3. Prepare the event for the target agent
|
|
43
|
-
finalEvent = {
|
|
44
|
-
...event,
|
|
45
|
-
type: 'agent:invoke',
|
|
46
|
-
data: {
|
|
47
|
-
...(event.data || {}),
|
|
48
|
-
content: rawContent,
|
|
49
|
-
},
|
|
50
|
-
meta: {
|
|
51
|
-
...(event.meta || {}),
|
|
52
|
-
// The threadId in meta is the anchor for new threads (Slack-style)
|
|
53
|
-
threadId: threadId || finalEvent.id,
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return { finalEvent, finalAgentId };
|
|
58
|
-
},
|
|
59
|
-
};
|
package/dist/harness/history.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sliding window: max number of messages we replay to the model on each
|
|
3
|
-
* invocation. Older turns stay on disk but are not sent. Keeps both the
|
|
4
|
-
* recent prompts and the prompt token budget bounded.
|
|
5
|
-
*/
|
|
6
|
-
const MAX_WINDOW_MESSAGES = 80;
|
|
7
|
-
/**
|
|
8
|
-
* Reconstructs a valid `OpenBotMessage[]` chain from a raw event log.
|
|
9
|
-
* Handles grouping tool calls into assistant messages and matching results.
|
|
10
|
-
*
|
|
11
|
-
* This replaces the old `shortTermMessages` concept by treating the event log
|
|
12
|
-
* as the single source of truth for conversation history.
|
|
13
|
-
*/
|
|
14
|
-
export function reconstructHistory(events) {
|
|
15
|
-
const messages = [];
|
|
16
|
-
for (const event of events) {
|
|
17
|
-
switch (event.type) {
|
|
18
|
-
case 'user:input':
|
|
19
|
-
messages.push({ role: 'user', content: event.data.content });
|
|
20
|
-
break;
|
|
21
|
-
case 'agent:output': {
|
|
22
|
-
const last = messages[messages.length - 1];
|
|
23
|
-
if (last && last.role === 'assistant') {
|
|
24
|
-
if (typeof last.content === 'string') {
|
|
25
|
-
last.content += '\n' + event.data.content;
|
|
26
|
-
}
|
|
27
|
-
else if (Array.isArray(last.content)) {
|
|
28
|
-
const textPart = last.content.find((p) => p.type === 'text');
|
|
29
|
-
if (textPart && textPart.type === 'text') {
|
|
30
|
-
textPart.text += '\n' + event.data.content;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
last.content.unshift({ type: 'text', text: event.data.content });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
messages.push({ role: 'assistant', content: event.data.content });
|
|
39
|
-
}
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
case 'agent:invoke': {
|
|
43
|
-
const invokeEvent = event;
|
|
44
|
-
// Only treat as a message if it has content and is explicitly from a role
|
|
45
|
-
if (invokeEvent.data?.content && invokeEvent.data?.role) {
|
|
46
|
-
const role = invokeEvent.data.role;
|
|
47
|
-
messages.push({ role, content: invokeEvent.data.content });
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
default:
|
|
52
|
-
// Handle tool calls (action:*)
|
|
53
|
-
if (event.type.startsWith('action:') && !event.type.endsWith(':result')) {
|
|
54
|
-
const toolName = event.type.slice(7);
|
|
55
|
-
const toolCallId = event.meta?.toolCallId;
|
|
56
|
-
if (!toolCallId)
|
|
57
|
-
break;
|
|
58
|
-
const toolCall = {
|
|
59
|
-
type: 'tool-call',
|
|
60
|
-
toolCallId,
|
|
61
|
-
toolName,
|
|
62
|
-
input: event.data,
|
|
63
|
-
};
|
|
64
|
-
const last = messages[messages.length - 1];
|
|
65
|
-
if (last && last.role === 'assistant') {
|
|
66
|
-
if (typeof last.content === 'string') {
|
|
67
|
-
last.content = [
|
|
68
|
-
{ type: 'text', text: last.content },
|
|
69
|
-
toolCall,
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
last.content.push(toolCall);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
messages.push({
|
|
78
|
-
role: 'assistant',
|
|
79
|
-
content: [toolCall],
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Handle tool results (action:*:result)
|
|
84
|
-
else if (event.type.startsWith('action:') && event.type.endsWith(':result')) {
|
|
85
|
-
const toolName = event.type.slice(7, -7);
|
|
86
|
-
const toolCallId = event.meta?.toolCallId;
|
|
87
|
-
if (!toolCallId)
|
|
88
|
-
break;
|
|
89
|
-
const last = messages[messages.length - 1];
|
|
90
|
-
if (last && last.role === 'tool' && Array.isArray(last.content)) {
|
|
91
|
-
last.content.push({
|
|
92
|
-
type: 'tool-result',
|
|
93
|
-
toolCallId,
|
|
94
|
-
toolName,
|
|
95
|
-
output: event.data,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
messages.push({
|
|
100
|
-
role: 'tool',
|
|
101
|
-
content: [
|
|
102
|
-
{
|
|
103
|
-
type: 'tool-result',
|
|
104
|
-
toolCallId,
|
|
105
|
-
toolName,
|
|
106
|
-
output: event.data,
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return repairAndWindow(messages);
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Self-healing pass: every assistant tool_call must have a matching tool
|
|
119
|
-
* result before the next user/assistant turn. Also applies the sliding window.
|
|
120
|
-
*/
|
|
121
|
-
function repairAndWindow(messages) {
|
|
122
|
-
const fulfilled = new Set();
|
|
123
|
-
for (const m of messages) {
|
|
124
|
-
if (m.role === 'tool' && Array.isArray(m.content)) {
|
|
125
|
-
for (const part of m.content) {
|
|
126
|
-
if (part.type === 'tool-result') {
|
|
127
|
-
fulfilled.add(part.toolCallId);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const repaired = [];
|
|
133
|
-
for (const m of messages) {
|
|
134
|
-
repaired.push(m);
|
|
135
|
-
if (m.role !== 'assistant' || typeof m.content === 'string')
|
|
136
|
-
continue;
|
|
137
|
-
const missingResults = [];
|
|
138
|
-
for (const part of m.content) {
|
|
139
|
-
if (part.type === 'tool-call' && !fulfilled.has(part.toolCallId)) {
|
|
140
|
-
missingResults.push({
|
|
141
|
-
type: 'tool-result',
|
|
142
|
-
toolCallId: part.toolCallId,
|
|
143
|
-
toolName: part.toolName,
|
|
144
|
-
output: {
|
|
145
|
-
success: false,
|
|
146
|
-
error: 'Tool result was lost (handler did not emit a matching :result event).',
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
fulfilled.add(part.toolCallId);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (missingResults.length > 0) {
|
|
153
|
-
repaired.push({
|
|
154
|
-
role: 'tool',
|
|
155
|
-
content: missingResults,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (repaired.length <= MAX_WINDOW_MESSAGES)
|
|
160
|
-
return repaired;
|
|
161
|
-
const tail = repaired.slice(-MAX_WINDOW_MESSAGES);
|
|
162
|
-
// Ensure the tail doesn't start with an orphan tool result
|
|
163
|
-
const knownAssistantCallIds = new Set();
|
|
164
|
-
for (const m of tail) {
|
|
165
|
-
if (m.role === 'assistant' && Array.isArray(m.content)) {
|
|
166
|
-
for (const part of m.content) {
|
|
167
|
-
if (part.type === 'tool-call')
|
|
168
|
-
knownAssistantCallIds.add(part.toolCallId);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return tail.filter((m) => {
|
|
173
|
-
if (m.role !== 'tool' || typeof m.content === 'string')
|
|
174
|
-
return true;
|
|
175
|
-
return m.content.some((part) => part.type === 'tool-result' && knownAssistantCallIds.has(part.toolCallId));
|
|
176
|
-
});
|
|
177
|
-
}
|
package/dist/harness/mcp.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
-
import { loadConfig } from '../app/config.js';
|
|
4
|
-
class MCPService {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.clients = new Map();
|
|
7
|
-
this.transports = new Map();
|
|
8
|
-
}
|
|
9
|
-
getServerConfig(serverId) {
|
|
10
|
-
const config = loadConfig();
|
|
11
|
-
const server = (config.mcpServers || []).find((s) => s.id === serverId);
|
|
12
|
-
if (!server) {
|
|
13
|
-
throw new Error(`MCP server "${serverId}" is not configured`);
|
|
14
|
-
}
|
|
15
|
-
return server;
|
|
16
|
-
}
|
|
17
|
-
async getClient(serverId) {
|
|
18
|
-
const existing = this.clients.get(serverId);
|
|
19
|
-
if (existing) {
|
|
20
|
-
return existing;
|
|
21
|
-
}
|
|
22
|
-
const server = this.getServerConfig(serverId);
|
|
23
|
-
const client = new Client({
|
|
24
|
-
name: 'openbot-v2',
|
|
25
|
-
version: '0.1.0',
|
|
26
|
-
}, {
|
|
27
|
-
capabilities: {},
|
|
28
|
-
});
|
|
29
|
-
const transport = new StdioClientTransport({
|
|
30
|
-
command: server.command,
|
|
31
|
-
args: server.args || [],
|
|
32
|
-
env: server.env,
|
|
33
|
-
cwd: server.cwd,
|
|
34
|
-
});
|
|
35
|
-
await client.connect(transport);
|
|
36
|
-
this.clients.set(serverId, client);
|
|
37
|
-
this.transports.set(serverId, transport);
|
|
38
|
-
return client;
|
|
39
|
-
}
|
|
40
|
-
async listServers() {
|
|
41
|
-
const config = loadConfig();
|
|
42
|
-
return (config.mcpServers || []).map((server) => server.id);
|
|
43
|
-
}
|
|
44
|
-
async listTools(serverId) {
|
|
45
|
-
const client = await this.getClient(serverId);
|
|
46
|
-
const result = await client.listTools();
|
|
47
|
-
return (result.tools || []).map((tool) => ({
|
|
48
|
-
name: tool.name,
|
|
49
|
-
description: tool.description,
|
|
50
|
-
inputSchema: tool.inputSchema,
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
async callTool(serverId, toolName, args) {
|
|
54
|
-
const client = await this.getClient(serverId);
|
|
55
|
-
return client.callTool({
|
|
56
|
-
name: toolName,
|
|
57
|
-
arguments: args,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export const mcpService = new MCPService();
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { ORCHESTRATOR_AGENT_ID } from './context.js';
|
|
2
|
-
const readThreadState = (state) => state.threadDetails?.state ?? {};
|
|
3
|
-
export const readTodosFromState = (state) => {
|
|
4
|
-
const raw = readThreadState(state).todos;
|
|
5
|
-
return Array.isArray(raw) ? raw : [];
|
|
6
|
-
};
|
|
7
|
-
export const readOrchestration = (state) => {
|
|
8
|
-
const raw = readThreadState(state).orchestration;
|
|
9
|
-
if (!raw || typeof raw !== 'object')
|
|
10
|
-
return { active: false };
|
|
11
|
-
return raw;
|
|
12
|
-
};
|
|
13
|
-
export const isOrchestrationActive = (orch) => orch.active === true;
|
|
14
|
-
export const hasActiveTodos = (state) => readTodosFromState(state).some((t) => t.status === 'pending' || t.status === 'in_progress');
|
|
15
|
-
export const readPendingDelegation = (state) => readOrchestration(state).pendingDelegation;
|
|
16
|
-
async function patchOrchestration(storage, state, patch) {
|
|
17
|
-
if (!state.threadId)
|
|
18
|
-
throw new Error('No active thread');
|
|
19
|
-
const current = readOrchestration(state);
|
|
20
|
-
const next = { ...current, ...patch };
|
|
21
|
-
await storage.patchThreadState({
|
|
22
|
-
channelId: state.channelId,
|
|
23
|
-
threadId: state.threadId,
|
|
24
|
-
state: { orchestration: next },
|
|
25
|
-
});
|
|
26
|
-
state.threadDetails = await storage.getThreadDetails({
|
|
27
|
-
channelId: state.channelId,
|
|
28
|
-
threadId: state.threadId,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
export async function activateOrchestration(storage, state) {
|
|
32
|
-
const current = readOrchestration(state);
|
|
33
|
-
if (current.active)
|
|
34
|
-
return;
|
|
35
|
-
await patchOrchestration(storage, state, { active: true, startedAt: Date.now() });
|
|
36
|
-
}
|
|
37
|
-
export async function deactivateOrchestration(storage, state) {
|
|
38
|
-
const current = readOrchestration(state);
|
|
39
|
-
if (!current.active)
|
|
40
|
-
return;
|
|
41
|
-
await patchOrchestration(storage, state, {
|
|
42
|
-
active: false,
|
|
43
|
-
pendingDelegation: undefined,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
export async function queueDelegation(storage, state, delegation) {
|
|
47
|
-
await patchOrchestration(storage, state, {
|
|
48
|
-
active: true,
|
|
49
|
-
startedAt: readOrchestration(state).startedAt ?? Date.now(),
|
|
50
|
-
pendingDelegation: delegation,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
export async function clearPendingDelegation(storage, state) {
|
|
54
|
-
const current = readOrchestration(state);
|
|
55
|
-
if (!current.pendingDelegation)
|
|
56
|
-
return;
|
|
57
|
-
await patchOrchestration(storage, state, { pendingDelegation: undefined });
|
|
58
|
-
}
|
|
59
|
-
export function buildWorkerReviewPrompt(workerAgentId, output) {
|
|
60
|
-
const body = output && output.trim()
|
|
61
|
-
? output.trim()
|
|
62
|
-
: '(The worker produced no text output.)';
|
|
63
|
-
return [
|
|
64
|
-
'[Orchestrator — worker step completed]',
|
|
65
|
-
'',
|
|
66
|
-
`Worker: @${workerAgentId}`,
|
|
67
|
-
'Output:',
|
|
68
|
-
'---',
|
|
69
|
-
body,
|
|
70
|
-
'---',
|
|
71
|
-
'',
|
|
72
|
-
'Review the shared todo plan, update statuses with `todo_write` if needed,',
|
|
73
|
-
'tell the user if appropriate, then either call `delegate_to_agent` for the next step',
|
|
74
|
-
'or confirm the goal is complete.',
|
|
75
|
-
].join('\n');
|
|
76
|
-
}
|
|
77
|
-
export function buildPlanContinuePrompt() {
|
|
78
|
-
return [
|
|
79
|
-
'[Orchestrator — continue plan execution]',
|
|
80
|
-
'',
|
|
81
|
-
'You have an active todo plan with remaining work.',
|
|
82
|
-
'Review the plan and either call `delegate_to_agent` for the next step',
|
|
83
|
-
'or handle the work yourself. Update todos to reflect progress.',
|
|
84
|
-
].join('\n');
|
|
85
|
-
}
|
|
86
|
-
export function isOrchestratorAgent(agentId) {
|
|
87
|
-
return agentId === ORCHESTRATOR_AGENT_ID;
|
|
88
|
-
}
|