openbot 0.4.7 → 0.5.4
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 +50 -0
- package/Dockerfile +41 -0
- package/deploy/README.md +61 -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 +3 -1
- package/dist/app/cloud-mode.js +26 -0
- package/dist/app/config.js +61 -16
- package/dist/app/server.js +38 -6
- 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 -8
- package/dist/plugins/openbot/model.js +59 -0
- package/dist/plugins/openbot/runtime.js +39 -56
- package/dist/plugins/openbot/system-prompt.js +43 -8
- package/dist/plugins/preview/index.js +267 -0
- package/dist/plugins/storage/index.js +3 -3
- package/dist/plugins/storage/service.js +58 -12
- 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/model-registry.js +101 -0
- package/dist/services/plugins/registry.js +6 -4
- 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 +1 -1
- package/package.json +5 -2
- package/pnpm-workspace.yaml +2 -0
- package/src/app/bootstrap.ts +43 -0
- package/src/app/cli.ts +3 -1
- package/src/app/cloud-mode.ts +41 -0
- package/src/app/config.ts +71 -19
- package/src/app/server.ts +45 -6
- package/src/app/types.ts +162 -31
- package/src/plugins/approval/index.ts +1 -1
- package/src/plugins/bash/index.ts +498 -185
- package/src/plugins/memory/service.ts +2 -5
- package/src/plugins/openbot/context.ts +17 -1
- package/src/plugins/openbot/index.ts +25 -10
- package/src/plugins/openbot/model.ts +76 -0
- package/src/plugins/openbot/runtime.ts +47 -69
- package/src/plugins/openbot/system-prompt.ts +43 -8
- package/src/plugins/preview/index.ts +323 -0
- package/src/plugins/storage/index.ts +5 -8
- package/src/plugins/storage/service.ts +79 -15
- package/src/plugins/todo/index.ts +166 -0
- package/src/plugins/todo/service.ts +123 -0
- package/src/plugins/ui/index.ts +8 -1
- package/src/services/plugins/domain.ts +2 -0
- package/src/services/plugins/model-registry.ts +146 -0
- package/src/services/plugins/registry.ts +6 -4
- package/src/services/plugins/service.ts +2 -6
- 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/bus/types.js +0 -1
- 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
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { storageService } from '../services/storage.js';
|
|
2
|
-
import { createAgentRuntime } from './runtime-factory.js';
|
|
3
|
-
import { EventNormalizer } from './event-normalizer.js';
|
|
4
|
-
import { QueueProcessor } from './queue-processor.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 = (options) => {
|
|
16
|
-
pruneStopRequests();
|
|
17
|
-
return stopRequests.find((request) => {
|
|
18
|
-
if (request.runId !== options.runId)
|
|
19
|
-
return false;
|
|
20
|
-
if (request.agentId && request.agentId !== options.agentId)
|
|
21
|
-
return false;
|
|
22
|
-
if (request.channelId && request.channelId !== options.channelId)
|
|
23
|
-
return false;
|
|
24
|
-
if (request.threadId && request.threadId !== options.threadId)
|
|
25
|
-
return false;
|
|
26
|
-
return true;
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
export const orchestratorService = {
|
|
30
|
-
/**
|
|
31
|
-
* The primary entry point for all events coming into the system (e.g. from the API).
|
|
32
|
-
* Handles routing and initial UI message creation.
|
|
33
|
-
*/
|
|
34
|
-
dispatch: async (options) => {
|
|
35
|
-
const { runId, channelId, threadId, onEvent } = options;
|
|
36
|
-
if (options.event.type === 'action:agent_run_stop') {
|
|
37
|
-
const stopEvent = options.event;
|
|
38
|
-
stopRequests.push({
|
|
39
|
-
runId: stopEvent.data.runId,
|
|
40
|
-
agentId: stopEvent.data.agentId,
|
|
41
|
-
channelId: stopEvent.data.channelId || channelId,
|
|
42
|
-
threadId: stopEvent.data.threadId || threadId,
|
|
43
|
-
reason: stopEvent.data.reason,
|
|
44
|
-
requestedAt: Date.now(),
|
|
45
|
-
});
|
|
46
|
-
const state = await storageService.getOpenBotState({
|
|
47
|
-
runId,
|
|
48
|
-
agentId: options.agentId || 'system',
|
|
49
|
-
channelId,
|
|
50
|
-
threadId,
|
|
51
|
-
event: options.event,
|
|
52
|
-
});
|
|
53
|
-
await onEvent({
|
|
54
|
-
type: 'action:agent_run_stop:result',
|
|
55
|
-
data: {
|
|
56
|
-
success: true,
|
|
57
|
-
message: `Stop requested for run ${stopEvent.data.runId}.`,
|
|
58
|
-
},
|
|
59
|
-
meta: options.event.meta,
|
|
60
|
-
}, state);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
// 1. Normalize incoming event
|
|
64
|
-
const { finalEvent, finalAgentId } = await EventNormalizer.normalize(options.event, {
|
|
65
|
-
runId,
|
|
66
|
-
agentId: options.agentId,
|
|
67
|
-
channelId,
|
|
68
|
-
threadId,
|
|
69
|
-
onEvent,
|
|
70
|
-
});
|
|
71
|
-
// 2. Initialize Queue Processor
|
|
72
|
-
const processor = new QueueProcessor({
|
|
73
|
-
runId,
|
|
74
|
-
channelId,
|
|
75
|
-
threadId,
|
|
76
|
-
onEvent,
|
|
77
|
-
executeAgent: orchestratorService.executeAgent,
|
|
78
|
-
shouldStopRun: orchestratorService.shouldStopRun,
|
|
79
|
-
});
|
|
80
|
-
// 3. Enqueue initial event
|
|
81
|
-
processor.enqueue({ agentId: finalAgentId, event: finalEvent });
|
|
82
|
-
// 4. Run execution loop
|
|
83
|
-
await processor.run();
|
|
84
|
-
},
|
|
85
|
-
/**
|
|
86
|
-
* Executes a single agent runtime.
|
|
87
|
-
*/
|
|
88
|
-
executeAgent: async (options) => {
|
|
89
|
-
const { runId, agentId, event, channelId, threadId, onEvent } = options;
|
|
90
|
-
let agentState;
|
|
91
|
-
try {
|
|
92
|
-
agentState = await storageService.getOpenBotState(options);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
if (error.code === 'AGENT_NOT_FOUND') {
|
|
96
|
-
const fallbackState = await storageService.getOpenBotState({
|
|
97
|
-
runId,
|
|
98
|
-
agentId: 'system',
|
|
99
|
-
channelId,
|
|
100
|
-
threadId,
|
|
101
|
-
event,
|
|
102
|
-
});
|
|
103
|
-
const warning = `⚠️ Agent **${agentId}** does not exist. Please check the agent ID and try again.`;
|
|
104
|
-
await onEvent({
|
|
105
|
-
type: 'agent:output',
|
|
106
|
-
data: { content: warning },
|
|
107
|
-
meta: { agentId: 'system', threadId },
|
|
108
|
-
}, fallbackState);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
const agentRuntime = await createAgentRuntime(agentState);
|
|
114
|
-
const stopInfo = {
|
|
115
|
-
runId,
|
|
116
|
-
agentId,
|
|
117
|
-
channelId,
|
|
118
|
-
threadId,
|
|
119
|
-
};
|
|
120
|
-
try {
|
|
121
|
-
// RUN the agent runtime
|
|
122
|
-
for await (const chunk of agentRuntime.run(event, { state: agentState, runId })) {
|
|
123
|
-
const stopRequest = findStopRequest(stopInfo);
|
|
124
|
-
if (stopRequest) {
|
|
125
|
-
await onEvent({
|
|
126
|
-
type: 'agent:run:stopped',
|
|
127
|
-
data: {
|
|
128
|
-
runId,
|
|
129
|
-
agentId,
|
|
130
|
-
channelId,
|
|
131
|
-
threadId,
|
|
132
|
-
reason: stopRequest.reason,
|
|
133
|
-
},
|
|
134
|
-
}, agentState);
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
chunk.meta = { ...chunk.meta, agentId };
|
|
138
|
-
await onEvent(chunk, agentState);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
console.error(`[orchestrator] Agent run failed: ${agentId}`, error);
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
shouldStopRun: (options) => {
|
|
146
|
-
const request = findStopRequest(options);
|
|
147
|
-
return request ? { shouldStop: true, reason: request.reason } : { shouldStop: false };
|
|
148
|
-
},
|
|
149
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Channel `participants` (from `state.json`) scope which agents may collaborate
|
|
3
|
-
* in that channel. Used for system-prompt hints and dispatch guards.
|
|
4
|
-
*/
|
|
5
|
-
/** Solo DM: exactly one participant and it is the acting agent (no peer bots). */
|
|
6
|
-
export function isDmSoloChannel(participants, actingAgentId) {
|
|
7
|
-
return participants.length === 1 && participants[0] === actingAgentId;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* When `participants` is non-empty, dispatch targets must appear in that list.
|
|
11
|
-
* Solo DM forbids targeting any agent other than yourself.
|
|
12
|
-
*/
|
|
13
|
-
export function isParticipantDispatchAllowed(participants, actingAgentId, targetAgentId) {
|
|
14
|
-
if (participants.length === 0)
|
|
15
|
-
return true;
|
|
16
|
-
if (!participants.includes(targetAgentId))
|
|
17
|
-
return false;
|
|
18
|
-
if (isDmSoloChannel(participants, actingAgentId) && targetAgentId !== actingAgentId) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
return true;
|
|
22
|
-
}
|
package/dist/harness/process.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { loadVariables } from '../app/config.js';
|
|
2
|
-
/** Keys last applied from workspace `variables.json` (used to unset removed entries). */
|
|
3
|
-
let lastWorkspaceVariableKeys = new Set();
|
|
4
|
-
function applyVariablesList(variables) {
|
|
5
|
-
const nextKeys = new Set(variables.map((v) => v.key));
|
|
6
|
-
for (const key of lastWorkspaceVariableKeys) {
|
|
7
|
-
if (!nextKeys.has(key)) {
|
|
8
|
-
delete process.env[key];
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
for (const variable of variables) {
|
|
12
|
-
process.env[variable.key] = variable.value;
|
|
13
|
-
}
|
|
14
|
-
lastWorkspaceVariableKeys = nextKeys;
|
|
15
|
-
}
|
|
16
|
-
export const processService = {
|
|
17
|
-
/**
|
|
18
|
-
* Reload workspace variables from disk into `process.env`.
|
|
19
|
-
* Call after server start and whenever `variables.json` changes.
|
|
20
|
-
*/
|
|
21
|
-
syncWorkspaceVariablesToProcessEnv: () => {
|
|
22
|
-
const { variables } = loadVariables();
|
|
23
|
-
applyVariablesList(variables);
|
|
24
|
-
},
|
|
25
|
-
/** Apply a variable list directly (same unset semantics as sync). Prefer `syncWorkspaceVariablesToProcessEnv` when reading from disk. */
|
|
26
|
-
applyVariablesToProcessEnv: (variables) => {
|
|
27
|
-
applyVariablesList(variables);
|
|
28
|
-
},
|
|
29
|
-
};
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
import { advanceAfterRun } from './todo-advance.js';
|
|
4
|
-
export class QueueProcessor {
|
|
5
|
-
constructor(options) {
|
|
6
|
-
this.options = options;
|
|
7
|
-
this.currentQueue = [];
|
|
8
|
-
this.MAX_ITERATIONS = 20;
|
|
9
|
-
this.currentThreadId = options.threadId;
|
|
10
|
-
}
|
|
11
|
-
enqueue(item) {
|
|
12
|
-
this.currentQueue.push(item);
|
|
13
|
-
}
|
|
14
|
-
async run() {
|
|
15
|
-
let iterations = 0;
|
|
16
|
-
while (this.currentQueue.length > 0 && iterations < this.MAX_ITERATIONS) {
|
|
17
|
-
iterations++;
|
|
18
|
-
// Group by agentId to avoid parallel state corruption for the same agent.
|
|
19
|
-
const groups = new Map();
|
|
20
|
-
for (const item of this.currentQueue) {
|
|
21
|
-
const list = groups.get(item.agentId) || [];
|
|
22
|
-
list.push(item);
|
|
23
|
-
groups.set(item.agentId, list);
|
|
24
|
-
}
|
|
25
|
-
const nextQueue = [];
|
|
26
|
-
// Run each agent group in parallel
|
|
27
|
-
await Promise.all(Array.from(groups.entries()).map(async ([agentId, items]) => {
|
|
28
|
-
// Run items for the SAME agent sequentially to preserve event order and state consistency.
|
|
29
|
-
for (const item of items) {
|
|
30
|
-
const { event: currentEvent } = item;
|
|
31
|
-
const stopCheck = this.options.shouldStopRun?.({
|
|
32
|
-
runId: this.options.runId,
|
|
33
|
-
agentId,
|
|
34
|
-
channelId: this.options.channelId,
|
|
35
|
-
threadId: this.currentThreadId,
|
|
36
|
-
});
|
|
37
|
-
if (stopCheck?.shouldStop) {
|
|
38
|
-
const stoppedState = await storageService.getOpenBotState({
|
|
39
|
-
runId: this.options.runId,
|
|
40
|
-
agentId,
|
|
41
|
-
channelId: this.options.channelId,
|
|
42
|
-
threadId: this.currentThreadId,
|
|
43
|
-
event: currentEvent,
|
|
44
|
-
});
|
|
45
|
-
await this.options.onEvent({
|
|
46
|
-
type: 'agent:run:stopped',
|
|
47
|
-
data: {
|
|
48
|
-
runId: this.options.runId,
|
|
49
|
-
agentId,
|
|
50
|
-
channelId: this.options.channelId,
|
|
51
|
-
threadId: this.currentThreadId,
|
|
52
|
-
reason: stopCheck.reason,
|
|
53
|
-
},
|
|
54
|
-
}, stoppedState);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
// Track handoff requests queued in this step to avoid accidental duplicates.
|
|
58
|
-
const queuedRequestKeys = new Set();
|
|
59
|
-
const queuedItems = [];
|
|
60
|
-
let lastAgentOutput;
|
|
61
|
-
const runOnEvent = async (chunk, state) => {
|
|
62
|
-
// 0. Filter out echoed input events to prevent duplication in the UI/storage
|
|
63
|
-
if (chunk.type === currentEvent.type && chunk.id === currentEvent.id) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
if (chunk.type === 'agent:output') {
|
|
67
|
-
const outMeta = chunk.meta;
|
|
68
|
-
if (outMeta?.agentId === agentId) {
|
|
69
|
-
const content = chunk.data?.content;
|
|
70
|
-
if (typeof content === 'string' && content.trim()) {
|
|
71
|
-
lastAgentOutput = content.trim();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// 1. Detect if a new thread was created and update the context for the rest of the loop
|
|
76
|
-
if (chunk.type === 'action:create_thread:result' && chunk.data.success) {
|
|
77
|
-
this.currentThreadId = chunk.data.threadId || this.currentThreadId;
|
|
78
|
-
}
|
|
79
|
-
// 2. Internal routing (handoff requests are internal — not forwarded)
|
|
80
|
-
if (chunk.type === 'handoff:request') {
|
|
81
|
-
const request = chunk;
|
|
82
|
-
const targetAgentId = request.data?.agentId;
|
|
83
|
-
const requestKey = `handoff:${targetAgentId}`;
|
|
84
|
-
if (targetAgentId &&
|
|
85
|
-
targetAgentId !== agentId &&
|
|
86
|
-
!queuedRequestKeys.has(requestKey)) {
|
|
87
|
-
queuedRequestKeys.add(requestKey);
|
|
88
|
-
const targetEvent = ensureEventId({
|
|
89
|
-
type: 'agent:invoke',
|
|
90
|
-
data: {
|
|
91
|
-
role: 'user',
|
|
92
|
-
content: request.data.content,
|
|
93
|
-
},
|
|
94
|
-
meta: {
|
|
95
|
-
...(request.meta || {}),
|
|
96
|
-
threadId: this.currentThreadId,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
queuedItems.push({ agentId: targetAgentId, event: targetEvent });
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
// If we get here, the event is accepted and should be emitted.
|
|
104
|
-
await this.options.onEvent(chunk, state);
|
|
105
|
-
return true;
|
|
106
|
-
};
|
|
107
|
-
const startState = await storageService.getOpenBotState({
|
|
108
|
-
runId: this.options.runId,
|
|
109
|
-
agentId,
|
|
110
|
-
channelId: this.options.channelId,
|
|
111
|
-
threadId: this.currentThreadId,
|
|
112
|
-
event: currentEvent,
|
|
113
|
-
});
|
|
114
|
-
await this.options.onEvent({
|
|
115
|
-
type: 'agent:run:start',
|
|
116
|
-
data: {
|
|
117
|
-
runId: this.options.runId,
|
|
118
|
-
agentId,
|
|
119
|
-
channelId: this.options.channelId,
|
|
120
|
-
threadId: this.currentThreadId,
|
|
121
|
-
},
|
|
122
|
-
}, startState);
|
|
123
|
-
try {
|
|
124
|
-
await this.options.executeAgent({
|
|
125
|
-
runId: this.options.runId,
|
|
126
|
-
agentId,
|
|
127
|
-
event: currentEvent,
|
|
128
|
-
channelId: this.options.channelId,
|
|
129
|
-
threadId: this.currentThreadId,
|
|
130
|
-
onEvent: runOnEvent,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
const endState = await storageService.getOpenBotState({
|
|
135
|
-
runId: this.options.runId,
|
|
136
|
-
agentId,
|
|
137
|
-
channelId: this.options.channelId,
|
|
138
|
-
threadId: this.currentThreadId,
|
|
139
|
-
event: currentEvent,
|
|
140
|
-
});
|
|
141
|
-
await this.options.onEvent({
|
|
142
|
-
type: 'agent:run:end',
|
|
143
|
-
data: {
|
|
144
|
-
runId: this.options.runId,
|
|
145
|
-
agentId,
|
|
146
|
-
channelId: this.options.channelId,
|
|
147
|
-
threadId: this.currentThreadId,
|
|
148
|
-
},
|
|
149
|
-
}, endState);
|
|
150
|
-
// Autonomous todo advance: mark this agent's in_progress todo done
|
|
151
|
-
// and dispatch the next assignee, if any. Single trigger point,
|
|
152
|
-
// no reliance on the LLM remembering to call `todo_update`.
|
|
153
|
-
try {
|
|
154
|
-
const handoff = await advanceAfterRun({
|
|
155
|
-
storage: storageService,
|
|
156
|
-
channelId: this.options.channelId,
|
|
157
|
-
threadId: this.currentThreadId,
|
|
158
|
-
endedAgentId: agentId,
|
|
159
|
-
lastAgentOutput,
|
|
160
|
-
});
|
|
161
|
-
if (handoff) {
|
|
162
|
-
const requestKey = `handoff:${handoff.agentId}`;
|
|
163
|
-
if (!queuedRequestKeys.has(requestKey)) {
|
|
164
|
-
queuedRequestKeys.add(requestKey);
|
|
165
|
-
const targetEvent = ensureEventId({
|
|
166
|
-
type: 'agent:invoke',
|
|
167
|
-
data: { role: 'user', content: handoff.content },
|
|
168
|
-
meta: { threadId: this.currentThreadId },
|
|
169
|
-
});
|
|
170
|
-
queuedItems.push({ agentId: handoff.agentId, event: targetEvent });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
console.warn('[queue] todo advance failed', error);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
nextQueue.push(...queuedItems);
|
|
179
|
-
}
|
|
180
|
-
}));
|
|
181
|
-
this.currentQueue = nextQueue;
|
|
182
|
-
}
|
|
183
|
-
if (iterations >= this.MAX_ITERATIONS) {
|
|
184
|
-
console.warn(`[orchestrator] Reached MAX_ITERATIONS (${this.MAX_ITERATIONS}). Stopping execution.`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
import { createAgentRuntime } from './runtime-factory.js';
|
|
4
|
-
const stopRequests = [];
|
|
5
|
-
const STOP_TTL_MS = 30 * 60 * 1000;
|
|
6
|
-
const pruneStops = () => {
|
|
7
|
-
const cutoff = Date.now() - STOP_TTL_MS;
|
|
8
|
-
for (let i = stopRequests.length - 1; i >= 0; i -= 1) {
|
|
9
|
-
if (stopRequests[i].requestedAt < cutoff)
|
|
10
|
-
stopRequests.splice(i, 1);
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
const findStop = (target) => {
|
|
14
|
-
pruneStops();
|
|
15
|
-
return stopRequests.find((r) => r.runId === target.runId &&
|
|
16
|
-
(!r.agentId || r.agentId === target.agentId) &&
|
|
17
|
-
(!r.channelId || r.channelId === target.channelId) &&
|
|
18
|
-
(!r.threadId || r.threadId === target.threadId));
|
|
19
|
-
};
|
|
20
|
-
const target = (ctx, agentId) => ({ ...ctx, agentId });
|
|
21
|
-
const loadState = (t, event) => storageService.getOpenBotState({ ...t, event });
|
|
22
|
-
export async function runHarness(options) {
|
|
23
|
-
const { event } = options;
|
|
24
|
-
ensureEventId(event);
|
|
25
|
-
if (event.type === 'action:agent_run_stop') {
|
|
26
|
-
await handleStop(event, options);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const ctx = {
|
|
30
|
-
runId: options.runId,
|
|
31
|
-
channelId: options.channelId,
|
|
32
|
-
threadId: options.threadId,
|
|
33
|
-
onEvent: options.onEvent,
|
|
34
|
-
};
|
|
35
|
-
const agentId = options.agentId || 'system';
|
|
36
|
-
if (event.type === 'user:input' || event.type === 'agent:invoke') {
|
|
37
|
-
await runStep(ctx, agentId, await prepareInvoke(event, ctx));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
await runPassThrough(ctx, agentId, event);
|
|
41
|
-
}
|
|
42
|
-
async function runStep(ctx, agentId, event) {
|
|
43
|
-
const t = target(ctx, agentId);
|
|
44
|
-
const early = findStop(t);
|
|
45
|
-
if (early) {
|
|
46
|
-
const state = await loadState(t, event);
|
|
47
|
-
await ctx.onEvent({ type: 'agent:run:stopped', data: { ...t, reason: early.reason } }, state);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
let state;
|
|
51
|
-
try {
|
|
52
|
-
state = await loadState(t, event);
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
if (error.code !== 'AGENT_NOT_FOUND')
|
|
56
|
-
throw error;
|
|
57
|
-
const fallback = await loadState(target(ctx, 'system'), event);
|
|
58
|
-
await ctx.onEvent({
|
|
59
|
-
type: 'agent:output',
|
|
60
|
-
data: {
|
|
61
|
-
content: `⚠️ Agent **${agentId}** does not exist. Please check the agent ID and try again.`,
|
|
62
|
-
},
|
|
63
|
-
meta: { agentId: 'system', threadId: ctx.threadId },
|
|
64
|
-
}, fallback);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
await ctx.onEvent({ type: 'agent:run:start', data: { ...t } }, state);
|
|
68
|
-
try {
|
|
69
|
-
await streamEvents(ctx, t, event, state, true);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
console.error(`[run-harness] Agent run failed: ${agentId}`, error);
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
const after = await loadState(t, event);
|
|
76
|
-
await ctx.onEvent({ type: 'agent:run:end', data: { ...t } }, after);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async function runPassThrough(ctx, agentId, event) {
|
|
80
|
-
const t = target(ctx, agentId);
|
|
81
|
-
let state;
|
|
82
|
-
try {
|
|
83
|
-
state = await loadState(t, event);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (error.code === 'AGENT_NOT_FOUND')
|
|
87
|
-
return;
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
await streamEvents(ctx, t, event, state, false);
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error(`[run-harness] Event failed: ${event.type} (${agentId})`, error);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async function streamEvents(ctx, t, event, state, step) {
|
|
98
|
-
const runtime = await createAgentRuntime(state);
|
|
99
|
-
for await (const chunk of runtime.run(event, { state, runId: ctx.runId })) {
|
|
100
|
-
if (step) {
|
|
101
|
-
const stop = findStop(t);
|
|
102
|
-
if (stop) {
|
|
103
|
-
await ctx.onEvent({ type: 'agent:run:stopped', data: { ...t, reason: stop.reason } }, state);
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (chunk.id === event.id && chunk.type === event.type)
|
|
108
|
-
continue;
|
|
109
|
-
if (step && chunk.type === 'action:create_thread:result' && chunk.data.success) {
|
|
110
|
-
ctx.threadId = chunk.data.threadId || ctx.threadId;
|
|
111
|
-
}
|
|
112
|
-
chunk.meta = { ...chunk.meta, agentId: t.agentId };
|
|
113
|
-
await ctx.onEvent(chunk, state);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
async function prepareInvoke(event, ctx) {
|
|
117
|
-
const rawContent = event.data?.content || '';
|
|
118
|
-
const userFacing = {
|
|
119
|
-
type: 'agent:invoke',
|
|
120
|
-
id: event.id,
|
|
121
|
-
data: { content: rawContent, role: 'user' },
|
|
122
|
-
meta: {
|
|
123
|
-
agentId: 'system',
|
|
124
|
-
userId: event.meta?.userId,
|
|
125
|
-
userName: event.meta?.userName,
|
|
126
|
-
userAvatarUrl: event.meta?.userAvatarUrl,
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
const initialState = await loadState(target(ctx, 'system'), userFacing);
|
|
130
|
-
await ctx.onEvent(userFacing, initialState);
|
|
131
|
-
return {
|
|
132
|
-
...event,
|
|
133
|
-
type: 'agent:invoke',
|
|
134
|
-
data: { ...(event.data || {}), content: rawContent, role: 'user' },
|
|
135
|
-
meta: { ...(event.meta || {}), threadId: ctx.threadId || event.id },
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
async function handleStop(stopEvent, options) {
|
|
139
|
-
const { runId, channelId, threadId, onEvent } = options;
|
|
140
|
-
stopRequests.push({
|
|
141
|
-
runId: stopEvent.data.runId,
|
|
142
|
-
agentId: stopEvent.data.agentId,
|
|
143
|
-
channelId: stopEvent.data.channelId || channelId,
|
|
144
|
-
threadId: stopEvent.data.threadId || threadId,
|
|
145
|
-
reason: stopEvent.data.reason,
|
|
146
|
-
requestedAt: Date.now(),
|
|
147
|
-
});
|
|
148
|
-
const state = await loadState({ runId, channelId, threadId, onEvent, agentId: options.agentId || 'system' }, stopEvent);
|
|
149
|
-
await onEvent({
|
|
150
|
-
type: 'action:agent_run_stop:result',
|
|
151
|
-
data: { success: true, message: `Stop requested for run ${stopEvent.data.runId}.` },
|
|
152
|
-
meta: stopEvent.meta,
|
|
153
|
-
}, state);
|
|
154
|
-
}
|
package/dist/harness/run.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { ensureEventId } from '../app/utils.js';
|
|
2
|
-
import { storageService } from '../services/storage.js';
|
|
3
|
-
import { createAgentRuntime } from './runtime.js';
|
|
4
|
-
const TODO_RESULT_MAX_CHARS = 12000;
|
|
5
|
-
const readTodos = (state) => {
|
|
6
|
-
const raw = state.threadDetails?.state?.todos;
|
|
7
|
-
return Array.isArray(raw) ? raw : [];
|
|
8
|
-
};
|
|
9
|
-
function truncateTodoResult(text, maxChars = TODO_RESULT_MAX_CHARS) {
|
|
10
|
-
const trimmed = text.trim();
|
|
11
|
-
if (!trimmed)
|
|
12
|
-
return undefined;
|
|
13
|
-
if (trimmed.length <= maxChars)
|
|
14
|
-
return trimmed;
|
|
15
|
-
return `${trimmed.slice(0, maxChars)}\n…[truncated]`;
|
|
16
|
-
}
|
|
17
|
-
function resolveTodoIdForWorker(todos, workerId, delegationTodoId) {
|
|
18
|
-
if (delegationTodoId && todos.some((t) => t.id === delegationTodoId)) {
|
|
19
|
-
return delegationTodoId;
|
|
20
|
-
}
|
|
21
|
-
const inProgress = todos.find((t) => t.status === 'in_progress' && t.assignee === workerId);
|
|
22
|
-
if (inProgress)
|
|
23
|
-
return inProgress.id;
|
|
24
|
-
const assigned = todos.find((t) => (t.status === 'pending' || t.status === 'in_progress') && t.assignee === workerId);
|
|
25
|
-
return assigned?.id;
|
|
26
|
-
}
|
|
27
|
-
async function recordWorkerTodoResult(state, workerId, output, delegationTodoId) {
|
|
28
|
-
if (!state.threadId)
|
|
29
|
-
return;
|
|
30
|
-
const result = truncateTodoResult(output ?? '');
|
|
31
|
-
if (!result)
|
|
32
|
-
return;
|
|
33
|
-
const todos = readTodos(state);
|
|
34
|
-
if (todos.length === 0)
|
|
35
|
-
return;
|
|
36
|
-
const todoId = resolveTodoIdForWorker(todos, workerId, delegationTodoId);
|
|
37
|
-
if (!todoId)
|
|
38
|
-
return;
|
|
39
|
-
const prior = todos.find((t) => t.id === todoId);
|
|
40
|
-
if (prior?.result === result)
|
|
41
|
-
return;
|
|
42
|
-
const now = Date.now();
|
|
43
|
-
const next = todos.map((t) => (t.id === todoId ? { ...t, result, updatedAt: now } : t));
|
|
44
|
-
await storageService.patchThreadState({
|
|
45
|
-
channelId: state.channelId,
|
|
46
|
-
threadId: state.threadId,
|
|
47
|
-
state: { todos: next },
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
export function makeInternalInvoke(content, threadId) {
|
|
51
|
-
return ensureEventId({
|
|
52
|
-
type: 'agent:invoke',
|
|
53
|
-
data: { role: 'user', content },
|
|
54
|
-
meta: { threadId, internal: true },
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Run one agent turn. Yields runtime events for persistence/streaming;
|
|
59
|
-
* returns the last non-empty `agent:output` text when used as a worker.
|
|
60
|
-
*/
|
|
61
|
-
export async function* runTurn(options) {
|
|
62
|
-
const { runId, channelId, threadId, agentId, event, delegationTodoId } = options;
|
|
63
|
-
const target = { runId, agentId, channelId, threadId };
|
|
64
|
-
let state;
|
|
65
|
-
try {
|
|
66
|
-
state = await storageService.getOpenBotState({ ...target, event });
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (error.code === 'AGENT_NOT_FOUND') {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
74
|
-
yield { type: 'agent:run:start', data: { ...target } };
|
|
75
|
-
let lastAgentOutput;
|
|
76
|
-
try {
|
|
77
|
-
const runtime = await createAgentRuntime(state);
|
|
78
|
-
for await (const chunk of runtime.run(event, { state, runId })) {
|
|
79
|
-
if (chunk.id === event.id && chunk.type === event.type)
|
|
80
|
-
continue;
|
|
81
|
-
if (chunk.type === 'agent:output' &&
|
|
82
|
-
chunk.meta?.agentId === agentId) {
|
|
83
|
-
const content = chunk.data?.content;
|
|
84
|
-
if (typeof content === 'string' && content.trim()) {
|
|
85
|
-
lastAgentOutput = content.trim();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
chunk.meta = { ...chunk.meta, agentId };
|
|
89
|
-
yield chunk;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
const stateAfterRun = await storageService.getOpenBotState({ ...target, event });
|
|
94
|
-
yield { type: 'agent:run:end', data: { ...target } };
|
|
95
|
-
await recordWorkerTodoResult(stateAfterRun, agentId, lastAgentOutput, delegationTodoId);
|
|
96
|
-
}
|
|
97
|
-
return lastAgentOutput;
|
|
98
|
-
}
|