@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,261 +0,0 @@
1
- /**
2
- * Example 04 — Multi-Model Team with Custom Tools
3
- *
4
- * Demonstrates:
5
- * - Mixing Anthropic and OpenAI models in the same team
6
- * - Defining custom tools with defineTool() and Zod schemas
7
- * - Building agents with a custom ToolRegistry so they can use custom tools
8
- * - Running a team goal that uses the custom tools
9
- *
10
- * Run:
11
- * npx tsx examples/04-multi-model-team.ts
12
- *
13
- * Prerequisites:
14
- * ANTHROPIC_API_KEY and OPENAI_API_KEY env vars must be set.
15
- * (If you only have one key, set useOpenAI = false below.)
16
- */
17
-
18
- import { z } from 'zod'
19
- import { OpenMultiAgent, defineTool } from '../src/index.js'
20
- import type { AgentConfig, OrchestratorEvent } from '../src/types.js'
21
-
22
- // ---------------------------------------------------------------------------
23
- // Custom tools — defined with defineTool() + Zod schemas
24
- // ---------------------------------------------------------------------------
25
-
26
- /**
27
- * A custom tool that fetches live exchange rates from a public API.
28
- */
29
- const exchangeRateTool = defineTool({
30
- name: 'get_exchange_rate',
31
- description:
32
- 'Get the current exchange rate between two currencies. ' +
33
- 'Returns the rate as a decimal: 1 unit of `from` = N units of `to`.',
34
- inputSchema: z.object({
35
- from: z.string().describe('ISO 4217 currency code, e.g. "USD"'),
36
- to: z.string().describe('ISO 4217 currency code, e.g. "EUR"'),
37
- }),
38
- execute: async ({ from, to }) => {
39
- try {
40
- const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=1`
41
- const resp = await fetch(url, { signal: AbortSignal.timeout(5000) })
42
-
43
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
44
-
45
- interface ExchangeRateResponse {
46
- result?: number
47
- info?: { rate?: number }
48
- }
49
- const json = (await resp.json()) as ExchangeRateResponse
50
- const rate: number | undefined = json?.result ?? json?.info?.rate
51
-
52
- if (typeof rate !== 'number') throw new Error('Unexpected API response shape')
53
-
54
- return {
55
- data: JSON.stringify({ from, to, rate, timestamp: new Date().toISOString() }),
56
- isError: false,
57
- }
58
- } catch (err) {
59
- // Graceful degradation — return a stubbed rate so the team can still proceed
60
- const stub = parseFloat((Math.random() * 0.5 + 0.8).toFixed(4))
61
- return {
62
- data: JSON.stringify({
63
- from,
64
- to,
65
- rate: stub,
66
- note: `Live fetch failed (${err instanceof Error ? err.message : String(err)}). Using stub rate.`,
67
- }),
68
- isError: false,
69
- }
70
- }
71
- },
72
- })
73
-
74
- /**
75
- * A custom tool that formats a number as a localised currency string.
76
- */
77
- const formatCurrencyTool = defineTool({
78
- name: 'format_currency',
79
- description: 'Format a number as a localised currency string.',
80
- inputSchema: z.object({
81
- amount: z.number().describe('The numeric amount to format.'),
82
- currency: z.string().describe('ISO 4217 currency code, e.g. "USD".'),
83
- locale: z
84
- .string()
85
- .optional()
86
- .describe('BCP 47 locale string, e.g. "en-US". Defaults to "en-US".'),
87
- }),
88
- execute: async ({ amount, currency, locale = 'en-US' }) => {
89
- try {
90
- const formatted = new Intl.NumberFormat(locale, {
91
- style: 'currency',
92
- currency,
93
- }).format(amount)
94
- return { data: formatted, isError: false }
95
- } catch {
96
- return { data: `${amount} ${currency}`, isError: true }
97
- }
98
- },
99
- })
100
-
101
- // ---------------------------------------------------------------------------
102
- // Helper: build an AgentConfig whose tools list includes custom tool names.
103
- //
104
- // Agents reference tools by name in their AgentConfig.tools array.
105
- // The ToolRegistry is injected via the Agent constructor. When using OpenMultiAgent
106
- // convenience methods (runTeam, runTasks, runAgent), the orchestrator builds
107
- // agents internally using buildAgent(), which registers only the five built-in
108
- // tools. For custom tools, use AgentPool + Agent directly (see the note in the
109
- // README) or provide the custom tool names in the tools array and rely on a
110
- // registry you inject yourself.
111
- //
112
- // In this example we demonstrate the custom-tool pattern by running the agents
113
- // directly through AgentPool rather than through the OpenMultiAgent high-level API.
114
- // ---------------------------------------------------------------------------
115
-
116
- import { Agent, AgentPool, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../src/index.js'
117
-
118
- /**
119
- * Build an Agent with both built-in and custom tools registered.
120
- */
121
- function buildCustomAgent(
122
- config: AgentConfig,
123
- extraTools: ReturnType<typeof defineTool>[],
124
- ): Agent {
125
- const registry = new ToolRegistry()
126
- registerBuiltInTools(registry)
127
- for (const tool of extraTools) {
128
- registry.register(tool)
129
- }
130
- const executor = new ToolExecutor(registry)
131
- return new Agent(config, registry, executor)
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // Agent definitions — mixed providers
136
- // ---------------------------------------------------------------------------
137
-
138
- const useOpenAI = Boolean(process.env.OPENAI_API_KEY)
139
-
140
- const researcherConfig: AgentConfig = {
141
- name: 'researcher',
142
- model: 'claude-sonnet-4-6',
143
- provider: 'anthropic',
144
- systemPrompt: `You are a financial data researcher.
145
- Use the get_exchange_rate tool to fetch current rates between the currency pairs you are given.
146
- Return the raw rates as a JSON object keyed by pair, e.g. { "USD/EUR": 0.91, "USD/GBP": 0.79 }.`,
147
- tools: ['get_exchange_rate'],
148
- maxTurns: 6,
149
- temperature: 0,
150
- }
151
-
152
- const analystConfig: AgentConfig = {
153
- name: 'analyst',
154
- model: useOpenAI ? 'gpt-5.4' : 'claude-sonnet-4-6',
155
- provider: useOpenAI ? 'openai' : 'anthropic',
156
- systemPrompt: `You are a foreign exchange analyst.
157
- You receive exchange rate data and produce a short briefing.
158
- Use format_currency to show example conversions.
159
- Keep the briefing under 200 words.`,
160
- tools: ['format_currency'],
161
- maxTurns: 4,
162
- temperature: 0.3,
163
- }
164
-
165
- // ---------------------------------------------------------------------------
166
- // Build agents with custom tools
167
- // ---------------------------------------------------------------------------
168
-
169
- const researcher = buildCustomAgent(researcherConfig, [exchangeRateTool])
170
- const analyst = buildCustomAgent(analystConfig, [formatCurrencyTool])
171
-
172
- // ---------------------------------------------------------------------------
173
- // Run with AgentPool for concurrency control
174
- // ---------------------------------------------------------------------------
175
-
176
- console.log('Multi-model team with custom tools')
177
- console.log(`Providers: researcher=anthropic, analyst=${useOpenAI ? 'openai (gpt-5.4)' : 'anthropic (fallback)'}`)
178
- console.log('Custom tools:', [exchangeRateTool.name, formatCurrencyTool.name].join(', '))
179
- console.log()
180
-
181
- const pool = new AgentPool(1) // sequential for readability
182
- pool.add(researcher)
183
- pool.add(analyst)
184
-
185
- // Step 1: researcher fetches the rates
186
- console.log('[1/2] Researcher fetching FX rates...')
187
- const researchResult = await pool.run(
188
- 'researcher',
189
- `Fetch exchange rates for these pairs using the get_exchange_rate tool:
190
- - USD to EUR
191
- - USD to GBP
192
- - USD to JPY
193
- - EUR to GBP
194
-
195
- Return the results as a JSON object: { "USD/EUR": <rate>, "USD/GBP": <rate>, ... }`,
196
- )
197
-
198
- if (!researchResult.success) {
199
- console.error('Researcher failed:', researchResult.output)
200
- process.exit(1)
201
- }
202
-
203
- console.log('Researcher done. Tool calls made:', researchResult.toolCalls.map(c => c.toolName).join(', '))
204
-
205
- // Step 2: analyst writes the briefing, receiving the researcher output as context
206
- console.log('\n[2/2] Analyst writing FX briefing...')
207
- const analystResult = await pool.run(
208
- 'analyst',
209
- `Here are the current FX rates gathered by the research team:
210
-
211
- ${researchResult.output}
212
-
213
- Using format_currency, show what $1,000 USD and €1,000 EUR convert to in each of the other currencies.
214
- Then write a short FX market briefing (under 200 words) covering:
215
- - Each rate with a brief observation
216
- - The strongest and weakest currency in the set
217
- - One-sentence market comment`,
218
- )
219
-
220
- if (!analystResult.success) {
221
- console.error('Analyst failed:', analystResult.output)
222
- process.exit(1)
223
- }
224
-
225
- console.log('Analyst done. Tool calls made:', analystResult.toolCalls.map(c => c.toolName).join(', '))
226
-
227
- // ---------------------------------------------------------------------------
228
- // Results
229
- // ---------------------------------------------------------------------------
230
-
231
- console.log('\n' + '='.repeat(60))
232
-
233
- console.log('\nResearcher output:')
234
- console.log(researchResult.output.slice(0, 400))
235
-
236
- console.log('\nAnalyst briefing:')
237
- console.log('─'.repeat(60))
238
- console.log(analystResult.output)
239
- console.log('─'.repeat(60))
240
-
241
- const totalInput = researchResult.tokenUsage.input_tokens + analystResult.tokenUsage.input_tokens
242
- const totalOutput = researchResult.tokenUsage.output_tokens + analystResult.tokenUsage.output_tokens
243
- console.log(`\nTotal tokens — input: ${totalInput}, output: ${totalOutput}`)
244
-
245
- // ---------------------------------------------------------------------------
246
- // Bonus: show how defineTool() works in isolation (no LLM needed)
247
- // ---------------------------------------------------------------------------
248
-
249
- console.log('\n--- Bonus: testing custom tools in isolation ---\n')
250
-
251
- const fmtResult = await formatCurrencyTool.execute(
252
- { amount: 1234.56, currency: 'EUR', locale: 'de-DE' },
253
- { agent: { name: 'test', role: 'test', model: 'test' } },
254
- )
255
- console.log(`format_currency(1234.56, EUR, de-DE) = ${fmtResult.data}`)
256
-
257
- const rateResult = await exchangeRateTool.execute(
258
- { from: 'USD', to: 'EUR' },
259
- { agent: { name: 'test', role: 'test', model: 'test' } },
260
- )
261
- console.log(`get_exchange_rate(USD→EUR) = ${rateResult.data}`)
@@ -1,49 +0,0 @@
1
- /**
2
- * Quick smoke test for the Copilot adapter.
3
- *
4
- * Run:
5
- * npx tsx examples/05-copilot-test.ts
6
- *
7
- * If GITHUB_COPILOT_TOKEN is not set, the adapter will start an interactive
8
- * OAuth2 device flow — you'll be prompted to sign in via your browser.
9
- */
10
-
11
- import { OpenMultiAgent } from '../src/index.js'
12
- import type { OrchestratorEvent } from '../src/types.js'
13
-
14
- const orchestrator = new OpenMultiAgent({
15
- defaultModel: 'gpt-4o',
16
- defaultProvider: 'copilot',
17
- onProgress: (event: OrchestratorEvent) => {
18
- if (event.type === 'agent_start') {
19
- console.log(`[start] agent=${event.agent}`)
20
- } else if (event.type === 'agent_complete') {
21
- console.log(`[complete] agent=${event.agent}`)
22
- }
23
- },
24
- })
25
-
26
- console.log('Testing Copilot adapter with gpt-4o...\n')
27
-
28
- const result = await orchestrator.runAgent(
29
- {
30
- name: 'assistant',
31
- model: 'gpt-4o',
32
- provider: 'copilot',
33
- systemPrompt: 'You are a helpful assistant. Keep answers brief.',
34
- maxTurns: 1,
35
- maxTokens: 256,
36
- },
37
- 'What is 2 + 2? Reply in one sentence.',
38
- )
39
-
40
- if (result.success) {
41
- console.log('\nAgent output:')
42
- console.log('─'.repeat(60))
43
- console.log(result.output)
44
- console.log('─'.repeat(60))
45
- console.log(`\nTokens: input=${result.tokenUsage.input_tokens}, output=${result.tokenUsage.output_tokens}`)
46
- } else {
47
- console.error('Agent failed:', result.output)
48
- process.exit(1)
49
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * Example 06 — Local Model + Cloud Model Team (Ollama + Claude)
3
- *
4
- * Demonstrates mixing a local model served by Ollama with a cloud model
5
- * (Claude) in the same task pipeline. The key technique is using
6
- * `provider: 'openai'` with a custom `baseURL` pointing at Ollama's
7
- * OpenAI-compatible endpoint.
8
- *
9
- * This pattern works with ANY OpenAI-compatible local server:
10
- * - Ollama → http://localhost:11434/v1
11
- * - vLLM → http://localhost:8000/v1
12
- * - LM Studio → http://localhost:1234/v1
13
- * - llama.cpp → http://localhost:8080/v1
14
- * Just change the baseURL and model name below.
15
- *
16
- * Run:
17
- * npx tsx examples/06-local-model.ts
18
- *
19
- * Prerequisites:
20
- * 1. Ollama installed and running: https://ollama.com
21
- * 2. Pull the model: ollama pull llama3.1
22
- * 3. ANTHROPIC_API_KEY env var must be set.
23
- */
24
-
25
- import { OpenMultiAgent } from '../src/index.js'
26
- import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
27
-
28
- // ---------------------------------------------------------------------------
29
- // Agents
30
- // ---------------------------------------------------------------------------
31
-
32
- /**
33
- * Coder — uses Claude (Anthropic) for high-quality code generation.
34
- */
35
- const coder: AgentConfig = {
36
- name: 'coder',
37
- model: 'claude-sonnet-4-6',
38
- provider: 'anthropic',
39
- systemPrompt: `You are a senior TypeScript developer. Write clean, well-typed,
40
- production-quality code. Use the tools to write files to /tmp/local-model-demo/.
41
- Always include brief JSDoc comments on exported functions.`,
42
- tools: ['bash', 'file_write'],
43
- maxTurns: 6,
44
- }
45
-
46
- /**
47
- * Reviewer — uses a local Ollama model via the OpenAI-compatible API.
48
- * The apiKey is required by the OpenAI SDK but Ollama ignores it,
49
- * so we pass the placeholder string 'ollama'.
50
- */
51
- const reviewer: AgentConfig = {
52
- name: 'reviewer',
53
- model: 'llama3.1',
54
- provider: 'openai', // 'openai' here means "OpenAI-compatible protocol", not the OpenAI cloud
55
- baseURL: 'http://localhost:11434/v1',
56
- apiKey: 'ollama',
57
- systemPrompt: `You are a code reviewer. You read source files and produce a structured review.
58
- Your review MUST include these sections:
59
- - Summary (2-3 sentences)
60
- - Strengths (bullet list)
61
- - Issues (bullet list — or "None found" if the code is clean)
62
- - Verdict: SHIP or NEEDS WORK
63
-
64
- Be specific and constructive. Reference line numbers or function names when possible.`,
65
- tools: ['file_read'],
66
- maxTurns: 4,
67
- timeoutMs: 120_000, // 2 min — local models can be slow
68
- }
69
-
70
- // ---------------------------------------------------------------------------
71
- // Progress handler
72
- // ---------------------------------------------------------------------------
73
-
74
- const taskTimes = new Map<string, number>()
75
-
76
- function handleProgress(event: OrchestratorEvent): void {
77
- const ts = new Date().toISOString().slice(11, 23)
78
-
79
- switch (event.type) {
80
- case 'task_start': {
81
- taskTimes.set(event.task ?? '', Date.now())
82
- const task = event.data as Task | undefined
83
- console.log(`[${ts}] TASK READY "${task?.title ?? event.task}" → ${task?.assignee ?? '?'}`)
84
- break
85
- }
86
- case 'task_complete': {
87
- const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
88
- console.log(`[${ts}] TASK DONE task=${event.task} in ${elapsed}ms`)
89
- break
90
- }
91
- case 'agent_start':
92
- console.log(`[${ts}] AGENT START ${event.agent}`)
93
- break
94
- case 'agent_complete':
95
- console.log(`[${ts}] AGENT DONE ${event.agent}`)
96
- break
97
- case 'error':
98
- console.error(`[${ts}] ERROR ${event.agent ?? ''} task=${event.task ?? '?'}`)
99
- break
100
- }
101
- }
102
-
103
- // ---------------------------------------------------------------------------
104
- // Orchestrator + Team
105
- // ---------------------------------------------------------------------------
106
-
107
- const orchestrator = new OpenMultiAgent({
108
- defaultModel: 'claude-sonnet-4-6',
109
- maxConcurrency: 2,
110
- onProgress: handleProgress,
111
- })
112
-
113
- const team = orchestrator.createTeam('local-cloud-team', {
114
- name: 'local-cloud-team',
115
- agents: [coder, reviewer],
116
- sharedMemory: true,
117
- })
118
-
119
- // ---------------------------------------------------------------------------
120
- // Task pipeline: code → review
121
- // ---------------------------------------------------------------------------
122
-
123
- const OUTPUT_DIR = '/tmp/local-model-demo'
124
-
125
- const tasks: Array<{
126
- title: string
127
- description: string
128
- assignee?: string
129
- dependsOn?: string[]
130
- }> = [
131
- {
132
- title: 'Write: retry utility',
133
- description: `Write a small but complete TypeScript utility to ${OUTPUT_DIR}/retry.ts.
134
-
135
- The module should export:
136
- 1. A \`RetryOptions\` interface with: maxRetries (number), delayMs (number),
137
- backoffFactor (optional number, default 2), shouldRetry (optional predicate
138
- taking the error and returning boolean).
139
- 2. An async \`retry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>\`
140
- function that retries \`fn\` with exponential backoff.
141
- 3. A convenience \`withRetry\` wrapper that returns a new function with retry
142
- behaviour baked in.
143
-
144
- Include JSDoc comments. No external dependencies — use only Node built-ins.
145
- After writing the file, also create a small test script at ${OUTPUT_DIR}/retry-test.ts
146
- that exercises the happy path and a failure case, then run it with \`npx tsx\`.`,
147
- assignee: 'coder',
148
- },
149
- {
150
- title: 'Review: retry utility',
151
- description: `Read the files at ${OUTPUT_DIR}/retry.ts and ${OUTPUT_DIR}/retry-test.ts.
152
-
153
- Produce a structured code review covering:
154
- - Summary (2-3 sentences describing the module)
155
- - Strengths (bullet list)
156
- - Issues (bullet list — be specific about what and why)
157
- - Verdict: SHIP or NEEDS WORK`,
158
- assignee: 'reviewer',
159
- dependsOn: ['Write: retry utility'],
160
- },
161
- ]
162
-
163
- // ---------------------------------------------------------------------------
164
- // Run
165
- // ---------------------------------------------------------------------------
166
-
167
- console.log('Local + Cloud model team')
168
- console.log(` coder → Claude (${coder.model}) via Anthropic API`)
169
- console.log(` reviewer → Ollama (${reviewer.model}) at ${reviewer.baseURL}`)
170
- console.log()
171
- console.log('Pipeline: coder writes code → local model reviews it')
172
- console.log('='.repeat(60))
173
-
174
- const result = await orchestrator.runTasks(team, tasks)
175
-
176
- // ---------------------------------------------------------------------------
177
- // Summary
178
- // ---------------------------------------------------------------------------
179
-
180
- console.log('\n' + '='.repeat(60))
181
- console.log('Pipeline complete.\n')
182
- console.log(`Overall success: ${result.success}`)
183
- console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
184
-
185
- console.log('\nPer-agent summary:')
186
- for (const [name, r] of result.agentResults) {
187
- const icon = r.success ? 'OK ' : 'FAIL'
188
- const provider = name === 'coder' ? 'anthropic' : 'ollama (local)'
189
- const tools = r.toolCalls.map(c => c.toolName).join(', ')
190
- console.log(` [${icon}] ${name.padEnd(10)} (${provider.padEnd(16)}) tools: ${tools || '(none)'}`)
191
- }
192
-
193
- // Print the reviewer's output
194
- const review = result.agentResults.get('reviewer')
195
- if (review?.success) {
196
- console.log('\nCode review (from local model):')
197
- console.log('─'.repeat(60))
198
- console.log(review.output)
199
- console.log('─'.repeat(60))
200
- }