@jackchen_me/open-multi-agent 0.2.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 (117) hide show
  1. package/README.md +87 -20
  2. package/dist/agent/agent.d.ts +15 -1
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +144 -10
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/loop-detector.d.ts +39 -0
  7. package/dist/agent/loop-detector.d.ts.map +1 -0
  8. package/dist/agent/loop-detector.js +122 -0
  9. package/dist/agent/loop-detector.js.map +1 -0
  10. package/dist/agent/pool.d.ts +2 -1
  11. package/dist/agent/pool.d.ts.map +1 -1
  12. package/dist/agent/pool.js +4 -2
  13. package/dist/agent/pool.js.map +1 -1
  14. package/dist/agent/runner.d.ts +23 -1
  15. package/dist/agent/runner.d.ts.map +1 -1
  16. package/dist/agent/runner.js +113 -12
  17. package/dist/agent/runner.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/llm/adapter.d.ts +4 -1
  23. package/dist/llm/adapter.d.ts.map +1 -1
  24. package/dist/llm/adapter.js +11 -0
  25. package/dist/llm/adapter.js.map +1 -1
  26. package/dist/llm/copilot.d.ts.map +1 -1
  27. package/dist/llm/copilot.js +2 -1
  28. package/dist/llm/copilot.js.map +1 -1
  29. package/dist/llm/gemini.d.ts +65 -0
  30. package/dist/llm/gemini.d.ts.map +1 -0
  31. package/dist/llm/gemini.js +317 -0
  32. package/dist/llm/gemini.js.map +1 -0
  33. package/dist/llm/grok.d.ts +21 -0
  34. package/dist/llm/grok.d.ts.map +1 -0
  35. package/dist/llm/grok.js +24 -0
  36. package/dist/llm/grok.js.map +1 -0
  37. package/dist/llm/openai-common.d.ts +8 -1
  38. package/dist/llm/openai-common.d.ts.map +1 -1
  39. package/dist/llm/openai-common.js +35 -2
  40. package/dist/llm/openai-common.js.map +1 -1
  41. package/dist/llm/openai.d.ts +1 -1
  42. package/dist/llm/openai.d.ts.map +1 -1
  43. package/dist/llm/openai.js +20 -2
  44. package/dist/llm/openai.js.map +1 -1
  45. package/dist/orchestrator/orchestrator.d.ts.map +1 -1
  46. package/dist/orchestrator/orchestrator.js +89 -9
  47. package/dist/orchestrator/orchestrator.js.map +1 -1
  48. package/dist/task/queue.d.ts +31 -2
  49. package/dist/task/queue.d.ts.map +1 -1
  50. package/dist/task/queue.js +69 -2
  51. package/dist/task/queue.js.map +1 -1
  52. package/dist/tool/text-tool-extractor.d.ts +32 -0
  53. package/dist/tool/text-tool-extractor.d.ts.map +1 -0
  54. package/dist/tool/text-tool-extractor.js +187 -0
  55. package/dist/tool/text-tool-extractor.js.map +1 -0
  56. package/dist/types.d.ts +139 -7
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/utils/trace.d.ts +12 -0
  59. package/dist/utils/trace.d.ts.map +1 -0
  60. package/dist/utils/trace.js +30 -0
  61. package/dist/utils/trace.js.map +1 -0
  62. package/package.json +18 -2
  63. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  64. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  65. package/.github/pull_request_template.md +0 -14
  66. package/.github/workflows/ci.yml +0 -23
  67. package/CLAUDE.md +0 -72
  68. package/CODE_OF_CONDUCT.md +0 -48
  69. package/CONTRIBUTING.md +0 -72
  70. package/DECISIONS.md +0 -43
  71. package/README_zh.md +0 -217
  72. package/SECURITY.md +0 -17
  73. package/examples/01-single-agent.ts +0 -131
  74. package/examples/02-team-collaboration.ts +0 -167
  75. package/examples/03-task-pipeline.ts +0 -201
  76. package/examples/04-multi-model-team.ts +0 -261
  77. package/examples/05-copilot-test.ts +0 -49
  78. package/examples/06-local-model.ts +0 -199
  79. package/examples/07-fan-out-aggregate.ts +0 -209
  80. package/examples/08-gemma4-local.ts +0 -203
  81. package/examples/09-gemma4-auto-orchestration.ts +0 -162
  82. package/src/agent/agent.ts +0 -473
  83. package/src/agent/pool.ts +0 -278
  84. package/src/agent/runner.ts +0 -413
  85. package/src/agent/structured-output.ts +0 -126
  86. package/src/index.ts +0 -167
  87. package/src/llm/adapter.ts +0 -87
  88. package/src/llm/anthropic.ts +0 -389
  89. package/src/llm/copilot.ts +0 -551
  90. package/src/llm/openai-common.ts +0 -255
  91. package/src/llm/openai.ts +0 -272
  92. package/src/memory/shared.ts +0 -181
  93. package/src/memory/store.ts +0 -124
  94. package/src/orchestrator/orchestrator.ts +0 -977
  95. package/src/orchestrator/scheduler.ts +0 -352
  96. package/src/task/queue.ts +0 -394
  97. package/src/task/task.ts +0 -239
  98. package/src/team/messaging.ts +0 -232
  99. package/src/team/team.ts +0 -334
  100. package/src/tool/built-in/bash.ts +0 -187
  101. package/src/tool/built-in/file-edit.ts +0 -154
  102. package/src/tool/built-in/file-read.ts +0 -105
  103. package/src/tool/built-in/file-write.ts +0 -81
  104. package/src/tool/built-in/grep.ts +0 -362
  105. package/src/tool/built-in/index.ts +0 -50
  106. package/src/tool/executor.ts +0 -178
  107. package/src/tool/framework.ts +0 -557
  108. package/src/types.ts +0 -391
  109. package/src/utils/semaphore.ts +0 -89
  110. package/tests/semaphore.test.ts +0 -57
  111. package/tests/shared-memory.test.ts +0 -122
  112. package/tests/structured-output.test.ts +0 -331
  113. package/tests/task-queue.test.ts +0 -244
  114. package/tests/task-retry.test.ts +0 -368
  115. package/tests/task-utils.test.ts +0 -155
  116. package/tests/tool-executor.test.ts +0 -193
  117. package/tsconfig.json +0 -25
@@ -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,199 +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
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // Progress handler
71
- // ---------------------------------------------------------------------------
72
-
73
- const taskTimes = new Map<string, number>()
74
-
75
- function handleProgress(event: OrchestratorEvent): void {
76
- const ts = new Date().toISOString().slice(11, 23)
77
-
78
- switch (event.type) {
79
- case 'task_start': {
80
- taskTimes.set(event.task ?? '', Date.now())
81
- const task = event.data as Task | undefined
82
- console.log(`[${ts}] TASK READY "${task?.title ?? event.task}" → ${task?.assignee ?? '?'}`)
83
- break
84
- }
85
- case 'task_complete': {
86
- const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
87
- console.log(`[${ts}] TASK DONE task=${event.task} in ${elapsed}ms`)
88
- break
89
- }
90
- case 'agent_start':
91
- console.log(`[${ts}] AGENT START ${event.agent}`)
92
- break
93
- case 'agent_complete':
94
- console.log(`[${ts}] AGENT DONE ${event.agent}`)
95
- break
96
- case 'error':
97
- console.error(`[${ts}] ERROR ${event.agent ?? ''} task=${event.task ?? '?'}`)
98
- break
99
- }
100
- }
101
-
102
- // ---------------------------------------------------------------------------
103
- // Orchestrator + Team
104
- // ---------------------------------------------------------------------------
105
-
106
- const orchestrator = new OpenMultiAgent({
107
- defaultModel: 'claude-sonnet-4-6',
108
- maxConcurrency: 2,
109
- onProgress: handleProgress,
110
- })
111
-
112
- const team = orchestrator.createTeam('local-cloud-team', {
113
- name: 'local-cloud-team',
114
- agents: [coder, reviewer],
115
- sharedMemory: true,
116
- })
117
-
118
- // ---------------------------------------------------------------------------
119
- // Task pipeline: code → review
120
- // ---------------------------------------------------------------------------
121
-
122
- const OUTPUT_DIR = '/tmp/local-model-demo'
123
-
124
- const tasks: Array<{
125
- title: string
126
- description: string
127
- assignee?: string
128
- dependsOn?: string[]
129
- }> = [
130
- {
131
- title: 'Write: retry utility',
132
- description: `Write a small but complete TypeScript utility to ${OUTPUT_DIR}/retry.ts.
133
-
134
- The module should export:
135
- 1. A \`RetryOptions\` interface with: maxRetries (number), delayMs (number),
136
- backoffFactor (optional number, default 2), shouldRetry (optional predicate
137
- taking the error and returning boolean).
138
- 2. An async \`retry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>\`
139
- function that retries \`fn\` with exponential backoff.
140
- 3. A convenience \`withRetry\` wrapper that returns a new function with retry
141
- behaviour baked in.
142
-
143
- Include JSDoc comments. No external dependencies — use only Node built-ins.
144
- After writing the file, also create a small test script at ${OUTPUT_DIR}/retry-test.ts
145
- that exercises the happy path and a failure case, then run it with \`npx tsx\`.`,
146
- assignee: 'coder',
147
- },
148
- {
149
- title: 'Review: retry utility',
150
- description: `Read the files at ${OUTPUT_DIR}/retry.ts and ${OUTPUT_DIR}/retry-test.ts.
151
-
152
- Produce a structured code review covering:
153
- - Summary (2-3 sentences describing the module)
154
- - Strengths (bullet list)
155
- - Issues (bullet list — be specific about what and why)
156
- - Verdict: SHIP or NEEDS WORK`,
157
- assignee: 'reviewer',
158
- dependsOn: ['Write: retry utility'],
159
- },
160
- ]
161
-
162
- // ---------------------------------------------------------------------------
163
- // Run
164
- // ---------------------------------------------------------------------------
165
-
166
- console.log('Local + Cloud model team')
167
- console.log(` coder → Claude (${coder.model}) via Anthropic API`)
168
- console.log(` reviewer → Ollama (${reviewer.model}) at ${reviewer.baseURL}`)
169
- console.log()
170
- console.log('Pipeline: coder writes code → local model reviews it')
171
- console.log('='.repeat(60))
172
-
173
- const result = await orchestrator.runTasks(team, tasks)
174
-
175
- // ---------------------------------------------------------------------------
176
- // Summary
177
- // ---------------------------------------------------------------------------
178
-
179
- console.log('\n' + '='.repeat(60))
180
- console.log('Pipeline complete.\n')
181
- console.log(`Overall success: ${result.success}`)
182
- console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
183
-
184
- console.log('\nPer-agent summary:')
185
- for (const [name, r] of result.agentResults) {
186
- const icon = r.success ? 'OK ' : 'FAIL'
187
- const provider = name === 'coder' ? 'anthropic' : 'ollama (local)'
188
- const tools = r.toolCalls.map(c => c.toolName).join(', ')
189
- console.log(` [${icon}] ${name.padEnd(10)} (${provider.padEnd(16)}) tools: ${tools || '(none)'}`)
190
- }
191
-
192
- // Print the reviewer's output
193
- const review = result.agentResults.get('reviewer')
194
- if (review?.success) {
195
- console.log('\nCode review (from local model):')
196
- console.log('─'.repeat(60))
197
- console.log(review.output)
198
- console.log('─'.repeat(60))
199
- }