@jackchen_me/open-multi-agent 1.0.0 → 1.0.1

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 (80) hide show
  1. package/package.json +8 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  4. package/.github/pull_request_template.md +0 -14
  5. package/.github/workflows/ci.yml +0 -23
  6. package/CLAUDE.md +0 -80
  7. package/CODE_OF_CONDUCT.md +0 -48
  8. package/CONTRIBUTING.md +0 -72
  9. package/DECISIONS.md +0 -43
  10. package/README_zh.md +0 -277
  11. package/SECURITY.md +0 -17
  12. package/examples/01-single-agent.ts +0 -131
  13. package/examples/02-team-collaboration.ts +0 -167
  14. package/examples/03-task-pipeline.ts +0 -201
  15. package/examples/04-multi-model-team.ts +0 -261
  16. package/examples/05-copilot-test.ts +0 -49
  17. package/examples/06-local-model.ts +0 -200
  18. package/examples/07-fan-out-aggregate.ts +0 -209
  19. package/examples/08-gemma4-local.ts +0 -192
  20. package/examples/09-structured-output.ts +0 -73
  21. package/examples/10-task-retry.ts +0 -132
  22. package/examples/11-trace-observability.ts +0 -133
  23. package/examples/12-grok.ts +0 -154
  24. package/examples/13-gemini.ts +0 -48
  25. package/src/agent/agent.ts +0 -622
  26. package/src/agent/loop-detector.ts +0 -137
  27. package/src/agent/pool.ts +0 -285
  28. package/src/agent/runner.ts +0 -542
  29. package/src/agent/structured-output.ts +0 -126
  30. package/src/index.ts +0 -182
  31. package/src/llm/adapter.ts +0 -98
  32. package/src/llm/anthropic.ts +0 -389
  33. package/src/llm/copilot.ts +0 -552
  34. package/src/llm/gemini.ts +0 -378
  35. package/src/llm/grok.ts +0 -29
  36. package/src/llm/openai-common.ts +0 -294
  37. package/src/llm/openai.ts +0 -292
  38. package/src/memory/shared.ts +0 -181
  39. package/src/memory/store.ts +0 -124
  40. package/src/orchestrator/orchestrator.ts +0 -1071
  41. package/src/orchestrator/scheduler.ts +0 -352
  42. package/src/task/queue.ts +0 -464
  43. package/src/task/task.ts +0 -239
  44. package/src/team/messaging.ts +0 -232
  45. package/src/team/team.ts +0 -334
  46. package/src/tool/built-in/bash.ts +0 -187
  47. package/src/tool/built-in/file-edit.ts +0 -154
  48. package/src/tool/built-in/file-read.ts +0 -105
  49. package/src/tool/built-in/file-write.ts +0 -81
  50. package/src/tool/built-in/grep.ts +0 -362
  51. package/src/tool/built-in/index.ts +0 -50
  52. package/src/tool/executor.ts +0 -178
  53. package/src/tool/framework.ts +0 -557
  54. package/src/tool/text-tool-extractor.ts +0 -219
  55. package/src/types.ts +0 -542
  56. package/src/utils/semaphore.ts +0 -89
  57. package/src/utils/trace.ts +0 -34
  58. package/tests/agent-hooks.test.ts +0 -473
  59. package/tests/agent-pool.test.ts +0 -212
  60. package/tests/approval.test.ts +0 -464
  61. package/tests/built-in-tools.test.ts +0 -393
  62. package/tests/gemini-adapter.test.ts +0 -97
  63. package/tests/grok-adapter.test.ts +0 -74
  64. package/tests/llm-adapters.test.ts +0 -357
  65. package/tests/loop-detection.test.ts +0 -456
  66. package/tests/openai-fallback.test.ts +0 -159
  67. package/tests/orchestrator.test.ts +0 -281
  68. package/tests/scheduler.test.ts +0 -221
  69. package/tests/semaphore.test.ts +0 -57
  70. package/tests/shared-memory.test.ts +0 -122
  71. package/tests/structured-output.test.ts +0 -331
  72. package/tests/task-queue.test.ts +0 -244
  73. package/tests/task-retry.test.ts +0 -368
  74. package/tests/task-utils.test.ts +0 -155
  75. package/tests/team-messaging.test.ts +0 -329
  76. package/tests/text-tool-extractor.test.ts +0 -170
  77. package/tests/tool-executor.test.ts +0 -193
  78. package/tests/trace.test.ts +0 -453
  79. package/tsconfig.json +0 -25
  80. package/vitest.config.ts +0 -9
@@ -1,232 +0,0 @@
1
- /**
2
- * @fileoverview Inter-agent message bus.
3
- *
4
- * Provides a lightweight pub/sub system so agents can exchange typed messages
5
- * without direct references to each other. All messages are retained in memory
6
- * for replay and audit; read-state is tracked per recipient.
7
- */
8
-
9
- import { randomUUID } from 'node:crypto'
10
-
11
- // ---------------------------------------------------------------------------
12
- // Message type
13
- // ---------------------------------------------------------------------------
14
-
15
- /** A single message exchanged between agents (or broadcast to all). */
16
- export interface Message {
17
- /** Stable UUID for this message. */
18
- readonly id: string
19
- /** Name of the sending agent. */
20
- readonly from: string
21
- /**
22
- * Recipient agent name, or `'*'` when the message is a broadcast intended
23
- * for every agent except the sender.
24
- */
25
- readonly to: string
26
- readonly content: string
27
- readonly timestamp: Date
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // Internal helpers
32
- // ---------------------------------------------------------------------------
33
-
34
- /** Returns true when `message` is addressed to `agentName`. */
35
- function isAddressedTo(message: Message, agentName: string): boolean {
36
- if (message.to === '*') {
37
- // Broadcasts are delivered to everyone except the sender.
38
- return message.from !== agentName
39
- }
40
- return message.to === agentName
41
- }
42
-
43
- // ---------------------------------------------------------------------------
44
- // MessageBus
45
- // ---------------------------------------------------------------------------
46
-
47
- /**
48
- * In-memory message bus for inter-agent communication.
49
- *
50
- * Agents can send point-to-point messages or broadcasts. Subscribers are
51
- * notified synchronously (within the same microtask) when a new message
52
- * arrives addressed to them.
53
- *
54
- * @example
55
- * ```ts
56
- * const bus = new MessageBus()
57
- *
58
- * const unsubscribe = bus.subscribe('worker', (msg) => {
59
- * console.log(`worker received: ${msg.content}`)
60
- * })
61
- *
62
- * bus.send('coordinator', 'worker', 'Start task A')
63
- * bus.broadcast('coordinator', 'All agents: stand by')
64
- *
65
- * unsubscribe()
66
- * ```
67
- */
68
- export class MessageBus {
69
- /** All messages ever sent, in insertion order. */
70
- private readonly messages: Message[] = []
71
-
72
- /**
73
- * Per-agent set of message IDs that have already been marked as read.
74
- * A message absent from this set is considered unread.
75
- */
76
- private readonly readState = new Map<string, Set<string>>()
77
-
78
- /**
79
- * Active subscribers keyed by agent name. Each subscriber is a callback
80
- * paired with a unique subscription ID used for unsubscription.
81
- */
82
- private readonly subscribers = new Map<
83
- string,
84
- Map<symbol, (message: Message) => void>
85
- >()
86
-
87
- // ---------------------------------------------------------------------------
88
- // Write operations
89
- // ---------------------------------------------------------------------------
90
-
91
- /**
92
- * Send a message from `from` to `to`.
93
- *
94
- * @returns The persisted {@link Message} including its generated ID and timestamp.
95
- */
96
- send(from: string, to: string, content: string): Message {
97
- const message: Message = {
98
- id: randomUUID(),
99
- from,
100
- to,
101
- content,
102
- timestamp: new Date(),
103
- }
104
- this.persist(message)
105
- return message
106
- }
107
-
108
- /**
109
- * Broadcast a message from `from` to all other agents (`to === '*'`).
110
- *
111
- * @returns The persisted broadcast {@link Message}.
112
- */
113
- broadcast(from: string, content: string): Message {
114
- return this.send(from, '*', content)
115
- }
116
-
117
- // ---------------------------------------------------------------------------
118
- // Read operations
119
- // ---------------------------------------------------------------------------
120
-
121
- /**
122
- * Returns messages that have not yet been marked as read by `agentName`,
123
- * including both direct messages and broadcasts addressed to them.
124
- */
125
- getUnread(agentName: string): Message[] {
126
- const read = this.readState.get(agentName) ?? new Set<string>()
127
- return this.messages.filter(
128
- (m) => isAddressedTo(m, agentName) && !read.has(m.id),
129
- )
130
- }
131
-
132
- /**
133
- * Returns every message (read or unread) addressed to `agentName`,
134
- * preserving insertion order.
135
- */
136
- getAll(agentName: string): Message[] {
137
- return this.messages.filter((m) => isAddressedTo(m, agentName))
138
- }
139
-
140
- /**
141
- * Mark a set of messages as read for `agentName`.
142
- * Passing IDs that were already marked, or do not exist, is a no-op.
143
- */
144
- markRead(agentName: string, messageIds: string[]): void {
145
- if (messageIds.length === 0) return
146
- let read = this.readState.get(agentName)
147
- if (!read) {
148
- read = new Set<string>()
149
- this.readState.set(agentName, read)
150
- }
151
- for (const id of messageIds) {
152
- read.add(id)
153
- }
154
- }
155
-
156
- /**
157
- * Returns all messages exchanged between `agent1` and `agent2` (in either
158
- * direction), sorted chronologically.
159
- */
160
- getConversation(agent1: string, agent2: string): Message[] {
161
- return this.messages.filter(
162
- (m) =>
163
- (m.from === agent1 && m.to === agent2) ||
164
- (m.from === agent2 && m.to === agent1),
165
- )
166
- }
167
-
168
- // ---------------------------------------------------------------------------
169
- // Subscriptions
170
- // ---------------------------------------------------------------------------
171
-
172
- /**
173
- * Subscribe to new messages addressed to `agentName`.
174
- *
175
- * The `callback` is invoked synchronously after each matching message is
176
- * persisted. Returns an unsubscribe function; calling it is idempotent.
177
- *
178
- * @example
179
- * ```ts
180
- * const off = bus.subscribe('agent-b', (msg) => handleMessage(msg))
181
- * // Later…
182
- * off()
183
- * ```
184
- */
185
- subscribe(
186
- agentName: string,
187
- callback: (message: Message) => void,
188
- ): () => void {
189
- let agentSubs = this.subscribers.get(agentName)
190
- if (!agentSubs) {
191
- agentSubs = new Map()
192
- this.subscribers.set(agentName, agentSubs)
193
- }
194
- const id = Symbol()
195
- agentSubs.set(id, callback)
196
- return () => {
197
- agentSubs!.delete(id)
198
- }
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Private helpers
203
- // ---------------------------------------------------------------------------
204
-
205
- private persist(message: Message): void {
206
- this.messages.push(message)
207
- this.notifySubscribers(message)
208
- }
209
-
210
- private notifySubscribers(message: Message): void {
211
- // Notify direct subscribers of `message.to` (unless broadcast).
212
- if (message.to !== '*') {
213
- this.fireCallbacks(message.to, message)
214
- return
215
- }
216
-
217
- // Broadcast: notify all subscribers except the sender.
218
- for (const [agentName, subs] of this.subscribers) {
219
- if (agentName !== message.from && subs.size > 0) {
220
- this.fireCallbacks(agentName, message)
221
- }
222
- }
223
- }
224
-
225
- private fireCallbacks(agentName: string, message: Message): void {
226
- const subs = this.subscribers.get(agentName)
227
- if (!subs) return
228
- for (const callback of subs.values()) {
229
- callback(message)
230
- }
231
- }
232
- }
package/src/team/team.ts DELETED
@@ -1,334 +0,0 @@
1
- /**
2
- * @fileoverview Team — the central coordination object for a named group of agents.
3
- *
4
- * A {@link Team} owns the agent roster, the inter-agent {@link MessageBus},
5
- * the {@link TaskQueue}, and (optionally) a {@link SharedMemory} instance.
6
- * It also exposes a typed event bus so orchestrators can react to lifecycle
7
- * events without polling.
8
- */
9
-
10
- import type {
11
- AgentConfig,
12
- MemoryStore,
13
- OrchestratorEvent,
14
- Task,
15
- TaskStatus,
16
- TeamConfig,
17
- } from '../types.js'
18
- import { SharedMemory } from '../memory/shared.js'
19
- import { MessageBus } from './messaging.js'
20
- import type { Message } from './messaging.js'
21
- import { TaskQueue } from '../task/queue.js'
22
- import { createTask } from '../task/task.js'
23
-
24
- export type { Message }
25
-
26
- // ---------------------------------------------------------------------------
27
- // Internal event bus
28
- // ---------------------------------------------------------------------------
29
-
30
- type EventHandler = (data: unknown) => void
31
-
32
- /** Minimal synchronous event emitter. */
33
- class EventBus {
34
- private readonly listeners = new Map<string, Map<symbol, EventHandler>>()
35
-
36
- on(event: string, handler: EventHandler): () => void {
37
- let map = this.listeners.get(event)
38
- if (!map) {
39
- map = new Map()
40
- this.listeners.set(event, map)
41
- }
42
- const id = Symbol()
43
- map.set(id, handler)
44
- return () => {
45
- map!.delete(id)
46
- }
47
- }
48
-
49
- emit(event: string, data: unknown): void {
50
- const map = this.listeners.get(event)
51
- if (!map) return
52
- for (const handler of map.values()) {
53
- handler(data)
54
- }
55
- }
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Team
60
- // ---------------------------------------------------------------------------
61
-
62
- /**
63
- * Coordinates a named group of agents with shared messaging, task queuing,
64
- * and optional shared memory.
65
- *
66
- * @example
67
- * ```ts
68
- * const team = new Team({
69
- * name: 'research-team',
70
- * agents: [researcherConfig, writerConfig],
71
- * sharedMemory: true,
72
- * maxConcurrency: 2,
73
- * })
74
- *
75
- * team.on('task:complete', (data) => {
76
- * const event = data as OrchestratorEvent
77
- * console.log(`Task done: ${event.task}`)
78
- * })
79
- *
80
- * const task = team.addTask({
81
- * title: 'Research topic',
82
- * description: 'Gather background on quantum computing',
83
- * status: 'pending',
84
- * assignee: 'researcher',
85
- * })
86
- * ```
87
- */
88
- export class Team {
89
- readonly name: string
90
- readonly config: TeamConfig
91
-
92
- private readonly agentMap: ReadonlyMap<string, AgentConfig>
93
- private readonly bus: MessageBus
94
- private readonly queue: TaskQueue
95
- private readonly memory: SharedMemory | undefined
96
- private readonly events: EventBus
97
-
98
- constructor(config: TeamConfig) {
99
- this.config = config
100
- this.name = config.name
101
-
102
- // Index agents by name for O(1) lookup.
103
- this.agentMap = new Map(config.agents.map((a) => [a.name, a]))
104
- this.bus = new MessageBus()
105
- this.queue = new TaskQueue()
106
- this.memory = config.sharedMemory ? new SharedMemory() : undefined
107
- this.events = new EventBus()
108
-
109
- // Bridge queue events onto the team's event bus.
110
- this.queue.on('task:ready', (task) => {
111
- const event: OrchestratorEvent = {
112
- type: 'task_start',
113
- task: task.id,
114
- data: task,
115
- }
116
- this.events.emit('task:ready', event)
117
- })
118
-
119
- this.queue.on('task:complete', (task) => {
120
- const event: OrchestratorEvent = {
121
- type: 'task_complete',
122
- task: task.id,
123
- data: task,
124
- }
125
- this.events.emit('task:complete', event)
126
- })
127
-
128
- this.queue.on('task:failed', (task) => {
129
- const event: OrchestratorEvent = {
130
- type: 'error',
131
- task: task.id,
132
- data: task,
133
- }
134
- this.events.emit('task:failed', event)
135
- })
136
-
137
- this.queue.on('all:complete', () => {
138
- this.events.emit('all:complete', undefined)
139
- })
140
- }
141
-
142
- // ---------------------------------------------------------------------------
143
- // Agent roster
144
- // ---------------------------------------------------------------------------
145
-
146
- /** Returns a shallow copy of the agent configs in registration order. */
147
- getAgents(): AgentConfig[] {
148
- return Array.from(this.agentMap.values())
149
- }
150
-
151
- /**
152
- * Looks up an agent by name.
153
- *
154
- * @returns The {@link AgentConfig} or `undefined` when the name is not known.
155
- */
156
- getAgent(name: string): AgentConfig | undefined {
157
- return this.agentMap.get(name)
158
- }
159
-
160
- // ---------------------------------------------------------------------------
161
- // Messaging
162
- // ---------------------------------------------------------------------------
163
-
164
- /**
165
- * Sends a point-to-point message from `from` to `to`.
166
- *
167
- * The message is persisted on the bus and any active subscribers for `to`
168
- * are notified synchronously.
169
- */
170
- sendMessage(from: string, to: string, content: string): void {
171
- const message = this.bus.send(from, to, content)
172
- const event: OrchestratorEvent = {
173
- type: 'message',
174
- agent: from,
175
- data: message,
176
- }
177
- this.events.emit('message', event)
178
- }
179
-
180
- /**
181
- * Returns all messages (read or unread) addressed to `agentName`, in
182
- * chronological order.
183
- */
184
- getMessages(agentName: string): Message[] {
185
- return this.bus.getAll(agentName)
186
- }
187
-
188
- /**
189
- * Broadcasts `content` from `from` to every other agent.
190
- *
191
- * The `to` field of the resulting message is `'*'`.
192
- */
193
- broadcast(from: string, content: string): void {
194
- const message = this.bus.broadcast(from, content)
195
- const event: OrchestratorEvent = {
196
- type: 'message',
197
- agent: from,
198
- data: message,
199
- }
200
- this.events.emit('broadcast', event)
201
- }
202
-
203
- // ---------------------------------------------------------------------------
204
- // Task management
205
- // ---------------------------------------------------------------------------
206
-
207
- /**
208
- * Creates a new task, adds it to the queue, and returns the persisted
209
- * {@link Task} (with generated `id`, `createdAt`, and `updatedAt`).
210
- *
211
- * @param task - Everything except the generated fields.
212
- */
213
- addTask(
214
- task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>,
215
- ): Task {
216
- const created = createTask({
217
- title: task.title,
218
- description: task.description,
219
- assignee: task.assignee,
220
- dependsOn: task.dependsOn ? [...task.dependsOn] : undefined,
221
- })
222
-
223
- // Preserve any non-default status (e.g. 'blocked') supplied by the caller.
224
- const finalTask: Task =
225
- task.status !== 'pending'
226
- ? { ...created, status: task.status as TaskStatus, result: task.result }
227
- : created
228
-
229
- this.queue.add(finalTask)
230
- return finalTask
231
- }
232
-
233
- /** Returns a snapshot of all tasks in the queue (any status). */
234
- getTasks(): Task[] {
235
- return this.queue.list()
236
- }
237
-
238
- /** Returns all tasks whose `assignee` is `agentName`. */
239
- getTasksByAssignee(agentName: string): Task[] {
240
- return this.queue.list().filter((t) => t.assignee === agentName)
241
- }
242
-
243
- /**
244
- * Applies a partial update to the task identified by `taskId`.
245
- *
246
- * @throws {Error} when the task is not found.
247
- */
248
- updateTask(taskId: string, update: Partial<Task>): Task {
249
- // Extract only mutable fields accepted by the queue.
250
- const { status, result, assignee } = update
251
- return this.queue.update(taskId, {
252
- ...(status !== undefined && { status }),
253
- ...(result !== undefined && { result }),
254
- ...(assignee !== undefined && { assignee }),
255
- })
256
- }
257
-
258
- /**
259
- * Returns the next `'pending'` task for `agentName`, respecting dependencies.
260
- *
261
- * Tries to find a task explicitly assigned to the agent first; falls back to
262
- * the first unassigned pending task.
263
- *
264
- * @returns `undefined` when no ready task exists for this agent.
265
- */
266
- getNextTask(agentName: string): Task | undefined {
267
- // Prefer a task explicitly assigned to this agent.
268
- const assigned = this.queue.next(agentName)
269
- if (assigned) return assigned
270
-
271
- // Fall back to any unassigned pending task.
272
- return this.queue.nextAvailable()
273
- }
274
-
275
- // ---------------------------------------------------------------------------
276
- // Memory
277
- // ---------------------------------------------------------------------------
278
-
279
- /**
280
- * Returns the shared {@link MemoryStore} for this team, or `undefined` if
281
- * `sharedMemory` was not enabled in {@link TeamConfig}.
282
- *
283
- * Note: the returned value satisfies the {@link MemoryStore} interface.
284
- * Callers that need the full {@link SharedMemory} API can use the
285
- * `as SharedMemory` cast, but depending on the concrete type is discouraged.
286
- */
287
- getSharedMemory(): MemoryStore | undefined {
288
- return this.memory?.getStore()
289
- }
290
-
291
- /**
292
- * Returns the raw {@link SharedMemory} instance (team-internal accessor).
293
- * Use this when you need the namespacing / `getSummary` features.
294
- *
295
- * @internal
296
- */
297
- getSharedMemoryInstance(): SharedMemory | undefined {
298
- return this.memory
299
- }
300
-
301
- // ---------------------------------------------------------------------------
302
- // Events
303
- // ---------------------------------------------------------------------------
304
-
305
- /**
306
- * Subscribes to a team event.
307
- *
308
- * Built-in events:
309
- * - `'task:ready'` — emitted when a task becomes runnable.
310
- * - `'task:complete'` — emitted when a task completes successfully.
311
- * - `'task:failed'` — emitted when a task fails.
312
- * - `'all:complete'` — emitted when every task in the queue has terminated.
313
- * - `'message'` — emitted on point-to-point messages.
314
- * - `'broadcast'` — emitted on broadcast messages.
315
- *
316
- * `data` is typed as `unknown`; cast to {@link OrchestratorEvent} for
317
- * structured access.
318
- *
319
- * @returns An unsubscribe function.
320
- */
321
- on(event: string, handler: (data: unknown) => void): () => void {
322
- return this.events.on(event, handler)
323
- }
324
-
325
- /**
326
- * Emits a custom event on the team's event bus.
327
- *
328
- * Orchestrators can use this to signal domain-specific lifecycle milestones
329
- * (e.g. `'phase:research:complete'`) without modifying the Team class.
330
- */
331
- emit(event: string, data: unknown): void {
332
- this.events.emit(event, data)
333
- }
334
- }