create-oma-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/index.js +83 -0
- package/dist/scaffold.js +41 -0
- package/package.json +56 -0
- package/template/README.md +27 -0
- package/template/_env.example +22 -0
- package/template/_gitignore +4 -0
- package/template/package.json +15 -0
- package/template/src/index.ts +178 -0
- package/template/tsconfig.json +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 open-multi-agent contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# create-oma-app
|
|
2
|
+
|
|
3
|
+
Scaffold a runnable multi-agent demo on [`@open-multi-agent/core`](https://www.npmjs.com/package/@open-multi-agent/core) — one command from zero to a live agent DAG.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create oma-app@latest
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Answer one prompt (the project name) and you get a small project that, on its first run, shows a **coordinator breaking a single goal into a multi-agent DAG** — agents running in parallel and in dependency order — then opens a dashboard of the run in your browser.
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
my-demo/
|
|
15
|
+
├── src/index.ts # the demo: one goal → multi-agent DAG → dashboard
|
|
16
|
+
├── .env.example # OpenAI, or any OpenAI-compatible provider
|
|
17
|
+
├── package.json # one runtime dependency: @open-multi-agent/core
|
|
18
|
+
├── tsconfig.json
|
|
19
|
+
└── README.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- **One runtime dependency** — `@open-multi-agent/core`; `tsx` is the only dev dependency.
|
|
23
|
+
- **Provider-neutral** — OpenAI out of the box, or any OpenAI-compatible endpoint (DeepSeek, Groq, Ollama, …) via `OPENAI_BASE_URL` + `OMA_MODEL`.
|
|
24
|
+
- **No tools, no filesystem writes** — the default demo is pure reasoning, so the first run is fast and robust across providers.
|
|
25
|
+
|
|
26
|
+
## Run it
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm create oma-app@latest my-demo
|
|
30
|
+
cd my-demo
|
|
31
|
+
npm install
|
|
32
|
+
cp .env.example .env # add your key
|
|
33
|
+
npm run dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Next steps
|
|
37
|
+
|
|
38
|
+
Open `src/index.ts` and change the goal, add an agent, or give an agent tools. See the [examples](https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/core/examples) for tool use, MCP, structured output, and providers.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-oma-app — scaffold a runnable multi-agent demo on @open-multi-agent/core.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npm create oma-app@latest [project-name]
|
|
7
|
+
*
|
|
8
|
+
* One command from zero to a live coordinator-driven agent DAG. The copy logic
|
|
9
|
+
* lives in scaffold.ts; this file is the interactive CLI. Zero runtime
|
|
10
|
+
* dependencies — Node.js built-ins only, so `npm create oma-app` stays a fast,
|
|
11
|
+
* clean one-shot whose own deps never reach the generated project or core.
|
|
12
|
+
*/
|
|
13
|
+
import { basename, resolve } from 'node:path';
|
|
14
|
+
import { createInterface } from 'node:readline';
|
|
15
|
+
import { isNonEmptyDir, scaffold } from './scaffold.js';
|
|
16
|
+
// Minimal ANSI styling — no dependency.
|
|
17
|
+
const ESC = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bold: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
cyan: '\x1b[36m',
|
|
22
|
+
green: '\x1b[32m',
|
|
23
|
+
yellow: '\x1b[33m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
};
|
|
26
|
+
const bold = (s) => `${ESC.bold}${s}${ESC.reset}`;
|
|
27
|
+
const dim = (s) => `${ESC.dim}${s}${ESC.reset}`;
|
|
28
|
+
const cyan = (s) => `${ESC.cyan}${s}${ESC.reset}`;
|
|
29
|
+
const green = (s) => `${ESC.green}${s}${ESC.reset}`;
|
|
30
|
+
function prompt(question) {
|
|
31
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
32
|
+
return new Promise((res) => {
|
|
33
|
+
rl.question(question, (answer) => {
|
|
34
|
+
rl.close();
|
|
35
|
+
res(answer.trim());
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(` ${bold('create-oma-app')}${dim(' — scaffold a multi-agent demo')}`);
|
|
42
|
+
console.log();
|
|
43
|
+
// 1. Resolve the project directory name (argv, else one prompt).
|
|
44
|
+
let projectName = process.argv[2];
|
|
45
|
+
if (!projectName)
|
|
46
|
+
projectName = await prompt(` Project name: ${dim('(oma-demo)')} `);
|
|
47
|
+
projectName = (projectName || 'oma-demo').trim();
|
|
48
|
+
const dirName = basename(projectName);
|
|
49
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
50
|
+
// 2. Refuse to overwrite a non-empty directory without confirmation.
|
|
51
|
+
if (isNonEmptyDir(targetDir)) {
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(` ${ESC.yellow}!${ESC.reset} ${bold(dirName)} already exists and is not empty.`);
|
|
54
|
+
const answer = (await prompt(` Write into it anyway? ${dim('(y/N)')} `)).toLowerCase();
|
|
55
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
56
|
+
console.log(` ${ESC.red}✗${ESC.reset} Aborted.`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 3. Scaffold. Use the basename for the package name so a path-y project
|
|
62
|
+
// name (e.g. ./apps/my-demo) still yields a clean npm name.
|
|
63
|
+
scaffold({ targetDir, projectName: dirName });
|
|
64
|
+
// 4. Next steps.
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(` ${green('✓')} Created ${bold(dirName)}`);
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(' Next steps:');
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(` ${cyan(`cd ${projectName}`)}`);
|
|
71
|
+
console.log(` ${cyan('npm install')}`);
|
|
72
|
+
console.log(` ${cyan('cp .env.example .env')} ${dim('# then add your API key')}`);
|
|
73
|
+
console.log(` ${cyan('npm run dev')}`);
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(dim(' One goal becomes a multi-agent DAG, then a dashboard of the run'));
|
|
76
|
+
console.log(dim(' opens in your browser. OpenAI or any OpenAI-compatible endpoint'));
|
|
77
|
+
console.log(dim(' (DeepSeek, Groq, Ollama, …) — see .env.example.'));
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
main().catch((err) => {
|
|
81
|
+
console.error(err);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
});
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create-oma-app scaffold core — copy the bundled template into a target directory,
|
|
3
|
+
* restore the shipped dotfiles, and stamp the project name. No console output,
|
|
4
|
+
* no prompts: the CLI in index.ts handles interaction so this stays testable.
|
|
5
|
+
*/
|
|
6
|
+
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
/** template/ ships alongside dist/ (and src/) one level under the package root. */
|
|
10
|
+
export const TEMPLATE_DIR = fileURLToPath(new URL('../template', import.meta.url));
|
|
11
|
+
/** Turn an arbitrary project name into a valid npm package name. */
|
|
12
|
+
export function toPackageName(input) {
|
|
13
|
+
return (input
|
|
14
|
+
.trim()
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9-~]+/g, '-')
|
|
17
|
+
.replace(/^-+|-+$/g, '') || 'oma-demo');
|
|
18
|
+
}
|
|
19
|
+
/** True when `dir` exists and contains at least one entry. */
|
|
20
|
+
export function isNonEmptyDir(dir) {
|
|
21
|
+
return existsSync(dir) && readdirSync(dir).length > 0;
|
|
22
|
+
}
|
|
23
|
+
/** Copy the template into `targetDir`, restore dotfiles, stamp the name. */
|
|
24
|
+
export function scaffold({ targetDir, projectName, templateDir = TEMPLATE_DIR }) {
|
|
25
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
26
|
+
restoreDotfile(targetDir, '_gitignore', '.gitignore');
|
|
27
|
+
restoreDotfile(targetDir, '_env.example', '.env.example');
|
|
28
|
+
const pkgPath = join(targetDir, 'package.json');
|
|
29
|
+
const stamped = readFileSync(pkgPath, 'utf8').replace(/__PROJECT_NAME__/g, toPackageName(projectName));
|
|
30
|
+
writeFileSync(pkgPath, stamped);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Restore a shipped dotfile. npm strips/renames real dotfiles (notably
|
|
34
|
+
* `.gitignore`) on publish, so the template ships them as `_gitignore` /
|
|
35
|
+
* `_env.example` and we rename them on scaffold.
|
|
36
|
+
*/
|
|
37
|
+
function restoreDotfile(dir, from, to) {
|
|
38
|
+
const fromPath = join(dir, from);
|
|
39
|
+
if (existsSync(fromPath))
|
|
40
|
+
renameSync(fromPath, join(dir, to));
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-oma-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a runnable multi-agent demo on @open-multi-agent/core — one command from zero to a live agent DAG.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-oma-app": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"template",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"lint": "tsc --noEmit",
|
|
21
|
+
"typecheck:template": "tsc -p template/tsconfig.json",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"open-multi-agent",
|
|
27
|
+
"multi-agent",
|
|
28
|
+
"scaffold",
|
|
29
|
+
"create",
|
|
30
|
+
"starter",
|
|
31
|
+
"ai",
|
|
32
|
+
"agent"
|
|
33
|
+
],
|
|
34
|
+
"author": {
|
|
35
|
+
"name": "open-multi-agent",
|
|
36
|
+
"url": "https://github.com/open-multi-agent"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/open-multi-agent/open-multi-agent.git",
|
|
42
|
+
"directory": "packages/create-oma-app"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/create-oma-app#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/open-multi-agent/open-multi-agent/issues"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"typescript": "^5.6.0",
|
|
54
|
+
"vitest": "^2.1.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Multi-agent demo
|
|
2
|
+
|
|
3
|
+
Built on [`@open-multi-agent/core`](https://www.npmjs.com/package/@open-multi-agent/core), scaffolded with `npm create oma-app`.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
cp .env.example .env # add your API key
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You'll see a **coordinator** break one goal into a task DAG, several agents run in parallel and in dependency order, and a **dashboard** of the run open in your browser (`dashboard.html`).
|
|
14
|
+
|
|
15
|
+
Works with OpenAI out of the box, or any OpenAI-compatible provider — set `OPENAI_BASE_URL` + `OMA_MODEL` in `.env` (DeepSeek, Groq, Ollama, …). See `.env.example`.
|
|
16
|
+
|
|
17
|
+
## What's next
|
|
18
|
+
|
|
19
|
+
Everything lives in [`src/index.ts`](src/index.ts):
|
|
20
|
+
|
|
21
|
+
- **Change the goal** — swap the goal string for your own multi-step task.
|
|
22
|
+
- **Add an agent** — add an `AgentConfig` to the team roster; the coordinator routes work to it.
|
|
23
|
+
- **Give agents tools** — add e.g. `tools: ['file_read', 'file_write', 'bash']` to an agent so it can read/write files, run commands, or call MCP servers.
|
|
24
|
+
|
|
25
|
+
More patterns (tool use, MCP, structured output, providers) are in the [examples](https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/core/examples).
|
|
26
|
+
|
|
27
|
+
> Tip: for full editor type-checking, `npm i -D @types/node`. Not needed to run.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Set ONE provider. The demo uses an OpenAI-compatible client.
|
|
2
|
+
|
|
3
|
+
# --- OpenAI (default) -------------------------------------------------------
|
|
4
|
+
OPENAI_API_KEY=sk-...
|
|
5
|
+
|
|
6
|
+
# --- Or any OpenAI-compatible endpoint --------------------------------------
|
|
7
|
+
# Set OPENAI_BASE_URL at the provider and OMA_MODEL to one of its models.
|
|
8
|
+
#
|
|
9
|
+
# DeepSeek:
|
|
10
|
+
# OPENAI_API_KEY=sk-...
|
|
11
|
+
# OPENAI_BASE_URL=https://api.deepseek.com
|
|
12
|
+
# OMA_MODEL=deepseek-chat
|
|
13
|
+
#
|
|
14
|
+
# Groq:
|
|
15
|
+
# OPENAI_API_KEY=gsk_...
|
|
16
|
+
# OPENAI_BASE_URL=https://api.groq.com/openai/v1
|
|
17
|
+
# OMA_MODEL=llama-3.3-70b-versatile
|
|
18
|
+
#
|
|
19
|
+
# Ollama (local, no cloud key — any non-empty value works):
|
|
20
|
+
# OPENAI_API_KEY=ollama
|
|
21
|
+
# OPENAI_BASE_URL=http://localhost:11434/v1
|
|
22
|
+
# OMA_MODEL=llama3.2
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@open-multi-agent/core": "1.7.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"tsx": "^4.21.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-agent demo — one goal, automatically decomposed into a task DAG.
|
|
3
|
+
*
|
|
4
|
+
* The OpenMultiAgent coordinator reads the goal below, breaks it into tasks,
|
|
5
|
+
* assigns them to the right agents, and runs them in parallel and in dependency
|
|
6
|
+
* order. When the run finishes, a dashboard of the DAG opens in your browser.
|
|
7
|
+
*
|
|
8
|
+
* These agents use NO tools — pure reasoning — so the first run is fast and
|
|
9
|
+
* works on any OpenAI-compatible model. To watch agents use tools (read/write
|
|
10
|
+
* files, run commands, call MCP servers), give an agent a `tools` array; see
|
|
11
|
+
* the examples linked in the README.
|
|
12
|
+
*
|
|
13
|
+
* Run: npm run dev
|
|
14
|
+
*/
|
|
15
|
+
import { spawn } from 'node:child_process'
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { resolve } from 'node:path'
|
|
18
|
+
import { pathToFileURL } from 'node:url'
|
|
19
|
+
import { OpenMultiAgent, renderTeamRunDashboard } from '@open-multi-agent/core'
|
|
20
|
+
import type { AgentConfig, OrchestratorEvent } from '@open-multi-agent/core'
|
|
21
|
+
|
|
22
|
+
// --- Minimal .env loader ----------------------------------------------------
|
|
23
|
+
// Reads .env into process.env so `npm run dev` works right after
|
|
24
|
+
// `cp .env.example .env`. Inlined to keep the project at ONE runtime
|
|
25
|
+
// dependency (no dotenv). Existing env vars win.
|
|
26
|
+
function loadEnv(path = '.env'): void {
|
|
27
|
+
if (!existsSync(path)) return
|
|
28
|
+
for (const raw of readFileSync(path, 'utf8').split('\n')) {
|
|
29
|
+
const line = raw.trim()
|
|
30
|
+
if (!line || line.startsWith('#')) continue
|
|
31
|
+
const eq = line.indexOf('=')
|
|
32
|
+
if (eq === -1) continue
|
|
33
|
+
const key = line.slice(0, eq).trim()
|
|
34
|
+
const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, '')
|
|
35
|
+
if (key && !(key in process.env)) process.env[key] = val
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
loadEnv()
|
|
39
|
+
|
|
40
|
+
// --- Provider (OpenAI-compatible, env-driven) -------------------------------
|
|
41
|
+
// OPENAI_API_KEY is read automatically by the OpenAI client. For another
|
|
42
|
+
// provider, set OPENAI_BASE_URL + OMA_MODEL in .env (DeepSeek, Groq, Ollama…).
|
|
43
|
+
const model = process.env.OMA_MODEL ?? 'gpt-5.4'
|
|
44
|
+
|
|
45
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
46
|
+
console.error(
|
|
47
|
+
'\n Missing OPENAI_API_KEY.\n\n' +
|
|
48
|
+
' 1. cp .env.example .env\n' +
|
|
49
|
+
' 2. add a key — OpenAI, or any OpenAI-compatible provider\n' +
|
|
50
|
+
' (DeepSeek, Groq, Ollama, …; see .env.example)\n',
|
|
51
|
+
)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- The team: four specialists, no tools, pure reasoning -------------------
|
|
56
|
+
const strategist: AgentConfig = {
|
|
57
|
+
name: 'strategist',
|
|
58
|
+
model,
|
|
59
|
+
systemPrompt: `You define sharp, outcome-focused goals.
|
|
60
|
+
Given a brief, name the few outcomes that matter most. Concrete bullet points, no filler.`,
|
|
61
|
+
maxTurns: 1,
|
|
62
|
+
temperature: 0.3,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const planner: AgentConfig = {
|
|
66
|
+
name: 'planner',
|
|
67
|
+
model,
|
|
68
|
+
systemPrompt: `You turn outcomes into a concrete, time-boxed plan.
|
|
69
|
+
Produce clear weekly milestones — each a short, checkable line. No filler.`,
|
|
70
|
+
maxTurns: 1,
|
|
71
|
+
temperature: 0.2,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const riskAnalyst: AgentConfig = {
|
|
75
|
+
name: 'risk-analyst',
|
|
76
|
+
model,
|
|
77
|
+
systemPrompt: `You stress-test plans.
|
|
78
|
+
Name the few highest-impact risks and give one concrete, specific mitigation for each.`,
|
|
79
|
+
maxTurns: 1,
|
|
80
|
+
temperature: 0.4,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const synthesizer: AgentConfig = {
|
|
84
|
+
name: 'synthesizer',
|
|
85
|
+
model,
|
|
86
|
+
systemPrompt: `You assemble the team's work into one clean deliverable.
|
|
87
|
+
Merge the outcomes, weekly plan, and risk mitigations into a single concise document
|
|
88
|
+
a manager could hand over directly. Use short markdown headings.`,
|
|
89
|
+
maxTurns: 1,
|
|
90
|
+
temperature: 0.2,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- Live progress: watch the DAG run ---------------------------------------
|
|
94
|
+
function onProgress(event: OrchestratorEvent): void {
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case 'task_start':
|
|
97
|
+
if (event.agent) console.log(` ▶ ${event.agent} working…`)
|
|
98
|
+
break
|
|
99
|
+
case 'task_complete':
|
|
100
|
+
if (event.agent) console.log(` ✓ ${event.agent} done`)
|
|
101
|
+
break
|
|
102
|
+
case 'error':
|
|
103
|
+
console.error(` ✗ ${event.agent ?? 'run'} failed`)
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Best-effort open in the default browser; prints the path if it can't. */
|
|
109
|
+
function openInBrowser(file: string): void {
|
|
110
|
+
// pathToFileURL handles drive letters, slashes, and spaces on every OS —
|
|
111
|
+
// a hand-built `file://` string breaks on Windows paths.
|
|
112
|
+
const url = pathToFileURL(resolve(file)).href
|
|
113
|
+
const fallback = (): void => console.log(` Open it manually: ${url}`)
|
|
114
|
+
try {
|
|
115
|
+
// On Windows the opener is cmd's `start`, whose first quoted arg is the
|
|
116
|
+
// window title — pass an empty title ("") so the URL isn't swallowed.
|
|
117
|
+
const child =
|
|
118
|
+
process.platform === 'win32'
|
|
119
|
+
? spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true })
|
|
120
|
+
: spawn(process.platform === 'darwin' ? 'open' : 'xdg-open', [url], { stdio: 'ignore', detached: true })
|
|
121
|
+
child.on('error', fallback)
|
|
122
|
+
child.unref()
|
|
123
|
+
} catch {
|
|
124
|
+
fallback()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const orchestrator = new OpenMultiAgent({
|
|
129
|
+
defaultProvider: 'openai',
|
|
130
|
+
defaultModel: model,
|
|
131
|
+
defaultBaseURL: process.env.OPENAI_BASE_URL, // unset = OpenAI
|
|
132
|
+
onProgress,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const team = orchestrator.createTeam('demo-team', {
|
|
136
|
+
name: 'demo-team',
|
|
137
|
+
agents: [strategist, planner, riskAnalyst, synthesizer],
|
|
138
|
+
sharedMemory: true,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// --- The goal: multi-step, so the coordinator MUST decompose it -------------
|
|
142
|
+
// Kept well over 200 characters so it never hits the single-agent
|
|
143
|
+
// short-circuit — that is what guarantees you see a multi-agent DAG, not one
|
|
144
|
+
// agent. (create-oma-app's tests assert this length.)
|
|
145
|
+
const goal = `Design a 30-day onboarding plan for a software engineer joining a fast-moving startup.
|
|
146
|
+
Step 1: identify the 4 outcomes the new hire should reach by day 30.
|
|
147
|
+
Step 2: turn those outcomes into concrete weekly milestones for weeks 1 through 4.
|
|
148
|
+
Step 3: list the 3 risks most likely to derail onboarding, each with a specific mitigation.
|
|
149
|
+
Step 4: synthesize everything into one concise plan a manager could hand over on day one.`
|
|
150
|
+
|
|
151
|
+
console.log(`\n Goal: ${goal.split('\n')[0]}\n`)
|
|
152
|
+
console.log(' The coordinator is decomposing the goal into a task DAG:\n')
|
|
153
|
+
|
|
154
|
+
const result = await orchestrator.runTeam(team, goal)
|
|
155
|
+
|
|
156
|
+
// --- Summary: the DAG the coordinator built from one goal -------------------
|
|
157
|
+
const line = '─'.repeat(52)
|
|
158
|
+
console.log(`\n ${line}`)
|
|
159
|
+
console.log(` Run complete — success: ${result.success}`)
|
|
160
|
+
console.log(` Tokens: ${result.totalTokenUsage.input_tokens} in / ${result.totalTokenUsage.output_tokens} out`)
|
|
161
|
+
console.log('\n Task DAG (auto-decomposed — you wrote none of this wiring):')
|
|
162
|
+
for (const task of result.tasks ?? []) {
|
|
163
|
+
console.log(` • ${task.title} [${task.assignee ?? '—'}] ${task.status}`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Final plan: the synthesizer merged the team's work into one document ---
|
|
167
|
+
const plan = result.agentResults.get('synthesizer')
|
|
168
|
+
if (plan?.success && plan.output.trim()) {
|
|
169
|
+
console.log(`\n Final plan (by synthesizer):\n ${line}`)
|
|
170
|
+
console.log(plan.output)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Dashboard: render the DAG and open it ----------------------------------
|
|
174
|
+
const dashboardPath = 'dashboard.html'
|
|
175
|
+
writeFileSync(dashboardPath, renderTeamRunDashboard(result))
|
|
176
|
+
console.log(`\n ${line}`)
|
|
177
|
+
console.log(` DAG dashboard → ${dashboardPath} (opening in your browser…)`)
|
|
178
|
+
openInBrowser(dashboardPath)
|