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.
Files changed (130) hide show
  1. package/.claude/agents/runtime-security-reviewer.md +32 -0
  2. package/.claude/hooks/format.sh +18 -0
  3. package/.claude/hooks/protect-secrets.sh +23 -0
  4. package/.claude/settings.json +26 -0
  5. package/.claude/skills/deploy/SKILL.md +26 -0
  6. package/.claude/skills/new-plugin/SKILL.md +46 -0
  7. package/.dockerignore +9 -0
  8. package/.mcp.json +8 -0
  9. package/AGENTS.md +2 -2
  10. package/CLAUDE.md +50 -0
  11. package/Dockerfile +41 -0
  12. package/deploy/README.md +61 -0
  13. package/deploy/entrypoint.sh +6 -0
  14. package/deploy/fly.toml +36 -0
  15. package/dist/app/bootstrap.js +29 -0
  16. package/dist/app/cli.js +3 -1
  17. package/dist/app/cloud-mode.js +26 -0
  18. package/dist/app/config.js +61 -16
  19. package/dist/app/server.js +38 -6
  20. package/dist/plugins/approval/index.js +1 -1
  21. package/dist/plugins/bash/index.js +386 -164
  22. package/dist/plugins/memory/service.js +2 -5
  23. package/dist/plugins/openbot/context.js +17 -1
  24. package/dist/plugins/openbot/index.js +23 -8
  25. package/dist/plugins/openbot/model.js +59 -0
  26. package/dist/plugins/openbot/runtime.js +39 -56
  27. package/dist/plugins/openbot/system-prompt.js +43 -8
  28. package/dist/plugins/preview/index.js +267 -0
  29. package/dist/plugins/storage/index.js +3 -3
  30. package/dist/plugins/storage/service.js +58 -12
  31. package/dist/plugins/todo/index.js +131 -38
  32. package/dist/plugins/todo/service.js +93 -0
  33. package/dist/plugins/ui/index.js +8 -1
  34. package/dist/services/plugins/model-registry.js +101 -0
  35. package/dist/services/plugins/registry.js +6 -4
  36. package/dist/services/plugins/service.js +2 -6
  37. package/docs/agents.md +20 -2
  38. package/docs/architecture.md +1 -1
  39. package/docs/plugins.md +13 -3
  40. package/docs/templates/AGENT.example.md +1 -1
  41. package/package.json +5 -2
  42. package/pnpm-workspace.yaml +2 -0
  43. package/src/app/bootstrap.ts +43 -0
  44. package/src/app/cli.ts +3 -1
  45. package/src/app/cloud-mode.ts +41 -0
  46. package/src/app/config.ts +71 -19
  47. package/src/app/server.ts +45 -6
  48. package/src/app/types.ts +162 -31
  49. package/src/plugins/approval/index.ts +1 -1
  50. package/src/plugins/bash/index.ts +498 -185
  51. package/src/plugins/memory/service.ts +2 -5
  52. package/src/plugins/openbot/context.ts +17 -1
  53. package/src/plugins/openbot/index.ts +25 -10
  54. package/src/plugins/openbot/model.ts +76 -0
  55. package/src/plugins/openbot/runtime.ts +47 -69
  56. package/src/plugins/openbot/system-prompt.ts +43 -8
  57. package/src/plugins/preview/index.ts +323 -0
  58. package/src/plugins/storage/index.ts +5 -8
  59. package/src/plugins/storage/service.ts +79 -15
  60. package/src/plugins/todo/index.ts +166 -0
  61. package/src/plugins/todo/service.ts +123 -0
  62. package/src/plugins/ui/index.ts +8 -1
  63. package/src/services/plugins/domain.ts +2 -0
  64. package/src/services/plugins/model-registry.ts +146 -0
  65. package/src/services/plugins/registry.ts +6 -4
  66. package/src/services/plugins/service.ts +2 -6
  67. package/dist/agents/openbot/index.js +0 -76
  68. package/dist/agents/openbot/middleware/approval.js +0 -132
  69. package/dist/agents/openbot/runtime.js +0 -289
  70. package/dist/agents/openbot/system-prompt.js +0 -32
  71. package/dist/agents/openbot/tools/delegation.js +0 -78
  72. package/dist/agents/openbot/tools/mcp.js +0 -99
  73. package/dist/agents/openbot/tools/shell.js +0 -91
  74. package/dist/agents/openbot/tools/storage.js +0 -75
  75. package/dist/agents/openbot/tools/ui.js +0 -176
  76. package/dist/agents/system.js +0 -33
  77. package/dist/bus/agent-package.js +0 -1
  78. package/dist/bus/plugin.js +0 -1
  79. package/dist/bus/services.js +0 -754
  80. package/dist/bus/types.js +0 -1
  81. package/dist/harness/agent-harness.js +0 -45
  82. package/dist/harness/agent-invoke-run.js +0 -44
  83. package/dist/harness/agent-turn.js +0 -99
  84. package/dist/harness/channel-participants.js +0 -40
  85. package/dist/harness/constants.js +0 -2
  86. package/dist/harness/context-meter.js +0 -97
  87. package/dist/harness/context.js +0 -363
  88. package/dist/harness/dispatch.js +0 -144
  89. package/dist/harness/dispatcher.js +0 -156
  90. package/dist/harness/event-normalizer.js +0 -59
  91. package/dist/harness/history.js +0 -177
  92. package/dist/harness/mcp.js +0 -61
  93. package/dist/harness/orchestration.js +0 -88
  94. package/dist/harness/orchestrator.js +0 -149
  95. package/dist/harness/participants.js +0 -22
  96. package/dist/harness/process.js +0 -29
  97. package/dist/harness/queue-processor.js +0 -187
  98. package/dist/harness/run-harness.js +0 -154
  99. package/dist/harness/run.js +0 -98
  100. package/dist/harness/runtime-factory.js +0 -73
  101. package/dist/harness/runtime.js +0 -57
  102. package/dist/harness/todo-advance.js +0 -93
  103. package/dist/harness/todo-dispatch.js +0 -51
  104. package/dist/harness/todos.js +0 -5
  105. package/dist/harness/turn.js +0 -79
  106. package/dist/harness/types.js +0 -1
  107. package/dist/plugins/ai-sdk/index.js +0 -34
  108. package/dist/plugins/ai-sdk/runtime.js +0 -395
  109. package/dist/plugins/ai-sdk/system-prompt.js +0 -18
  110. package/dist/plugins/ai-sdk.js +0 -331
  111. package/dist/plugins/approval.js +0 -163
  112. package/dist/plugins/delegation.js +0 -108
  113. package/dist/plugins/mcp/index.js +0 -108
  114. package/dist/plugins/mcp.js +0 -140
  115. package/dist/plugins/shell/index.js +0 -100
  116. package/dist/plugins/shell.js +0 -123
  117. package/dist/plugins/storage-tools/index.js +0 -76
  118. package/dist/plugins/storage.js +0 -737
  119. package/dist/plugins/thread-namer/index.js +0 -72
  120. package/dist/plugins/threads/index.js +0 -114
  121. package/dist/plugins/ui.js +0 -211
  122. package/dist/plugins/workflow/index.js +0 -65
  123. package/dist/registry/agents.js +0 -138
  124. package/dist/registry/plugins.js +0 -110
  125. package/dist/services/agent-packages.js +0 -103
  126. package/dist/services/memory.js +0 -152
  127. package/dist/services/plugins.js +0 -98
  128. package/dist/services/storage.js +0 -1028
  129. package/dist/workflow/service.js +0 -106
  130. 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
- }
@@ -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
- }
@@ -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
- }