refactor: implement three-layer agent architecture (agents / prompts / skills)
Layer 1 — src/agents/ (thin agent definitions, no prompt text)
registry.ts — AgentConfig, registerAgent(), getAgent(), AGENTS proxy, pick()
orchestrator.ts, coder.ts, pm.ts, marketing.ts — one file each, just metadata + tool picks
index.ts — barrel: imports prompts then agents (correct registration order)
Layer 2 — src/prompts/ (prompt text separated from agent logic)
loader.ts — registerPrompt(), resolvePrompt() with {{variable}} substitution
orchestrator.ts, coder.ts, pm.ts, marketing.ts — prompt templates as registered strings
orchestrator.ts now uses resolvePrompt('orchestrator', { knowledge }) instead of
inline SYSTEM_PROMPT const; {{knowledge}} variable injects project memory cleanly.
agent-runner.ts uses resolvePrompt(config.promptId) per agent turn.
Layer 3 — src/tools/skills.ts (new skills capability)
list_skills(repo) — lists .skills/<name>/SKILL.md directories from a Gitea repo
get_skill(repo, name) — reads and returns the markdown body of a skill file
Orchestrator and all agents now have get_skill in their tool sets.
Orchestrator also has list_skills and references skills in its prompt.
Also fixed:
- server.ts now passes history + knowledge_context from request body to orchestratorChat()
(these were being sent by the frontend but silently dropped)
- server.ts imports PROTECTED_GITEA_REPOS from tools/security.ts (no more duplicate)
- Deleted src/agents.ts (replaced by src/agents/ directory)
Made-with: Cursor
This commit is contained in:
4
dist/agent-runner.js
vendored
4
dist/agent-runner.js
vendored
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runAgent = runAgent;
|
||||
const llm_1 = require("./llm");
|
||||
const tools_1 = require("./tools");
|
||||
const loader_1 = require("./prompts/loader");
|
||||
const job_store_1 = require("./job-store");
|
||||
const MAX_TURNS = 40;
|
||||
/**
|
||||
@@ -23,8 +24,9 @@ async function runAgent(job, config, task, ctx) {
|
||||
(0, job_store_1.updateJob)(job.id, { status: 'running', progress: `Starting ${config.name} (${llm.modelId})…` });
|
||||
while (turn < MAX_TURNS) {
|
||||
turn++;
|
||||
const systemPrompt = (0, loader_1.resolvePrompt)(config.promptId);
|
||||
const messages = [
|
||||
{ role: 'system', content: config.systemPrompt },
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...history
|
||||
];
|
||||
const response = await llm.chat(messages, oaiTools, 8192);
|
||||
|
||||
1
dist/agents/coder.d.ts
vendored
Normal file
1
dist/agents/coder.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
16
dist/agents/coder.js
vendored
Normal file
16
dist/agents/coder.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const registry_1 = require("./registry");
|
||||
(0, registry_1.registerAgent)({
|
||||
name: 'Coder',
|
||||
description: 'Senior software engineer — writes, edits, tests, commits, and pushes code',
|
||||
model: 'B',
|
||||
promptId: 'coder',
|
||||
tools: (0, registry_1.pick)([
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'execute_command',
|
||||
'git_commit_and_push',
|
||||
'gitea_list_issues', 'gitea_close_issue',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
9
dist/agents/index.d.ts
vendored
Normal file
9
dist/agents/index.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import '../prompts/orchestrator';
|
||||
import '../prompts/coder';
|
||||
import '../prompts/pm';
|
||||
import '../prompts/marketing';
|
||||
import './orchestrator';
|
||||
import './coder';
|
||||
import './pm';
|
||||
import './marketing';
|
||||
export { AgentConfig, AGENTS, getAgent, allAgents, pick } from './registry';
|
||||
19
dist/agents/index.js
vendored
Normal file
19
dist/agents/index.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.pick = exports.allAgents = exports.getAgent = exports.AGENTS = void 0;
|
||||
// Import prompt templates first — side effects register them before agents reference promptIds
|
||||
require("../prompts/orchestrator");
|
||||
require("../prompts/coder");
|
||||
require("../prompts/pm");
|
||||
require("../prompts/marketing");
|
||||
// Import agent files — side effects register each agent into the registry
|
||||
require("./orchestrator");
|
||||
require("./coder");
|
||||
require("./pm");
|
||||
require("./marketing");
|
||||
// Re-export public API
|
||||
var registry_1 = require("./registry");
|
||||
Object.defineProperty(exports, "AGENTS", { enumerable: true, get: function () { return registry_1.AGENTS; } });
|
||||
Object.defineProperty(exports, "getAgent", { enumerable: true, get: function () { return registry_1.getAgent; } });
|
||||
Object.defineProperty(exports, "allAgents", { enumerable: true, get: function () { return registry_1.allAgents; } });
|
||||
Object.defineProperty(exports, "pick", { enumerable: true, get: function () { return registry_1.pick; } });
|
||||
1
dist/agents/marketing.d.ts
vendored
Normal file
1
dist/agents/marketing.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
14
dist/agents/marketing.js
vendored
Normal file
14
dist/agents/marketing.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const registry_1 = require("./registry");
|
||||
(0, registry_1.registerAgent)({
|
||||
name: 'Marketing',
|
||||
description: 'Marketing specialist — copy, blog posts, release notes, landing page content',
|
||||
model: 'A',
|
||||
promptId: 'marketing',
|
||||
tools: (0, registry_1.pick)([
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'git_commit_and_push',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
1
dist/agents/orchestrator.d.ts
vendored
Normal file
1
dist/agents/orchestrator.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
17
dist/agents/orchestrator.js
vendored
Normal file
17
dist/agents/orchestrator.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const registry_1 = require("./registry");
|
||||
(0, registry_1.registerAgent)({
|
||||
name: 'Orchestrator',
|
||||
description: 'Master coordinator — breaks down goals and delegates to specialist agents',
|
||||
model: 'B',
|
||||
promptId: 'orchestrator',
|
||||
tools: (0, registry_1.pick)([
|
||||
'gitea_create_issue', 'gitea_list_issues', 'gitea_close_issue',
|
||||
'spawn_agent', 'get_job_status',
|
||||
'coolify_list_projects', 'coolify_list_applications', 'coolify_deploy', 'coolify_get_logs',
|
||||
'list_repos', 'list_all_issues', 'list_all_apps', 'get_app_status',
|
||||
'read_repo_file', 'deploy_app', 'save_memory',
|
||||
'list_skills', 'get_skill'
|
||||
])
|
||||
});
|
||||
1
dist/agents/pm.d.ts
vendored
Normal file
1
dist/agents/pm.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
15
dist/agents/pm.js
vendored
Normal file
15
dist/agents/pm.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const registry_1 = require("./registry");
|
||||
(0, registry_1.registerAgent)({
|
||||
name: 'PM',
|
||||
description: 'Product manager — docs, issue management, project health reports',
|
||||
model: 'A',
|
||||
promptId: 'pm',
|
||||
tools: (0, registry_1.pick)([
|
||||
'gitea_create_issue', 'gitea_list_issues', 'gitea_close_issue',
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'git_commit_and_push',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
18
dist/agents/registry.d.ts
vendored
Normal file
18
dist/agents/registry.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ToolDefinition } from '../tools';
|
||||
export interface AgentConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
model: string;
|
||||
promptId: string;
|
||||
tools: ToolDefinition[];
|
||||
}
|
||||
export declare function registerAgent(config: AgentConfig): void;
|
||||
export declare function getAgent(name: string): AgentConfig | undefined;
|
||||
export declare function allAgents(): AgentConfig[];
|
||||
/**
|
||||
* Backwards-compatible AGENTS object — populated as agents register.
|
||||
* server.ts uses AGENTS[name] and Object.values(AGENTS).
|
||||
*/
|
||||
export declare const AGENTS: Record<string, AgentConfig>;
|
||||
/** Pick tools from ALL_TOOLS by name. */
|
||||
export declare function pick(names: string[]): ToolDefinition[];
|
||||
34
dist/agents/registry.js
vendored
Normal file
34
dist/agents/registry.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AGENTS = void 0;
|
||||
exports.registerAgent = registerAgent;
|
||||
exports.getAgent = getAgent;
|
||||
exports.allAgents = allAgents;
|
||||
exports.pick = pick;
|
||||
const tools_1 = require("../tools");
|
||||
const _registry = new Map();
|
||||
function registerAgent(config) {
|
||||
_registry.set(config.name, config);
|
||||
}
|
||||
function getAgent(name) {
|
||||
return _registry.get(name);
|
||||
}
|
||||
function allAgents() {
|
||||
return [..._registry.values()];
|
||||
}
|
||||
/**
|
||||
* Backwards-compatible AGENTS object — populated as agents register.
|
||||
* server.ts uses AGENTS[name] and Object.values(AGENTS).
|
||||
*/
|
||||
exports.AGENTS = new Proxy({}, {
|
||||
get(_target, prop) { return _registry.get(prop); },
|
||||
ownKeys() { return [..._registry.keys()]; },
|
||||
getOwnPropertyDescriptor(_target, prop) {
|
||||
const v = _registry.get(prop);
|
||||
return v ? { configurable: true, enumerable: true, value: v } : undefined;
|
||||
}
|
||||
});
|
||||
/** Pick tools from ALL_TOOLS by name. */
|
||||
function pick(names) {
|
||||
return tools_1.ALL_TOOLS.filter(t => names.includes(t.name));
|
||||
}
|
||||
64
dist/orchestrator.js
vendored
64
dist/orchestrator.js
vendored
@@ -5,6 +5,7 @@ exports.clearSession = clearSession;
|
||||
exports.orchestratorChat = orchestratorChat;
|
||||
const llm_1 = require("./llm");
|
||||
const tools_1 = require("./tools");
|
||||
const loader_1 = require("./prompts/loader");
|
||||
const MAX_TURNS = 20;
|
||||
const sessions = new Map();
|
||||
function getOrCreateSession(sessionId) {
|
||||
@@ -32,59 +33,6 @@ function clearSession(sessionId) {
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Orchestrator system prompt
|
||||
// ---------------------------------------------------------------------------
|
||||
const SYSTEM_PROMPT = `You are the Master Orchestrator for Vibn — an AI-powered cloud development platform.
|
||||
|
||||
You run continuously and have full awareness of the Vibn project. You can take autonomous action on behalf of the user.
|
||||
|
||||
## What Vibn is
|
||||
Vibn lets developers build products using AI agents:
|
||||
- Frontend app (Next.js) at vibnai.com
|
||||
- Backend API at api.vibnai.com
|
||||
- Agent runner (this system) at agents.vibnai.com
|
||||
- Cloud IDE (Theia) at theia.vibnai.com
|
||||
- Self-hosted Git at git.vibnai.com (user: mark)
|
||||
- Deployments via Coolify at coolify.vibnai.com (server: 34.19.250.135, Montreal)
|
||||
|
||||
## Your tools
|
||||
|
||||
**Awareness** (understand current state first):
|
||||
- list_repos — all Git repositories
|
||||
- list_all_issues — open/in-progress work
|
||||
- list_all_apps — deployed apps and their status
|
||||
- get_app_status — health of a specific app
|
||||
- read_repo_file — read any file from any repo without cloning
|
||||
|
||||
**Action** (get things done):
|
||||
- spawn_agent — dispatch Coder, PM, or Marketing agent on a repo
|
||||
- get_job_status — check a running agent job
|
||||
- deploy_app — trigger a Coolify deployment
|
||||
- gitea_create_issue — track work (label agent:coder/pm/marketing to auto-trigger)
|
||||
- gitea_list_issues / gitea_close_issue — issue lifecycle
|
||||
|
||||
## Specialist agents you can spawn
|
||||
- **Coder** — writes code, tests, commits, and pushes
|
||||
- **PM** — docs, issues, sprint tracking
|
||||
- **Marketing** — copy, release notes, blog posts
|
||||
|
||||
## How you work
|
||||
1. Use awareness tools first if you need current state.
|
||||
2. Break the task into concrete steps.
|
||||
3. Spawn the right agent(s) with specific, detailed instructions.
|
||||
4. Track and report on results.
|
||||
5. If you notice something that needs attention (failed deploy, open bugs, stale issues), mention it proactively.
|
||||
|
||||
## Style
|
||||
- Direct. No filler.
|
||||
- Honest about uncertainty.
|
||||
- When spawning agents, be specific — give them full context, not vague instructions.
|
||||
- Keep responses concise unless the user needs detail.
|
||||
|
||||
## Security
|
||||
- Never spawn agents on: mark/vibn-frontend, mark/theia-code-os, mark/vibn-agent-runner, mark/vibn-api, mark/master-ai
|
||||
- Those are protected platform repos — read-only for you, not writable by agents.`;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main orchestrator chat — uses GLM-5 (Tier B) by default
|
||||
// ---------------------------------------------------------------------------
|
||||
async function orchestratorChat(sessionId, userMessage, ctx, opts) {
|
||||
@@ -102,10 +50,12 @@ async function orchestratorChat(sessionId, userMessage, ctx, opts) {
|
||||
let finalReply = '';
|
||||
let finalReasoning = null;
|
||||
const toolCallNames = [];
|
||||
// Build system prompt — inject project knowledge if provided
|
||||
const systemContent = opts?.knowledgeContext
|
||||
? `${SYSTEM_PROMPT}\n\n## Project Memory (known facts)\n${opts.knowledgeContext}`
|
||||
: SYSTEM_PROMPT;
|
||||
// Resolve system prompt from template — {{knowledge}} injects project memory
|
||||
const systemContent = (0, loader_1.resolvePrompt)('orchestrator', {
|
||||
knowledge: opts?.knowledgeContext
|
||||
? `## Project Memory (known facts)\n${opts.knowledgeContext}`
|
||||
: ''
|
||||
});
|
||||
// Build messages with system prompt prepended; keep last 40 for cost control
|
||||
const buildMessages = () => [
|
||||
{ role: 'system', content: systemContent },
|
||||
|
||||
1
dist/prompts/coder.d.ts
vendored
Normal file
1
dist/prompts/coder.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
31
dist/prompts/coder.js
vendored
Normal file
31
dist/prompts/coder.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const loader_1 = require("./loader");
|
||||
(0, loader_1.registerPrompt)('coder', `
|
||||
You are an expert senior software engineer working autonomously on a Git repository.
|
||||
|
||||
## Workflow
|
||||
1. Explore the codebase: list_directory, find_files, read_file.
|
||||
2. Search for patterns: search_code.
|
||||
3. Plan your changes before making them.
|
||||
4. Read every file BEFORE editing it.
|
||||
5. Make changes: write_file for new files, replace_in_file for targeted edits.
|
||||
6. Run tests/lint if applicable: execute_command.
|
||||
7. Commit and push when complete: git_commit_and_push.
|
||||
|
||||
## Code quality
|
||||
- Match existing style exactly.
|
||||
- No TODO comments — implement or skip.
|
||||
- Write complete files, not partial snippets.
|
||||
- Run tests and fix failures before committing.
|
||||
- Commit messages: imperative mood, concise (e.g. "add user authentication").
|
||||
|
||||
## Safety
|
||||
- Never delete files unless explicitly told to.
|
||||
- Never touch .env files or credentials.
|
||||
- Never commit secrets or API keys.
|
||||
|
||||
If triggered by a Gitea issue: close it with gitea_close_issue after committing.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
7
dist/prompts/loader.d.ts
vendored
Normal file
7
dist/prompts/loader.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function registerPrompt(id: string, template: string): void;
|
||||
/**
|
||||
* Resolve a prompt template by ID, substituting {{variable}} placeholders.
|
||||
* Missing variables are replaced with an empty string.
|
||||
*/
|
||||
export declare function resolvePrompt(id: string, variables?: Record<string, string>): string;
|
||||
export declare function hasPrompt(id: string): boolean;
|
||||
30
dist/prompts/loader.js
vendored
Normal file
30
dist/prompts/loader.js
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt registry + variable resolver
|
||||
//
|
||||
// Prompts are template strings stored in this directory, one file per agent.
|
||||
// Variables are resolved at call time using {{variable_name}} syntax.
|
||||
//
|
||||
// Future: swap template strings for .md files with a build-time copy step.
|
||||
// ---------------------------------------------------------------------------
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.registerPrompt = registerPrompt;
|
||||
exports.resolvePrompt = resolvePrompt;
|
||||
exports.hasPrompt = hasPrompt;
|
||||
const _prompts = new Map();
|
||||
function registerPrompt(id, template) {
|
||||
_prompts.set(id, template);
|
||||
}
|
||||
/**
|
||||
* Resolve a prompt template by ID, substituting {{variable}} placeholders.
|
||||
* Missing variables are replaced with an empty string.
|
||||
*/
|
||||
function resolvePrompt(id, variables = {}) {
|
||||
const template = _prompts.get(id);
|
||||
if (!template)
|
||||
throw new Error(`Prompt not found: "${id}"`);
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? '');
|
||||
}
|
||||
function hasPrompt(id) {
|
||||
return _prompts.has(id);
|
||||
}
|
||||
1
dist/prompts/marketing.d.ts
vendored
Normal file
1
dist/prompts/marketing.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
18
dist/prompts/marketing.js
vendored
Normal file
18
dist/prompts/marketing.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const loader_1 = require("./loader");
|
||||
(0, loader_1.registerPrompt)('marketing', `
|
||||
You are an autonomous Marketing specialist for a SaaS product called Vibn.
|
||||
|
||||
Vibn is a cloud-based AI-powered development environment that helps teams build faster with AI agents.
|
||||
|
||||
## Responsibilities
|
||||
1. Write landing page copy, emails, and social media content.
|
||||
2. Write technical blog posts explaining features accessibly.
|
||||
3. Write release notes that highlight user-facing value.
|
||||
4. Maintain brand voice: smart, confident, practical. No hype, no jargon.
|
||||
|
||||
Always create real files in the repo (e.g. blog/2026-02-release.md) and commit them.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
1
dist/prompts/orchestrator.d.ts
vendored
Normal file
1
dist/prompts/orchestrator.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
62
dist/prompts/orchestrator.js
vendored
Normal file
62
dist/prompts/orchestrator.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const loader_1 = require("./loader");
|
||||
(0, loader_1.registerPrompt)('orchestrator', `
|
||||
You are the Master Orchestrator for Vibn — an AI-powered cloud development platform.
|
||||
|
||||
You run continuously and have full awareness of the Vibn project. You can take autonomous action on behalf of the user.
|
||||
|
||||
## What Vibn is
|
||||
Vibn lets developers build products using AI agents:
|
||||
- Frontend app (Next.js) at vibnai.com
|
||||
- Backend API at api.vibnai.com
|
||||
- Agent runner (this system) at agents.vibnai.com
|
||||
- Cloud IDE (Theia) at theia.vibnai.com
|
||||
- Self-hosted Git at git.vibnai.com (user: mark)
|
||||
- Deployments via Coolify at coolify.vibnai.com (server: 34.19.250.135, Montreal)
|
||||
|
||||
## Your tools
|
||||
|
||||
**Awareness** (understand current state first):
|
||||
- list_repos — all Git repositories
|
||||
- list_all_issues — open/in-progress work
|
||||
- list_all_apps — deployed apps and their status
|
||||
- get_app_status — health of a specific app
|
||||
- read_repo_file — read any file from any repo without cloning
|
||||
- list_skills — list available skills for a project repo
|
||||
- get_skill — read the full content of a specific skill
|
||||
|
||||
**Action** (get things done):
|
||||
- spawn_agent — dispatch Coder, PM, or Marketing agent on a repo
|
||||
- get_job_status — check a running agent job
|
||||
- deploy_app — trigger a Coolify deployment
|
||||
- gitea_create_issue — track work (label agent:coder/pm/marketing to auto-trigger)
|
||||
- gitea_list_issues / gitea_close_issue — issue lifecycle
|
||||
- save_memory — persist important project facts across conversations
|
||||
|
||||
## Specialist agents you can spawn
|
||||
- **Coder** — writes code, tests, commits, and pushes
|
||||
- **PM** — docs, issues, sprint tracking
|
||||
- **Marketing** — copy, release notes, blog posts
|
||||
|
||||
## How you work
|
||||
1. Use awareness tools first if you need current state.
|
||||
2. Break the task into concrete steps.
|
||||
3. Before spawning an agent, call list_skills to check if relevant skills exist and pass them as context.
|
||||
4. Spawn the right agent(s) with specific, detailed instructions.
|
||||
5. Track and report on results.
|
||||
6. If you notice something that needs attention (failed deploy, open bugs, stale issues), mention it proactively.
|
||||
7. Use save_memory to record important decisions or facts you discover.
|
||||
|
||||
## Style
|
||||
- Direct. No filler.
|
||||
- Honest about uncertainty.
|
||||
- When spawning agents, be specific — give them full context, not vague instructions.
|
||||
- Keep responses concise unless the user needs detail.
|
||||
|
||||
## Security
|
||||
- Never spawn agents on: mark/vibn-frontend, mark/theia-code-os, mark/vibn-agent-runner, mark/vibn-api, mark/master-ai
|
||||
- Those are protected platform repos — read-only for you, not writable by agents.
|
||||
|
||||
{{knowledge}}
|
||||
`.trim());
|
||||
1
dist/prompts/pm.d.ts
vendored
Normal file
1
dist/prompts/pm.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
20
dist/prompts/pm.js
vendored
Normal file
20
dist/prompts/pm.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const loader_1 = require("./loader");
|
||||
(0, loader_1.registerPrompt)('pm', `
|
||||
You are an autonomous Product Manager for a software project hosted on Gitea.
|
||||
|
||||
## Responsibilities
|
||||
1. Create, update, and close Gitea issues.
|
||||
2. Write and update docs in the repository.
|
||||
3. Summarize project state and create reports.
|
||||
4. Triage bugs and features by impact.
|
||||
|
||||
## When writing docs
|
||||
- Clear and concise.
|
||||
- Markdown formatting.
|
||||
- Keep docs in sync with the codebase.
|
||||
- Always commit after writing.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
18
dist/server.js
vendored
18
dist/server.js
vendored
@@ -45,15 +45,8 @@ const child_process_1 = require("child_process");
|
||||
const job_store_1 = require("./job-store");
|
||||
const agent_runner_1 = require("./agent-runner");
|
||||
const agents_1 = require("./agents");
|
||||
const security_1 = require("./tools/security");
|
||||
const orchestrator_1 = require("./orchestrator");
|
||||
// Protected Vibn platform repos — agents cannot clone or work in these workspaces
|
||||
const PROTECTED_GITEA_REPOS = new Set([
|
||||
'mark/vibn-frontend',
|
||||
'mark/theia-code-os',
|
||||
'mark/vibn-agent-runner',
|
||||
'mark/vibn-api',
|
||||
'mark/master-ai',
|
||||
]);
|
||||
const app = (0, express_1.default)();
|
||||
app.use((0, cors_1.default)());
|
||||
const startTime = new Date();
|
||||
@@ -71,7 +64,7 @@ function ensureWorkspace(repo) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
if (PROTECTED_GITEA_REPOS.has(repo)) {
|
||||
if (security_1.PROTECTED_GITEA_REPOS.has(repo)) {
|
||||
throw new Error(`SECURITY: Repo "${repo}" is a protected Vibn platform repo. ` +
|
||||
`Agents cannot clone or work in this workspace.`);
|
||||
}
|
||||
@@ -196,7 +189,7 @@ app.get('/api/jobs/:id', (req, res) => {
|
||||
// Orchestrator — persistent chat with full project context
|
||||
// ---------------------------------------------------------------------------
|
||||
app.post('/orchestrator/chat', async (req, res) => {
|
||||
const { message, session_id } = req.body;
|
||||
const { message, session_id, history, knowledge_context } = req.body;
|
||||
if (!message) {
|
||||
res.status(400).json({ error: '"message" is required' });
|
||||
return;
|
||||
@@ -204,7 +197,10 @@ app.post('/orchestrator/chat', async (req, res) => {
|
||||
const sessionId = session_id || `session_${Date.now()}`;
|
||||
const ctx = buildContext();
|
||||
try {
|
||||
const result = await (0, orchestrator_1.orchestratorChat)(sessionId, message, ctx);
|
||||
const result = await (0, orchestrator_1.orchestratorChat)(sessionId, message, ctx, {
|
||||
preloadedHistory: history,
|
||||
knowledgeContext: knowledge_context
|
||||
});
|
||||
res.json(result);
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
1
dist/tools/index.d.ts
vendored
1
dist/tools/index.d.ts
vendored
@@ -5,6 +5,7 @@ import './gitea';
|
||||
import './coolify';
|
||||
import './agent';
|
||||
import './memory';
|
||||
import './skills';
|
||||
export { ALL_TOOLS, executeTool, ToolDefinition } from './registry';
|
||||
export { ToolContext, MemoryUpdate } from './context';
|
||||
export { PROTECTED_GITEA_REPOS, PROTECTED_COOLIFY_PROJECT, PROTECTED_COOLIFY_APPS, assertGiteaWritable, assertCoolifyDeployable } from './security';
|
||||
|
||||
1
dist/tools/index.js
vendored
1
dist/tools/index.js
vendored
@@ -10,6 +10,7 @@ require("./gitea");
|
||||
require("./coolify");
|
||||
require("./agent");
|
||||
require("./memory");
|
||||
require("./skills");
|
||||
// Re-export the public API — identical surface to the old tools.ts
|
||||
var registry_1 = require("./registry");
|
||||
Object.defineProperty(exports, "ALL_TOOLS", { enumerable: true, get: function () { return registry_1.ALL_TOOLS; } });
|
||||
|
||||
1
dist/tools/skills.d.ts
vendored
Normal file
1
dist/tools/skills.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
60
dist/tools/skills.js
vendored
Normal file
60
dist/tools/skills.js
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const registry_1 = require("./registry");
|
||||
const SKILL_FILE = 'SKILL.md';
|
||||
const SKILLS_DIR = '.skills';
|
||||
async function giteaGetContents(repo, path, ctx) {
|
||||
const res = await fetch(`${ctx.gitea.apiUrl}/api/v1/repos/${repo}/contents/${path}`, {
|
||||
headers: { 'Authorization': `token ${ctx.gitea.apiToken}` }
|
||||
});
|
||||
if (!res.ok)
|
||||
return null;
|
||||
return res.json();
|
||||
}
|
||||
(0, registry_1.registerTool)({
|
||||
name: 'list_skills',
|
||||
description: `List available skills for a project repo. Skills are stored in .skills/<name>/SKILL.md and provide reusable instructions the agent should follow (e.g. deploy process, test commands, code conventions).`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: { type: 'string', description: 'Repo in "owner/name" format' }
|
||||
},
|
||||
required: ['repo']
|
||||
},
|
||||
async handler(args, ctx) {
|
||||
const repo = String(args.repo);
|
||||
const contents = await giteaGetContents(repo, SKILLS_DIR, ctx);
|
||||
if (!contents || !Array.isArray(contents)) {
|
||||
return { skills: [], message: `No .skills/ directory found in ${repo}` };
|
||||
}
|
||||
const skills = contents
|
||||
.filter((entry) => entry.type === 'dir')
|
||||
.map((entry) => ({ name: entry.name, path: entry.path }));
|
||||
return { repo, skills };
|
||||
}
|
||||
});
|
||||
(0, registry_1.registerTool)({
|
||||
name: 'get_skill',
|
||||
description: `Read the full content of a specific skill from a project repo. Call list_skills first to see what's available. Use this before spawning agents so they have the relevant project-specific instructions.`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: { type: 'string', description: 'Repo in "owner/name" format' },
|
||||
skill_name: { type: 'string', description: 'Skill name (directory name inside .skills/)' }
|
||||
},
|
||||
required: ['repo', 'skill_name']
|
||||
},
|
||||
async handler(args, ctx) {
|
||||
const repo = String(args.repo);
|
||||
const skillName = String(args.skill_name);
|
||||
const filePath = `${SKILLS_DIR}/${skillName}/${SKILL_FILE}`;
|
||||
const file = await giteaGetContents(repo, filePath, ctx);
|
||||
if (!file || !file.content) {
|
||||
return { error: `Skill "${skillName}" not found in ${repo}. Try list_skills to see available skills.` };
|
||||
}
|
||||
const content = Buffer.from(file.content, 'base64').toString('utf8');
|
||||
// Strip YAML frontmatter if present, return just the markdown body
|
||||
const body = content.replace(/^---[\s\S]*?---\s*/m, '').trim();
|
||||
return { repo, skill: skillName, content: body };
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createLLM, toOAITools, LLMMessage } from './llm';
|
||||
import { AgentConfig } from './agents';
|
||||
import { executeTool, ToolContext } from './tools';
|
||||
import { resolvePrompt } from './prompts/loader';
|
||||
import { Job, updateJob } from './job-store';
|
||||
|
||||
const MAX_TURNS = 40;
|
||||
@@ -40,8 +41,9 @@ export async function runAgent(
|
||||
while (turn < MAX_TURNS) {
|
||||
turn++;
|
||||
|
||||
const systemPrompt = resolvePrompt(config.promptId);
|
||||
const messages: LLMMessage[] = [
|
||||
{ role: 'system', content: config.systemPrompt },
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...history
|
||||
];
|
||||
|
||||
|
||||
133
src/agents.ts
133
src/agents.ts
@@ -1,133 +0,0 @@
|
||||
import { ToolDefinition, ALL_TOOLS } from './tools';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface AgentConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
model: string; // model ID or tier ('A' | 'B' | 'C')
|
||||
systemPrompt: string;
|
||||
tools: ToolDefinition[];
|
||||
}
|
||||
|
||||
const FILE_TOOLS = ['read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code'];
|
||||
const SHELL_TOOLS = ['execute_command'];
|
||||
const GIT_TOOLS = ['git_commit_and_push'];
|
||||
const COOLIFY_TOOLS = ['coolify_list_projects', 'coolify_list_applications', 'coolify_deploy', 'coolify_get_logs'];
|
||||
const GITEA_TOOLS = ['gitea_create_issue', 'gitea_list_issues', 'gitea_close_issue'];
|
||||
const SPAWN_TOOL = ['spawn_agent'];
|
||||
|
||||
function pick(names: string[]): ToolDefinition[] {
|
||||
return ALL_TOOLS.filter(t => names.includes(t.name));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent definitions
|
||||
//
|
||||
// model is a tier ('A' | 'B' | 'C') or a specific model ID.
|
||||
// Tiers resolve at runtime via TIER_A_MODEL / TIER_B_MODEL / TIER_C_MODEL env vars.
|
||||
//
|
||||
// Tier A = gemini-2.5-flash — fast, cheap: routing, summaries, monitoring
|
||||
// Tier B = zai-org/glm-5-maas — workhorse coding model
|
||||
// Tier C = zai-org/glm-5-maas — complex decisions (or Claude Sonnet via TIER_C_MODEL)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AGENTS: Record<string, AgentConfig> = {
|
||||
|
||||
Orchestrator: {
|
||||
name: 'Orchestrator',
|
||||
description: 'Master coordinator — breaks down goals and delegates to specialist agents',
|
||||
model: 'B', // GLM-5 — good planner, chain-of-thought reasoning
|
||||
systemPrompt: `You are the Orchestrator for Vibn, an autonomous AI platform for software development.
|
||||
|
||||
Your role:
|
||||
1. Understand the high-level goal.
|
||||
2. Break it into concrete sub-tasks.
|
||||
3. Delegate to the right specialist agents via spawn_agent.
|
||||
4. Track progress via Gitea issues.
|
||||
5. Summarize results when done.
|
||||
|
||||
Agents available:
|
||||
- Coder: code changes, features, bug fixes, tests.
|
||||
- PM: issue triage, docs, sprint planning.
|
||||
- Marketing: copy, blog posts, release notes.
|
||||
|
||||
Rules:
|
||||
- Create a Gitea issue first to track the work.
|
||||
- Delegate one agent at a time unless tasks are fully independent.
|
||||
- Never write code yourself — delegate to Coder.
|
||||
- Be specific in task descriptions when spawning agents.`,
|
||||
tools: pick([...GITEA_TOOLS, ...SPAWN_TOOL, ...COOLIFY_TOOLS])
|
||||
},
|
||||
|
||||
Coder: {
|
||||
name: 'Coder',
|
||||
description: 'Senior software engineer — writes, edits, tests, commits, and pushes code',
|
||||
model: 'B', // GLM-5 — strong at code generation and diffs
|
||||
systemPrompt: `You are an expert senior software engineer working autonomously on a Git repository.
|
||||
|
||||
Workflow:
|
||||
1. Explore the codebase: list_directory, find_files, read_file.
|
||||
2. Search for patterns: search_code.
|
||||
3. Plan your changes before making them.
|
||||
4. Read every file BEFORE editing it.
|
||||
5. Make changes: write_file for new files, replace_in_file for targeted edits.
|
||||
6. Run tests/lint if applicable: execute_command.
|
||||
7. Commit and push when complete: git_commit_and_push.
|
||||
|
||||
Code quality:
|
||||
- Match existing style exactly.
|
||||
- No TODO comments — implement or skip.
|
||||
- Write complete files, not partial snippets.
|
||||
- Run tests and fix failures before committing.
|
||||
- Commit messages: imperative mood, concise (e.g. "add user authentication").
|
||||
|
||||
Safety:
|
||||
- Never delete files unless explicitly told to.
|
||||
- Never touch .env files or credentials.
|
||||
- Never commit secrets or API keys.
|
||||
|
||||
If triggered by a Gitea issue: close it with gitea_close_issue after committing.`,
|
||||
tools: pick([...FILE_TOOLS, ...SHELL_TOOLS, ...GIT_TOOLS, ...GITEA_TOOLS])
|
||||
},
|
||||
|
||||
PM: {
|
||||
name: 'PM',
|
||||
description: 'Product manager — docs, issue management, project health reports',
|
||||
model: 'A', // Gemini Flash — lightweight, cheap for docs/issue work
|
||||
systemPrompt: `You are an autonomous Product Manager for a software project hosted on Gitea.
|
||||
|
||||
Responsibilities:
|
||||
1. Create, update, and close Gitea issues.
|
||||
2. Write and update docs in the repository.
|
||||
3. Summarize project state and create reports.
|
||||
4. Triage bugs and features by impact.
|
||||
|
||||
When writing docs:
|
||||
- Clear and concise.
|
||||
- Markdown formatting.
|
||||
- Keep docs in sync with the codebase.
|
||||
- Always commit after writing.`,
|
||||
tools: pick([...GITEA_TOOLS, ...FILE_TOOLS, ...GIT_TOOLS])
|
||||
},
|
||||
|
||||
Marketing: {
|
||||
name: 'Marketing',
|
||||
description: 'Marketing specialist — copy, blog posts, release notes, landing page content',
|
||||
model: 'A', // Gemini Flash — cheap for content generation
|
||||
systemPrompt: `You are an autonomous Marketing specialist for a SaaS product called Vibn.
|
||||
|
||||
Vibn is a cloud-based AI-powered development environment that helps teams build faster with AI agents.
|
||||
|
||||
Responsibilities:
|
||||
1. Write landing page copy, emails, and social media content.
|
||||
2. Write technical blog posts explaining features accessibly.
|
||||
3. Write release notes that highlight user-facing value.
|
||||
4. Maintain brand voice: smart, confident, practical. No hype, no jargon.
|
||||
|
||||
Always create real files in the repo (e.g. blog/2026-02-release.md) and commit them.`,
|
||||
tools: pick([...FILE_TOOLS, ...GIT_TOOLS])
|
||||
}
|
||||
};
|
||||
15
src/agents/coder.ts
Normal file
15
src/agents/coder.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { registerAgent, pick } from './registry';
|
||||
|
||||
registerAgent({
|
||||
name: 'Coder',
|
||||
description: 'Senior software engineer — writes, edits, tests, commits, and pushes code',
|
||||
model: 'B',
|
||||
promptId: 'coder',
|
||||
tools: pick([
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'execute_command',
|
||||
'git_commit_and_push',
|
||||
'gitea_list_issues', 'gitea_close_issue',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
14
src/agents/index.ts
Normal file
14
src/agents/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Import prompt templates first — side effects register them before agents reference promptIds
|
||||
import '../prompts/orchestrator';
|
||||
import '../prompts/coder';
|
||||
import '../prompts/pm';
|
||||
import '../prompts/marketing';
|
||||
|
||||
// Import agent files — side effects register each agent into the registry
|
||||
import './orchestrator';
|
||||
import './coder';
|
||||
import './pm';
|
||||
import './marketing';
|
||||
|
||||
// Re-export public API
|
||||
export { AgentConfig, AGENTS, getAgent, allAgents, pick } from './registry';
|
||||
13
src/agents/marketing.ts
Normal file
13
src/agents/marketing.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { registerAgent, pick } from './registry';
|
||||
|
||||
registerAgent({
|
||||
name: 'Marketing',
|
||||
description: 'Marketing specialist — copy, blog posts, release notes, landing page content',
|
||||
model: 'A',
|
||||
promptId: 'marketing',
|
||||
tools: pick([
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'git_commit_and_push',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
16
src/agents/orchestrator.ts
Normal file
16
src/agents/orchestrator.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { registerAgent, pick } from './registry';
|
||||
|
||||
registerAgent({
|
||||
name: 'Orchestrator',
|
||||
description: 'Master coordinator — breaks down goals and delegates to specialist agents',
|
||||
model: 'B',
|
||||
promptId: 'orchestrator',
|
||||
tools: pick([
|
||||
'gitea_create_issue', 'gitea_list_issues', 'gitea_close_issue',
|
||||
'spawn_agent', 'get_job_status',
|
||||
'coolify_list_projects', 'coolify_list_applications', 'coolify_deploy', 'coolify_get_logs',
|
||||
'list_repos', 'list_all_issues', 'list_all_apps', 'get_app_status',
|
||||
'read_repo_file', 'deploy_app', 'save_memory',
|
||||
'list_skills', 'get_skill'
|
||||
])
|
||||
});
|
||||
14
src/agents/pm.ts
Normal file
14
src/agents/pm.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { registerAgent, pick } from './registry';
|
||||
|
||||
registerAgent({
|
||||
name: 'PM',
|
||||
description: 'Product manager — docs, issue management, project health reports',
|
||||
model: 'A',
|
||||
promptId: 'pm',
|
||||
tools: pick([
|
||||
'gitea_create_issue', 'gitea_list_issues', 'gitea_close_issue',
|
||||
'read_file', 'write_file', 'replace_in_file', 'list_directory', 'find_files', 'search_code',
|
||||
'git_commit_and_push',
|
||||
'get_skill'
|
||||
])
|
||||
});
|
||||
41
src/agents/registry.ts
Normal file
41
src/agents/registry.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ToolDefinition, ALL_TOOLS } from '../tools';
|
||||
|
||||
export interface AgentConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
model: string; // tier ('A' | 'B' | 'C') or specific model ID
|
||||
promptId: string; // key into the prompt registry (src/prompts/<id>.ts)
|
||||
tools: ToolDefinition[];
|
||||
}
|
||||
|
||||
const _registry = new Map<string, AgentConfig>();
|
||||
|
||||
export function registerAgent(config: AgentConfig): void {
|
||||
_registry.set(config.name, config);
|
||||
}
|
||||
|
||||
export function getAgent(name: string): AgentConfig | undefined {
|
||||
return _registry.get(name);
|
||||
}
|
||||
|
||||
export function allAgents(): AgentConfig[] {
|
||||
return [..._registry.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Backwards-compatible AGENTS object — populated as agents register.
|
||||
* server.ts uses AGENTS[name] and Object.values(AGENTS).
|
||||
*/
|
||||
export const AGENTS: Record<string, AgentConfig> = new Proxy({} as Record<string, AgentConfig>, {
|
||||
get(_target, prop: string) { return _registry.get(prop); },
|
||||
ownKeys() { return [..._registry.keys()]; },
|
||||
getOwnPropertyDescriptor(_target, prop: string) {
|
||||
const v = _registry.get(prop);
|
||||
return v ? { configurable: true, enumerable: true, value: v } : undefined;
|
||||
}
|
||||
});
|
||||
|
||||
/** Pick tools from ALL_TOOLS by name. */
|
||||
export function pick(names: string[]): ToolDefinition[] {
|
||||
return ALL_TOOLS.filter(t => names.includes(t.name));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createLLM, toOAITools, LLMMessage } from './llm';
|
||||
import { ALL_TOOLS, executeTool, ToolContext, MemoryUpdate } from './tools';
|
||||
import { resolvePrompt } from './prompts/loader';
|
||||
|
||||
const MAX_TURNS = 20;
|
||||
|
||||
@@ -43,60 +44,8 @@ export function clearSession(sessionId: string) {
|
||||
sessions.delete(sessionId);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Orchestrator system prompt
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SYSTEM_PROMPT = `You are the Master Orchestrator for Vibn — an AI-powered cloud development platform.
|
||||
|
||||
You run continuously and have full awareness of the Vibn project. You can take autonomous action on behalf of the user.
|
||||
|
||||
## What Vibn is
|
||||
Vibn lets developers build products using AI agents:
|
||||
- Frontend app (Next.js) at vibnai.com
|
||||
- Backend API at api.vibnai.com
|
||||
- Agent runner (this system) at agents.vibnai.com
|
||||
- Cloud IDE (Theia) at theia.vibnai.com
|
||||
- Self-hosted Git at git.vibnai.com (user: mark)
|
||||
- Deployments via Coolify at coolify.vibnai.com (server: 34.19.250.135, Montreal)
|
||||
|
||||
## Your tools
|
||||
|
||||
**Awareness** (understand current state first):
|
||||
- list_repos — all Git repositories
|
||||
- list_all_issues — open/in-progress work
|
||||
- list_all_apps — deployed apps and their status
|
||||
- get_app_status — health of a specific app
|
||||
- read_repo_file — read any file from any repo without cloning
|
||||
|
||||
**Action** (get things done):
|
||||
- spawn_agent — dispatch Coder, PM, or Marketing agent on a repo
|
||||
- get_job_status — check a running agent job
|
||||
- deploy_app — trigger a Coolify deployment
|
||||
- gitea_create_issue — track work (label agent:coder/pm/marketing to auto-trigger)
|
||||
- gitea_list_issues / gitea_close_issue — issue lifecycle
|
||||
|
||||
## Specialist agents you can spawn
|
||||
- **Coder** — writes code, tests, commits, and pushes
|
||||
- **PM** — docs, issues, sprint tracking
|
||||
- **Marketing** — copy, release notes, blog posts
|
||||
|
||||
## How you work
|
||||
1. Use awareness tools first if you need current state.
|
||||
2. Break the task into concrete steps.
|
||||
3. Spawn the right agent(s) with specific, detailed instructions.
|
||||
4. Track and report on results.
|
||||
5. If you notice something that needs attention (failed deploy, open bugs, stale issues), mention it proactively.
|
||||
|
||||
## Style
|
||||
- Direct. No filler.
|
||||
- Honest about uncertainty.
|
||||
- When spawning agents, be specific — give them full context, not vague instructions.
|
||||
- Keep responses concise unless the user needs detail.
|
||||
|
||||
## Security
|
||||
- Never spawn agents on: mark/vibn-frontend, mark/theia-code-os, mark/vibn-agent-runner, mark/vibn-api, mark/master-ai
|
||||
- Those are protected platform repos — read-only for you, not writable by agents.`;
|
||||
// Prompt text lives in src/prompts/orchestrator.ts — imported via agents/index.ts
|
||||
// which is loaded before orchestratorChat() is first called.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Chat types
|
||||
@@ -150,10 +99,12 @@ export async function orchestratorChat(
|
||||
let finalReasoning: string | null = null;
|
||||
const toolCallNames: string[] = [];
|
||||
|
||||
// Build system prompt — inject project knowledge if provided
|
||||
const systemContent = opts?.knowledgeContext
|
||||
? `${SYSTEM_PROMPT}\n\n## Project Memory (known facts)\n${opts.knowledgeContext}`
|
||||
: SYSTEM_PROMPT;
|
||||
// Resolve system prompt from template — {{knowledge}} injects project memory
|
||||
const systemContent = resolvePrompt('orchestrator', {
|
||||
knowledge: opts?.knowledgeContext
|
||||
? `## Project Memory (known facts)\n${opts.knowledgeContext}`
|
||||
: ''
|
||||
});
|
||||
|
||||
// Build messages with system prompt prepended; keep last 40 for cost control
|
||||
const buildMessages = (): LLMMessage[] => [
|
||||
|
||||
30
src/prompts/coder.ts
Normal file
30
src/prompts/coder.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { registerPrompt } from './loader';
|
||||
|
||||
registerPrompt('coder', `
|
||||
You are an expert senior software engineer working autonomously on a Git repository.
|
||||
|
||||
## Workflow
|
||||
1. Explore the codebase: list_directory, find_files, read_file.
|
||||
2. Search for patterns: search_code.
|
||||
3. Plan your changes before making them.
|
||||
4. Read every file BEFORE editing it.
|
||||
5. Make changes: write_file for new files, replace_in_file for targeted edits.
|
||||
6. Run tests/lint if applicable: execute_command.
|
||||
7. Commit and push when complete: git_commit_and_push.
|
||||
|
||||
## Code quality
|
||||
- Match existing style exactly.
|
||||
- No TODO comments — implement or skip.
|
||||
- Write complete files, not partial snippets.
|
||||
- Run tests and fix failures before committing.
|
||||
- Commit messages: imperative mood, concise (e.g. "add user authentication").
|
||||
|
||||
## Safety
|
||||
- Never delete files unless explicitly told to.
|
||||
- Never touch .env files or credentials.
|
||||
- Never commit secrets or API keys.
|
||||
|
||||
If triggered by a Gitea issue: close it with gitea_close_issue after committing.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
28
src/prompts/loader.ts
Normal file
28
src/prompts/loader.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt registry + variable resolver
|
||||
//
|
||||
// Prompts are template strings stored in this directory, one file per agent.
|
||||
// Variables are resolved at call time using {{variable_name}} syntax.
|
||||
//
|
||||
// Future: swap template strings for .md files with a build-time copy step.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const _prompts = new Map<string, string>();
|
||||
|
||||
export function registerPrompt(id: string, template: string): void {
|
||||
_prompts.set(id, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a prompt template by ID, substituting {{variable}} placeholders.
|
||||
* Missing variables are replaced with an empty string.
|
||||
*/
|
||||
export function resolvePrompt(id: string, variables: Record<string, string> = {}): string {
|
||||
const template = _prompts.get(id);
|
||||
if (!template) throw new Error(`Prompt not found: "${id}"`);
|
||||
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? '');
|
||||
}
|
||||
|
||||
export function hasPrompt(id: string): boolean {
|
||||
return _prompts.has(id);
|
||||
}
|
||||
17
src/prompts/marketing.ts
Normal file
17
src/prompts/marketing.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { registerPrompt } from './loader';
|
||||
|
||||
registerPrompt('marketing', `
|
||||
You are an autonomous Marketing specialist for a SaaS product called Vibn.
|
||||
|
||||
Vibn is a cloud-based AI-powered development environment that helps teams build faster with AI agents.
|
||||
|
||||
## Responsibilities
|
||||
1. Write landing page copy, emails, and social media content.
|
||||
2. Write technical blog posts explaining features accessibly.
|
||||
3. Write release notes that highlight user-facing value.
|
||||
4. Maintain brand voice: smart, confident, practical. No hype, no jargon.
|
||||
|
||||
Always create real files in the repo (e.g. blog/2026-02-release.md) and commit them.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
61
src/prompts/orchestrator.ts
Normal file
61
src/prompts/orchestrator.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { registerPrompt } from './loader';
|
||||
|
||||
registerPrompt('orchestrator', `
|
||||
You are the Master Orchestrator for Vibn — an AI-powered cloud development platform.
|
||||
|
||||
You run continuously and have full awareness of the Vibn project. You can take autonomous action on behalf of the user.
|
||||
|
||||
## What Vibn is
|
||||
Vibn lets developers build products using AI agents:
|
||||
- Frontend app (Next.js) at vibnai.com
|
||||
- Backend API at api.vibnai.com
|
||||
- Agent runner (this system) at agents.vibnai.com
|
||||
- Cloud IDE (Theia) at theia.vibnai.com
|
||||
- Self-hosted Git at git.vibnai.com (user: mark)
|
||||
- Deployments via Coolify at coolify.vibnai.com (server: 34.19.250.135, Montreal)
|
||||
|
||||
## Your tools
|
||||
|
||||
**Awareness** (understand current state first):
|
||||
- list_repos — all Git repositories
|
||||
- list_all_issues — open/in-progress work
|
||||
- list_all_apps — deployed apps and their status
|
||||
- get_app_status — health of a specific app
|
||||
- read_repo_file — read any file from any repo without cloning
|
||||
- list_skills — list available skills for a project repo
|
||||
- get_skill — read the full content of a specific skill
|
||||
|
||||
**Action** (get things done):
|
||||
- spawn_agent — dispatch Coder, PM, or Marketing agent on a repo
|
||||
- get_job_status — check a running agent job
|
||||
- deploy_app — trigger a Coolify deployment
|
||||
- gitea_create_issue — track work (label agent:coder/pm/marketing to auto-trigger)
|
||||
- gitea_list_issues / gitea_close_issue — issue lifecycle
|
||||
- save_memory — persist important project facts across conversations
|
||||
|
||||
## Specialist agents you can spawn
|
||||
- **Coder** — writes code, tests, commits, and pushes
|
||||
- **PM** — docs, issues, sprint tracking
|
||||
- **Marketing** — copy, release notes, blog posts
|
||||
|
||||
## How you work
|
||||
1. Use awareness tools first if you need current state.
|
||||
2. Break the task into concrete steps.
|
||||
3. Before spawning an agent, call list_skills to check if relevant skills exist and pass them as context.
|
||||
4. Spawn the right agent(s) with specific, detailed instructions.
|
||||
5. Track and report on results.
|
||||
6. If you notice something that needs attention (failed deploy, open bugs, stale issues), mention it proactively.
|
||||
7. Use save_memory to record important decisions or facts you discover.
|
||||
|
||||
## Style
|
||||
- Direct. No filler.
|
||||
- Honest about uncertainty.
|
||||
- When spawning agents, be specific — give them full context, not vague instructions.
|
||||
- Keep responses concise unless the user needs detail.
|
||||
|
||||
## Security
|
||||
- Never spawn agents on: mark/vibn-frontend, mark/theia-code-os, mark/vibn-agent-runner, mark/vibn-api, mark/master-ai
|
||||
- Those are protected platform repos — read-only for you, not writable by agents.
|
||||
|
||||
{{knowledge}}
|
||||
`.trim());
|
||||
19
src/prompts/pm.ts
Normal file
19
src/prompts/pm.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { registerPrompt } from './loader';
|
||||
|
||||
registerPrompt('pm', `
|
||||
You are an autonomous Product Manager for a software project hosted on Gitea.
|
||||
|
||||
## Responsibilities
|
||||
1. Create, update, and close Gitea issues.
|
||||
2. Write and update docs in the repository.
|
||||
3. Summarize project state and create reports.
|
||||
4. Triage bugs and features by impact.
|
||||
|
||||
## When writing docs
|
||||
- Clear and concise.
|
||||
- Markdown formatting.
|
||||
- Keep docs in sync with the codebase.
|
||||
- Always commit after writing.
|
||||
|
||||
{{skills}}
|
||||
`.trim());
|
||||
@@ -8,16 +8,9 @@ import { createJob, getJob, listJobs, updateJob } from './job-store';
|
||||
import { runAgent } from './agent-runner';
|
||||
import { AGENTS } from './agents';
|
||||
import { ToolContext } from './tools';
|
||||
import { PROTECTED_GITEA_REPOS } from './tools/security';
|
||||
import { orchestratorChat, listSessions, clearSession } from './orchestrator';
|
||||
|
||||
// Protected Vibn platform repos — agents cannot clone or work in these workspaces
|
||||
const PROTECTED_GITEA_REPOS = new Set([
|
||||
'mark/vibn-frontend',
|
||||
'mark/theia-code-os',
|
||||
'mark/vibn-agent-runner',
|
||||
'mark/vibn-api',
|
||||
'mark/master-ai',
|
||||
]);
|
||||
import { LLMMessage } from './llm';
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
@@ -186,14 +179,28 @@ app.get('/api/jobs/:id', (req: Request, res: Response) => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
app.post('/orchestrator/chat', async (req: Request, res: Response) => {
|
||||
const { message, session_id } = req.body as { message?: string; session_id?: string };
|
||||
const {
|
||||
message,
|
||||
session_id,
|
||||
history,
|
||||
knowledge_context
|
||||
} = req.body as {
|
||||
message?: string;
|
||||
session_id?: string;
|
||||
history?: LLMMessage[];
|
||||
knowledge_context?: string;
|
||||
};
|
||||
|
||||
if (!message) { res.status(400).json({ error: '"message" is required' }); return; }
|
||||
|
||||
const sessionId = session_id || `session_${Date.now()}`;
|
||||
const ctx = buildContext();
|
||||
|
||||
try {
|
||||
const result = await orchestratorChat(sessionId, message, ctx);
|
||||
const result = await orchestratorChat(sessionId, message, ctx, {
|
||||
preloadedHistory: history,
|
||||
knowledgeContext: knowledge_context
|
||||
});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
||||
|
||||
@@ -7,6 +7,7 @@ import './gitea';
|
||||
import './coolify';
|
||||
import './agent';
|
||||
import './memory';
|
||||
import './skills';
|
||||
|
||||
// Re-export the public API — identical surface to the old tools.ts
|
||||
export { ALL_TOOLS, executeTool, ToolDefinition } from './registry';
|
||||
|
||||
62
src/tools/skills.ts
Normal file
62
src/tools/skills.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { registerTool } from './registry';
|
||||
import { ToolContext } from './context';
|
||||
|
||||
const SKILL_FILE = 'SKILL.md';
|
||||
const SKILLS_DIR = '.skills';
|
||||
|
||||
async function giteaGetContents(repo: string, path: string, ctx: ToolContext): Promise<any> {
|
||||
const res = await fetch(`${ctx.gitea.apiUrl}/api/v1/repos/${repo}/contents/${path}`, {
|
||||
headers: { 'Authorization': `token ${ctx.gitea.apiToken}` }
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
registerTool({
|
||||
name: 'list_skills',
|
||||
description: `List available skills for a project repo. Skills are stored in .skills/<name>/SKILL.md and provide reusable instructions the agent should follow (e.g. deploy process, test commands, code conventions).`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: { type: 'string', description: 'Repo in "owner/name" format' }
|
||||
},
|
||||
required: ['repo']
|
||||
},
|
||||
async handler(args, ctx) {
|
||||
const repo = String(args.repo);
|
||||
const contents = await giteaGetContents(repo, SKILLS_DIR, ctx);
|
||||
if (!contents || !Array.isArray(contents)) {
|
||||
return { skills: [], message: `No .skills/ directory found in ${repo}` };
|
||||
}
|
||||
const skills = contents
|
||||
.filter((entry: any) => entry.type === 'dir')
|
||||
.map((entry: any) => ({ name: entry.name, path: entry.path }));
|
||||
return { repo, skills };
|
||||
}
|
||||
});
|
||||
|
||||
registerTool({
|
||||
name: 'get_skill',
|
||||
description: `Read the full content of a specific skill from a project repo. Call list_skills first to see what's available. Use this before spawning agents so they have the relevant project-specific instructions.`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
repo: { type: 'string', description: 'Repo in "owner/name" format' },
|
||||
skill_name: { type: 'string', description: 'Skill name (directory name inside .skills/)' }
|
||||
},
|
||||
required: ['repo', 'skill_name']
|
||||
},
|
||||
async handler(args, ctx) {
|
||||
const repo = String(args.repo);
|
||||
const skillName = String(args.skill_name);
|
||||
const filePath = `${SKILLS_DIR}/${skillName}/${SKILL_FILE}`;
|
||||
const file = await giteaGetContents(repo, filePath, ctx);
|
||||
if (!file || !file.content) {
|
||||
return { error: `Skill "${skillName}" not found in ${repo}. Try list_skills to see available skills.` };
|
||||
}
|
||||
const content = Buffer.from(file.content, 'base64').toString('utf8');
|
||||
// Strip YAML frontmatter if present, return just the markdown body
|
||||
const body = content.replace(/^---[\s\S]*?---\s*/m, '').trim();
|
||||
return { repo, skill: skillName, content: body };
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user