openclaw-agent-builder 0.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.
- package/README.md +75 -0
- package/bin/cli.js +30 -0
- package/dist/assets/index-J39606WI.css +1 -0
- package/dist/assets/index-yzWCTaaY.js +228 -0
- package/dist/index.html +13 -0
- package/package.json +46 -0
- package/server/generators/agents.js +87 -0
- package/server/generators/bootstrap.js +42 -0
- package/server/generators/identity.js +18 -0
- package/server/generators/index.js +81 -0
- package/server/generators/memory.js +58 -0
- package/server/generators/routing.js +51 -0
- package/server/generators/soul.js +53 -0
- package/server/generators/team.js +58 -0
- package/server/generators/tools.js +42 -0
- package/server/generators/user.js +27 -0
- package/server/index.js +56 -0
- package/server/routes/agentChat.js +168 -0
- package/server/routes/capabilities.js +195 -0
- package/server/routes/channel.js +161 -0
- package/server/routes/chat.js +150 -0
- package/server/routes/config.js +130 -0
- package/server/routes/files.js +78 -0
- package/server/routes/generate.js +17 -0
- package/server/routes/preflight.js +114 -0
- package/server/routes/validate.js +30 -0
package/dist/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OpenClaw Agent Builder</title>
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-yzWCTaaY.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-J39606WI.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-agent-builder",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Web-based wizard to create and deploy OpenClaw agents and multi-agent teams",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openclaw-agent-builder": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "NODE_ENV=development node bin/cli.js --no-open",
|
|
11
|
+
"build": "cd client && vite build",
|
|
12
|
+
"start": "NODE_ENV=production node bin/cli.js",
|
|
13
|
+
"preview": "npm run build && npm start"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/",
|
|
17
|
+
"server/",
|
|
18
|
+
"dist/",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"openclaw",
|
|
26
|
+
"agent",
|
|
27
|
+
"wizard",
|
|
28
|
+
"ai",
|
|
29
|
+
"multi-agent"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"express": "^4.18.2",
|
|
34
|
+
"open": "^9.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
38
|
+
"autoprefixer": "^10.4.17",
|
|
39
|
+
"postcss": "^8.4.35",
|
|
40
|
+
"react": "^18.2.0",
|
|
41
|
+
"react-dom": "^18.2.0",
|
|
42
|
+
"tailwindcss": "^3.4.1",
|
|
43
|
+
"vite": "^5.0.12",
|
|
44
|
+
"zustand": "^4.5.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate AGENTS.md for an agent.
|
|
3
|
+
* Pure function — never invents content not in the TeamSpec.
|
|
4
|
+
*/
|
|
5
|
+
export function generateAgents(agent, teamSpec) {
|
|
6
|
+
const neverRules = agent.never && agent.never.length > 0
|
|
7
|
+
? agent.never.map(r => `- **NEVER** ${r}`).join('\n')
|
|
8
|
+
: '- // TODO: define red lines';
|
|
9
|
+
|
|
10
|
+
const isRouter = teamSpec.orchestration &&
|
|
11
|
+
teamSpec.orchestration.router_agent === agent.id;
|
|
12
|
+
|
|
13
|
+
const otherAgents = teamSpec.agents.filter(a => a.id !== agent.id);
|
|
14
|
+
|
|
15
|
+
let orchestrationSection = '';
|
|
16
|
+
if (isRouter && otherAgents.length > 0) {
|
|
17
|
+
const rows = otherAgents.map(a =>
|
|
18
|
+
`| ${a.id} | ${a.name} | ${a.mission || '// TODO'} |`
|
|
19
|
+
).join('\n');
|
|
20
|
+
orchestrationSection = `
|
|
21
|
+
## Orchestration Rules
|
|
22
|
+
|
|
23
|
+
You are the router agent. Route tasks to the appropriate specialist:
|
|
24
|
+
|
|
25
|
+
| Agent ID | Name | Mission |
|
|
26
|
+
|----------|------|---------|
|
|
27
|
+
${rows}
|
|
28
|
+
|
|
29
|
+
Orchestration mode: \`${teamSpec.orchestration.mode}\`
|
|
30
|
+
`;
|
|
31
|
+
} else if (isRouter) {
|
|
32
|
+
orchestrationSection = `
|
|
33
|
+
## Orchestration Rules
|
|
34
|
+
|
|
35
|
+
You are the router agent. Orchestration mode: \`${teamSpec.orchestration?.mode || 'hub_and_spoke'}\`
|
|
36
|
+
`;
|
|
37
|
+
} else if (teamSpec.orchestration && teamSpec.orchestration.router_agent) {
|
|
38
|
+
orchestrationSection = `
|
|
39
|
+
## Orchestration Rules
|
|
40
|
+
|
|
41
|
+
Escalate to **${teamSpec.orchestration.router_agent}** for coordination decisions.
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agentHandoffs = (teamSpec.handoffs || []).filter(h => h.from === agent.id);
|
|
46
|
+
let handoffSection = '';
|
|
47
|
+
if (agentHandoffs.length > 0) {
|
|
48
|
+
const rows = agentHandoffs.map(h =>
|
|
49
|
+
`| ${h.to} | ${h.when} | ${h.payload_contract} |`
|
|
50
|
+
).join('\n');
|
|
51
|
+
handoffSection = `
|
|
52
|
+
## Cross-Agent Handoffs
|
|
53
|
+
|
|
54
|
+
| To Agent | When | Payload |
|
|
55
|
+
|----------|------|---------|
|
|
56
|
+
${rows}
|
|
57
|
+
`;
|
|
58
|
+
} else {
|
|
59
|
+
handoffSection = `
|
|
60
|
+
## Cross-Agent Handoffs
|
|
61
|
+
|
|
62
|
+
// TODO: define handoff conditions
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `# AGENTS.md - Operating Rules
|
|
67
|
+
|
|
68
|
+
## Session Startup
|
|
69
|
+
|
|
70
|
+
1. Read \`SOUL.md\` — who you are
|
|
71
|
+
2. Read \`USER.md\` — who you're helping
|
|
72
|
+
3. Read \`MEMORY.md\` — your long-term memory
|
|
73
|
+
4. Read \`memory/YYYY-MM-DD.md\` (today + yesterday) for recent context
|
|
74
|
+
|
|
75
|
+
## Red Lines
|
|
76
|
+
|
|
77
|
+
${neverRules}
|
|
78
|
+
|
|
79
|
+
## Escalation Rules
|
|
80
|
+
|
|
81
|
+
${agent.escalation || '// TODO: define escalation rules'}
|
|
82
|
+
|
|
83
|
+
## Failure Behavior
|
|
84
|
+
|
|
85
|
+
${agent.failure || '// TODO: define failure behavior'}
|
|
86
|
+
${orchestrationSection}${handoffSection}`;
|
|
87
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate BOOTSTRAP.md for an agent.
|
|
3
|
+
* Pure function.
|
|
4
|
+
*/
|
|
5
|
+
export function generateBootstrap(agent, _teamSpec) {
|
|
6
|
+
const inputsSection = agent.inputs
|
|
7
|
+
? agent.inputs
|
|
8
|
+
.split(/[\n;]+/)
|
|
9
|
+
.map(s => s.trim())
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.map(s => `- ${s}`)
|
|
12
|
+
.join('\n')
|
|
13
|
+
: '- // TODO: inputs not specified';
|
|
14
|
+
|
|
15
|
+
return `# BOOTSTRAP.md - Session Startup
|
|
16
|
+
|
|
17
|
+
## Who You Are
|
|
18
|
+
|
|
19
|
+
**${agent.name}** — ${agent.mission || '// TODO: mission not specified'}
|
|
20
|
+
|
|
21
|
+
## What Triggers You
|
|
22
|
+
|
|
23
|
+
${inputsSection}
|
|
24
|
+
|
|
25
|
+
## Startup Checklist
|
|
26
|
+
|
|
27
|
+
1. Read \`SOUL.md\` — your identity and values
|
|
28
|
+
2. Read \`USER.md\` — who you're helping
|
|
29
|
+
3. Read \`MEMORY.md\` — long-term memory
|
|
30
|
+
4. Read \`memory/YYYY-MM-DD.md\` — recent context
|
|
31
|
+
5. Check your task queue
|
|
32
|
+
|
|
33
|
+
## First Run
|
|
34
|
+
|
|
35
|
+
If this is your first session, introduce yourself and confirm your mission.
|
|
36
|
+
Update \`IDENTITY.md\` and \`USER.md\` with what you learn.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
_Delete this file once you're up and running. You won't need it again._
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate IDENTITY.md for an agent.
|
|
3
|
+
* Pure function.
|
|
4
|
+
*/
|
|
5
|
+
export function generateIdentity(agent, _teamSpec) {
|
|
6
|
+
const roleShort = agent.mission
|
|
7
|
+
? agent.mission.split('.')[0].slice(0, 80)
|
|
8
|
+
: '// TODO';
|
|
9
|
+
|
|
10
|
+
return `# IDENTITY.md - Who Am I?
|
|
11
|
+
|
|
12
|
+
- **Name:** ${agent.name}
|
|
13
|
+
- **Role:** ${roleShort}
|
|
14
|
+
- **Vibe:** // TODO
|
|
15
|
+
- **Emoji:** // TODO
|
|
16
|
+
- **Avatar:** // TODO
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { generateSoul } from './soul.js';
|
|
2
|
+
import { generateAgents } from './agents.js';
|
|
3
|
+
import { generateIdentity } from './identity.js';
|
|
4
|
+
import { generateUser } from './user.js';
|
|
5
|
+
import { generateTools } from './tools.js';
|
|
6
|
+
import { generateBootstrap } from './bootstrap.js';
|
|
7
|
+
import { generateMemory, generateMemoryReadme } from './memory.js';
|
|
8
|
+
import { generateTeam } from './team.js';
|
|
9
|
+
import { generateRouting } from './routing.js';
|
|
10
|
+
|
|
11
|
+
function generateSkillsReadme(agent, teamSpec) {
|
|
12
|
+
const recommended = teamSpec.capabilities?.recommendedSkills || [];
|
|
13
|
+
const agentSkills = recommended.filter(s => s.source === 'clawhub' || s.source === 'workspace');
|
|
14
|
+
|
|
15
|
+
const installLines = agentSkills.length > 0
|
|
16
|
+
? agentSkills.map(s => `# ${s.name}: ${s.reason}\nclawhub install ${s.id}`).join('\n\n')
|
|
17
|
+
: '# Add workspace-local skills here\n# clawhub install <skill-id>';
|
|
18
|
+
|
|
19
|
+
return `# Workspace Skills — ${agent.name || agent.id}
|
|
20
|
+
|
|
21
|
+
Skills placed in this folder are **local to this agent only** and override any global skill with the same name.
|
|
22
|
+
|
|
23
|
+
## How to install a skill into this workspace
|
|
24
|
+
|
|
25
|
+
\`\`\`bash
|
|
26
|
+
# From inside this workspace directory:
|
|
27
|
+
clawhub install <skill-id>
|
|
28
|
+
|
|
29
|
+
# Or specify the workspace explicitly:
|
|
30
|
+
clawhub install <skill-id> --workdir ~/.openclaw/workspace-${agent.id}
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
## Recommended skills for this agent
|
|
34
|
+
|
|
35
|
+
${installLines}
|
|
36
|
+
|
|
37
|
+
## Notes
|
|
38
|
+
|
|
39
|
+
- Skills here take precedence over bundled and managed (~/.openclaw/skills) skills of the same name.
|
|
40
|
+
- After installing, restart the OpenClaw gateway (or start a new session) for the skill to take effect.
|
|
41
|
+
- Browse all available skills at https://clawhub.ai
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate all workspace files for a TeamSpec.
|
|
47
|
+
* Returns [{ path, content }] in the canonical order.
|
|
48
|
+
*/
|
|
49
|
+
export function generateFiles(teamSpec) {
|
|
50
|
+
const files = [];
|
|
51
|
+
|
|
52
|
+
// 1. team/TEAM.md
|
|
53
|
+
files.push({
|
|
54
|
+
path: 'team/TEAM.md',
|
|
55
|
+
content: generateTeam(teamSpec),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 2. gateway/openclaw.routing.json5
|
|
59
|
+
files.push({
|
|
60
|
+
path: 'gateway/openclaw.routing.json5',
|
|
61
|
+
content: generateRouting(teamSpec),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 3. Per-agent files, alphabetically by agentId
|
|
65
|
+
const sortedAgents = [...teamSpec.agents].sort((a, b) => a.id.localeCompare(b.id));
|
|
66
|
+
|
|
67
|
+
for (const agent of sortedAgents) {
|
|
68
|
+
const prefix = `workspace-${agent.id}`;
|
|
69
|
+
files.push({ path: `${prefix}/SOUL.md`, content: generateSoul(agent, teamSpec) });
|
|
70
|
+
files.push({ path: `${prefix}/AGENTS.md`, content: generateAgents(agent, teamSpec) });
|
|
71
|
+
files.push({ path: `${prefix}/IDENTITY.md`, content: generateIdentity(agent, teamSpec) });
|
|
72
|
+
files.push({ path: `${prefix}/USER.md`, content: generateUser(agent, teamSpec) });
|
|
73
|
+
files.push({ path: `${prefix}/TOOLS.md`, content: generateTools(agent, teamSpec) });
|
|
74
|
+
files.push({ path: `${prefix}/BOOTSTRAP.md`, content: generateBootstrap(agent, teamSpec) });
|
|
75
|
+
files.push({ path: `${prefix}/MEMORY.md`, content: generateMemory(agent, teamSpec) });
|
|
76
|
+
files.push({ path: `${prefix}/memory/README.md`, content: generateMemoryReadme(agent, teamSpec) });
|
|
77
|
+
files.push({ path: `${prefix}/skills/README.md`, content: generateSkillsReadme(agent, teamSpec) });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return files;
|
|
81
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate MEMORY.md and memory/README.md for an agent.
|
|
3
|
+
* Pure functions.
|
|
4
|
+
*/
|
|
5
|
+
export function generateMemory(agent, _teamSpec) {
|
|
6
|
+
return `# MEMORY.md - Long-Term Memory
|
|
7
|
+
|
|
8
|
+
## Role
|
|
9
|
+
|
|
10
|
+
**${agent.name}** — ${agent.mission || '// TODO'}
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
// TODO: Add long-term goals and current priorities
|
|
15
|
+
|
|
16
|
+
## Active Context
|
|
17
|
+
|
|
18
|
+
// TODO: What are you currently working on?
|
|
19
|
+
|
|
20
|
+
## Key Decisions
|
|
21
|
+
|
|
22
|
+
// TODO: Important decisions made so far
|
|
23
|
+
|
|
24
|
+
## Lessons Learned
|
|
25
|
+
|
|
26
|
+
// TODO: What have you learned that future-you should know?
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
_Update this file during sessions. This is your curated long-term memory._
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function generateMemoryReadme(_agent, _teamSpec) {
|
|
35
|
+
return `# memory/ - Session Logs
|
|
36
|
+
|
|
37
|
+
## Naming Convention
|
|
38
|
+
|
|
39
|
+
\`YYYY-MM-DD.md\` — one file per day.
|
|
40
|
+
|
|
41
|
+
## What to Log
|
|
42
|
+
|
|
43
|
+
- Tasks completed
|
|
44
|
+
- Decisions made and why
|
|
45
|
+
- Context that will be useful next session
|
|
46
|
+
- Errors encountered and how they were resolved
|
|
47
|
+
- Links, IDs, and references for ongoing work
|
|
48
|
+
|
|
49
|
+
## What NOT to Log
|
|
50
|
+
|
|
51
|
+
- Sensitive credentials (use environment variables)
|
|
52
|
+
- Redundant info already in SOUL.md or AGENTS.md
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
_These are raw daily notes. Distill important learnings into MEMORY.md periodically._
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate gateway/openclaw.routing.json5.
|
|
3
|
+
* Pure function — outputs JSON5 string with // TODO comments for missing data.
|
|
4
|
+
*/
|
|
5
|
+
export function generateRouting(teamSpec) {
|
|
6
|
+
const { agents, routing, orchestration } = teamSpec;
|
|
7
|
+
const defaultAgentId = routing?.defaultAgentId || orchestration?.router_agent || agents[0]?.id;
|
|
8
|
+
|
|
9
|
+
const agentsList = agents.map(a => {
|
|
10
|
+
const isDefault = a.id === defaultAgentId;
|
|
11
|
+
const workspace = `~/.openclaw/workspace-${a.id}`;
|
|
12
|
+
if (isDefault) {
|
|
13
|
+
return ` { id: "${a.id}", default: true, workspace: "${workspace}" }`;
|
|
14
|
+
}
|
|
15
|
+
return ` { id: "${a.id}", workspace: "${workspace}" }`;
|
|
16
|
+
}).join(',\n');
|
|
17
|
+
|
|
18
|
+
const bindingsList = (routing?.bindings || []).map(b => {
|
|
19
|
+
const matchParts = Object.entries(b.match)
|
|
20
|
+
.map(([k, v]) => {
|
|
21
|
+
if (typeof v === 'object') {
|
|
22
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
23
|
+
}
|
|
24
|
+
return `${k}: "${v}"`;
|
|
25
|
+
})
|
|
26
|
+
.join(', ');
|
|
27
|
+
return ` { agentId: "${b.agentId}", match: { ${matchParts} } }`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const bindingsSection = bindingsList.length === 0
|
|
31
|
+
? ` // TODO: Add channel bindings\n // Example: { agentId: "${defaultAgentId}", match: { channel: "discord", accountId: "${defaultAgentId}" } }`
|
|
32
|
+
: bindingsList.join(',\n');
|
|
33
|
+
|
|
34
|
+
return `{
|
|
35
|
+
// OpenClaw Gateway Routing Config
|
|
36
|
+
// Generated by openclaw-agent-builder
|
|
37
|
+
// Edit as needed — this is JSON5, so comments are supported.
|
|
38
|
+
|
|
39
|
+
agents: {
|
|
40
|
+
list: [
|
|
41
|
+
${agentsList}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Bindings — sorted by specificity: peer > guildId > teamId > accountId-exact > accountId:* > default
|
|
46
|
+
bindings: [
|
|
47
|
+
${bindingsSection}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate SOUL.md for an agent.
|
|
3
|
+
* Pure function — never invents content not in the TeamSpec.
|
|
4
|
+
*/
|
|
5
|
+
export function generateSoul(agent, teamSpec) {
|
|
6
|
+
const otherAgents = teamSpec.agents.filter(a => a.id !== agent.id);
|
|
7
|
+
|
|
8
|
+
const whatIDo = agent.outputs
|
|
9
|
+
? agent.outputs
|
|
10
|
+
.split(/[\n;]+/)
|
|
11
|
+
.map(s => s.trim())
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map(s => `- ${s}`)
|
|
14
|
+
.join('\n')
|
|
15
|
+
: '- // TODO: outputs not specified';
|
|
16
|
+
|
|
17
|
+
const teamSection = otherAgents.length > 0
|
|
18
|
+
? otherAgents.map(a => `- **${a.name}** — ${a.mission || '// TODO: mission'}`).join('\n')
|
|
19
|
+
: teamSpec.team && teamSpec.team.name
|
|
20
|
+
? `- Working solo within **${teamSpec.team.name}**`
|
|
21
|
+
: '- // TODO: team context';
|
|
22
|
+
|
|
23
|
+
const neverRules = agent.never && agent.never.length > 0
|
|
24
|
+
? agent.never.map(r => `- **NEVER** ${r}`).join('\n')
|
|
25
|
+
: '- // TODO: define never rules';
|
|
26
|
+
|
|
27
|
+
return `# SOUL.md - Who I Am
|
|
28
|
+
|
|
29
|
+
I'm **${agent.name}** — ${agent.mission || '// TODO: mission not specified'}
|
|
30
|
+
|
|
31
|
+
## My Purpose
|
|
32
|
+
|
|
33
|
+
${agent.mission || '// TODO: describe agent purpose in detail'}
|
|
34
|
+
|
|
35
|
+
## What I Do
|
|
36
|
+
|
|
37
|
+
${whatIDo}
|
|
38
|
+
|
|
39
|
+
## How I Operate
|
|
40
|
+
|
|
41
|
+
${agent.failure ? `**Failure behavior:** ${agent.failure}` : '// TODO: failure behavior'}
|
|
42
|
+
|
|
43
|
+
${agent.escalation ? `**Escalation:** ${agent.escalation}` : '// TODO: escalation rules'}
|
|
44
|
+
|
|
45
|
+
## My Team
|
|
46
|
+
|
|
47
|
+
${teamSection}
|
|
48
|
+
|
|
49
|
+
## Hard Rules
|
|
50
|
+
|
|
51
|
+
${neverRules}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate team/TEAM.md.
|
|
3
|
+
* Pure function.
|
|
4
|
+
*/
|
|
5
|
+
export function generateTeam(teamSpec) {
|
|
6
|
+
const agentsTable = teamSpec.agents.map(a =>
|
|
7
|
+
`| ${a.id} | ${a.name} | ${a.mission || '// TODO'} |`
|
|
8
|
+
).join('\n');
|
|
9
|
+
|
|
10
|
+
const handoffGraph = (teamSpec.handoffs || []).length > 0
|
|
11
|
+
? teamSpec.handoffs.map(h =>
|
|
12
|
+
`- **${h.from}** → **${h.to}**: ${h.when} (${h.payload_contract})`
|
|
13
|
+
).join('\n')
|
|
14
|
+
: '// TODO: No handoffs defined';
|
|
15
|
+
|
|
16
|
+
const sharedNever = (teamSpec.shared_constraints?.never || []).length > 0
|
|
17
|
+
? teamSpec.shared_constraints.never.map(r => `- **NEVER** ${r}`).join('\n')
|
|
18
|
+
: '// TODO: Define shared constraints';
|
|
19
|
+
|
|
20
|
+
const memoryMode = teamSpec.shared_memory?.mode || 'separate';
|
|
21
|
+
const memoryLocation = teamSpec.shared_memory?.location || '// TODO';
|
|
22
|
+
|
|
23
|
+
const teamName = teamSpec.team?.name ||
|
|
24
|
+
(teamSpec.agents.length === 1 ? `${teamSpec.agents[0].name} Workspace` : '// TODO');
|
|
25
|
+
|
|
26
|
+
return `# TEAM.md - Team Charter
|
|
27
|
+
|
|
28
|
+
## Team
|
|
29
|
+
|
|
30
|
+
**${teamName}**
|
|
31
|
+
|
|
32
|
+
${teamSpec.team?.mission || '// TODO: Define team mission'}
|
|
33
|
+
|
|
34
|
+
## Agents
|
|
35
|
+
|
|
36
|
+
| ID | Name | Mission |
|
|
37
|
+
|----|------|---------|
|
|
38
|
+
${agentsTable}
|
|
39
|
+
|
|
40
|
+
## Orchestration
|
|
41
|
+
|
|
42
|
+
- **Mode:** ${teamSpec.orchestration?.mode || '// TODO'}
|
|
43
|
+
- **Router Agent:** ${teamSpec.orchestration?.router_agent || '// TODO'}
|
|
44
|
+
|
|
45
|
+
## Handoff Graph
|
|
46
|
+
|
|
47
|
+
${handoffGraph}
|
|
48
|
+
|
|
49
|
+
## Shared Constraints
|
|
50
|
+
|
|
51
|
+
${sharedNever}
|
|
52
|
+
|
|
53
|
+
## Memory Model
|
|
54
|
+
|
|
55
|
+
- **Mode:** ${memoryMode}
|
|
56
|
+
${memoryMode === 'shared_summary' ? `- **Location:** ${memoryLocation}` : '- Each agent maintains their own MEMORY.md'}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate TOOLS.md for an agent.
|
|
3
|
+
* Pure function.
|
|
4
|
+
*/
|
|
5
|
+
export function generateTools(agent, _teamSpec) {
|
|
6
|
+
const inputsSection = agent.inputs
|
|
7
|
+
? agent.inputs
|
|
8
|
+
.split(/[\n;]+/)
|
|
9
|
+
.map(s => s.trim())
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.map(s => `- ${s}`)
|
|
12
|
+
.join('\n')
|
|
13
|
+
: '- // TODO: inputs not specified';
|
|
14
|
+
|
|
15
|
+
const outputsSection = agent.outputs
|
|
16
|
+
? agent.outputs
|
|
17
|
+
.split(/[\n;]+/)
|
|
18
|
+
.map(s => s.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map(s => `- ${s}`)
|
|
21
|
+
.join('\n')
|
|
22
|
+
: '- // TODO: outputs not specified';
|
|
23
|
+
|
|
24
|
+
return `# TOOLS.md - Local Notes
|
|
25
|
+
|
|
26
|
+
## Inputs / Triggers
|
|
27
|
+
|
|
28
|
+
${inputsSection}
|
|
29
|
+
|
|
30
|
+
## Outputs
|
|
31
|
+
|
|
32
|
+
${outputsSection}
|
|
33
|
+
|
|
34
|
+
## Environment
|
|
35
|
+
|
|
36
|
+
// TODO: Add environment-specific details (API keys locations, SSH hosts, device names, etc.)
|
|
37
|
+
|
|
38
|
+
## Skills
|
|
39
|
+
|
|
40
|
+
// TODO: List installed skills and their configurations
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate USER.md for an agent.
|
|
3
|
+
* Pure function.
|
|
4
|
+
*/
|
|
5
|
+
export function generateUser(agent, teamSpec) {
|
|
6
|
+
const teamName = teamSpec.team?.name || '// TODO';
|
|
7
|
+
const teamMission = teamSpec.team?.mission || '// TODO';
|
|
8
|
+
|
|
9
|
+
return `# USER.md - About Your Human
|
|
10
|
+
|
|
11
|
+
- **Name:** // TODO
|
|
12
|
+
- **What to call them:** // TODO
|
|
13
|
+
- **Pronouns:** // TODO
|
|
14
|
+
- **Timezone:** // TODO
|
|
15
|
+
- **Notes:** // TODO
|
|
16
|
+
|
|
17
|
+
## Context
|
|
18
|
+
|
|
19
|
+
// TODO: Learn about the person you're helping. Update this as you go.
|
|
20
|
+
|
|
21
|
+
## Team Context
|
|
22
|
+
|
|
23
|
+
- **Team:** ${teamName}
|
|
24
|
+
- **Team Mission:** ${teamMission}
|
|
25
|
+
- **Your Role:** ${agent.mission || '// TODO'}
|
|
26
|
+
`;
|
|
27
|
+
}
|
package/server/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import generateRoute from './routes/generate.js';
|
|
6
|
+
import filesRoute from './routes/files.js';
|
|
7
|
+
import configRoute from './routes/config.js';
|
|
8
|
+
import validateRoute from './routes/validate.js';
|
|
9
|
+
import chatRoute from './routes/chat.js';
|
|
10
|
+
import channelRoute from './routes/channel.js';
|
|
11
|
+
import preflightRoute from './routes/preflight.js';
|
|
12
|
+
import capabilitiesRoute from './routes/capabilities.js';
|
|
13
|
+
import agentChatRoute from './routes/agentChat.js';
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
export async function createServer() {
|
|
18
|
+
const app = express();
|
|
19
|
+
app.use(express.json({ limit: '10mb' }));
|
|
20
|
+
|
|
21
|
+
// API routes
|
|
22
|
+
app.use('/api/generate', generateRoute);
|
|
23
|
+
app.use('/api', filesRoute);
|
|
24
|
+
app.use('/api', configRoute);
|
|
25
|
+
app.use('/api', validateRoute);
|
|
26
|
+
app.use('/api', chatRoute);
|
|
27
|
+
app.use('/api', channelRoute);
|
|
28
|
+
app.use('/api', preflightRoute);
|
|
29
|
+
app.use('/api', capabilitiesRoute);
|
|
30
|
+
app.use('/api', agentChatRoute);
|
|
31
|
+
|
|
32
|
+
if (process.env.NODE_ENV === 'development') {
|
|
33
|
+
// Vite dev server proxy
|
|
34
|
+
const { createServer: createViteServer } = await import('vite');
|
|
35
|
+
const vite = await createViteServer({
|
|
36
|
+
root: path.join(__dirname, '../client'),
|
|
37
|
+
server: { middlewareMode: true },
|
|
38
|
+
appType: 'spa',
|
|
39
|
+
});
|
|
40
|
+
app.use(vite.middlewares);
|
|
41
|
+
} else {
|
|
42
|
+
// Serve built client
|
|
43
|
+
const distPath = path.join(__dirname, '../dist');
|
|
44
|
+
if (!existsSync(path.join(distPath, 'index.html'))) {
|
|
45
|
+
console.error('\n ERROR: No built client found at dist/index.html');
|
|
46
|
+
console.error(' Run "npm run build" first, then start the server.\n');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
app.use(express.static(distPath));
|
|
50
|
+
app.get('*', (_req, res) => {
|
|
51
|
+
res.sendFile(path.join(distPath, 'index.html'));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return app;
|
|
56
|
+
}
|