@bahulam/code 0.1.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 +93 -0
- package/package.json +56 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +223 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +312 -0
- package/src/commands/agent.mjs +221 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +202 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/model-catalog.mjs +57 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/prose-chunker.mjs +255 -0
- package/src/context/retriever.mjs +425 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +145 -0
- package/src/core/approval.mjs +700 -0
- package/src/core/attachments.mjs +666 -0
- package/src/core/backend-url.mjs +68 -0
- package/src/core/bundled-runtime.mjs +418 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +331 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +460 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +538 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +388 -0
- package/src/core/safety.mjs +260 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +957 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +2231 -0
- package/src/core/trust.mjs +160 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +374 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +700 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +122 -0
- package/src/terminal/agents.mjs +353 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +310 -0
- package/src/terminal/repl-ask-form.mjs +120 -0
- package/src/terminal/repl-explore.mjs +44 -0
- package/src/terminal/repl-format.mjs +317 -0
- package/src/terminal/repl-model-form.mjs +132 -0
- package/src/terminal/repl-render.mjs +833 -0
- package/src/terminal/repl-resume.mjs +640 -0
- package/src/terminal/repl-state.mjs +120 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +5032 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +392 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +703 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +510 -0
- package/src/ui/banner.mjs +232 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +170 -0
- package/src/ui/input-dock.mjs +772 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/render-queue.mjs +500 -0
- package/src/ui/slash-commands.mjs +257 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +167 -0
- package/src/ui/term.mjs +174 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +740 -0
- package/src/ui/tool-details.mjs +504 -0
- package/src/ui/transcript-block.mjs +20 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { AgentLoader } from './loader.mjs';
|
|
6
|
+
|
|
7
|
+
export const AGENT_SYNC_ENDPOINT = '/api/user/agents/sync';
|
|
8
|
+
|
|
9
|
+
export function slugifyAgentName(value) {
|
|
10
|
+
return String(value || 'agent')
|
|
11
|
+
.trim()
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
14
|
+
.replace(/^-+|-+$/g, '')
|
|
15
|
+
|| 'agent';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function agentContentHash(agent) {
|
|
19
|
+
const body = JSON.stringify(agentToSpec(agent));
|
|
20
|
+
return crypto.createHash('sha256').update(body).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function agentToSpec(agent) {
|
|
24
|
+
const slug = slugifyAgentName(agent.slug || agent.id || agent.name);
|
|
25
|
+
const config = agent.raw_config || {
|
|
26
|
+
apiVersion: 'agent.framework/v1',
|
|
27
|
+
kind: 'SubAgent',
|
|
28
|
+
metadata: {
|
|
29
|
+
name: agent.name || slug,
|
|
30
|
+
role: agent.role || 'specialist',
|
|
31
|
+
description: agent.description || '',
|
|
32
|
+
capabilities: Array.isArray(agent.capabilities) ? agent.capabilities : [],
|
|
33
|
+
domains: Array.isArray(agent.domains) ? agent.domains : [],
|
|
34
|
+
},
|
|
35
|
+
agent: {
|
|
36
|
+
...(agent.model ? { model: agent.model } : {}),
|
|
37
|
+
...(agent.maxTokens ? { max_tokens: agent.maxTokens } : { max_tokens: 4096 }),
|
|
38
|
+
max_iterations: agent.max_iterations || agent.maxTurns || 10,
|
|
39
|
+
system_prompt: agent.prompt || agent.system_prompt || '',
|
|
40
|
+
},
|
|
41
|
+
tools: Array.isArray(agent.tools) ? agent.tools : [],
|
|
42
|
+
};
|
|
43
|
+
const spec = {
|
|
44
|
+
id: slug,
|
|
45
|
+
slug,
|
|
46
|
+
name: agent.name || slug,
|
|
47
|
+
description: agent.description || '',
|
|
48
|
+
role: agent.role || 'specialist',
|
|
49
|
+
model: agent.model || undefined,
|
|
50
|
+
models: agent.models && Object.keys(agent.models).length ? agent.models : undefined,
|
|
51
|
+
tools: Array.isArray(agent.tools) ? agent.tools : [],
|
|
52
|
+
capabilities: Array.isArray(agent.capabilities) ? agent.capabilities : [],
|
|
53
|
+
domains: Array.isArray(agent.domains) ? agent.domains : [],
|
|
54
|
+
system_prompt: agent.prompt || agent.system_prompt || '',
|
|
55
|
+
config,
|
|
56
|
+
max_iterations: agent.max_iterations || agent.maxTurns || 10,
|
|
57
|
+
can_delegate: agent.can_delegate ?? false,
|
|
58
|
+
can_be_delegated_to: agent.can_be_delegated_to ?? true,
|
|
59
|
+
source: 'cli',
|
|
60
|
+
};
|
|
61
|
+
return Object.fromEntries(Object.entries(spec).filter(([, value]) => value !== undefined));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function listLocalAgents(cwd = process.cwd()) {
|
|
65
|
+
return new AgentLoader().load(cwd).list().map(agent => {
|
|
66
|
+
const spec = agentToSpec(agent);
|
|
67
|
+
return {
|
|
68
|
+
...agent,
|
|
69
|
+
slug: spec.slug,
|
|
70
|
+
spec,
|
|
71
|
+
content_hash: agentContentHash(agent),
|
|
72
|
+
source_scope: String(agent.source || '').includes(`${path.sep}.bahulam${path.sep}agents${path.sep}`)
|
|
73
|
+
? (String(agent.source).startsWith(path.join(cwd, '.bahulam')) ? 'project' : 'global')
|
|
74
|
+
: 'unknown',
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function splitCommand(value) {
|
|
80
|
+
return String(value || '')
|
|
81
|
+
.match(/"[^"]+"|'[^']+'|\S+/g)
|
|
82
|
+
?.map(part => part.replace(/^["']|["']$/g, '')) || [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function commandExists(command, env = process.env) {
|
|
86
|
+
if (!command) return false;
|
|
87
|
+
if (command.includes(path.sep)) return fs.existsSync(command);
|
|
88
|
+
|
|
89
|
+
const pathValue = env.PATH || '';
|
|
90
|
+
const extensions = process.platform === 'win32'
|
|
91
|
+
? ['', '.cmd', '.exe', '.bat']
|
|
92
|
+
: [''];
|
|
93
|
+
return pathValue.split(path.delimiter).some(dir => (
|
|
94
|
+
extensions.some(ext => fs.existsSync(path.join(dir, `${command}${ext}`)))
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function isVsCodeTerminal(env = process.env) {
|
|
99
|
+
return env.TERM_PROGRAM === 'vscode' || Boolean(env.VSCODE_PID);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveAgentEditor({
|
|
103
|
+
env = process.env,
|
|
104
|
+
allowConfiguredEditor = true,
|
|
105
|
+
} = {}) {
|
|
106
|
+
if (isVsCodeTerminal(env) && commandExists('code', env)) {
|
|
107
|
+
return { command: 'code', args: ['-r'], label: 'VS Code' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!allowConfiguredEditor) return null;
|
|
111
|
+
const configured = env.VISUAL || env.EDITOR || '';
|
|
112
|
+
const [command, ...args] = splitCommand(configured);
|
|
113
|
+
if (command && commandExists(command, env)) {
|
|
114
|
+
return { command, args, label: command };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function openAgentFile(filePath, {
|
|
120
|
+
env = process.env,
|
|
121
|
+
allowConfiguredEditor = true,
|
|
122
|
+
} = {}) {
|
|
123
|
+
const editor = resolveAgentEditor({ env, allowConfiguredEditor });
|
|
124
|
+
if (!editor) {
|
|
125
|
+
return {
|
|
126
|
+
opened: false,
|
|
127
|
+
reason: isVsCodeTerminal(env)
|
|
128
|
+
? 'VS Code terminal detected, but the code command is unavailable.'
|
|
129
|
+
: 'No editor command found. Set VISUAL or EDITOR, or open the file manually.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const child = spawn(editor.command, [...editor.args, filePath], {
|
|
134
|
+
detached: true,
|
|
135
|
+
stdio: 'ignore',
|
|
136
|
+
env,
|
|
137
|
+
});
|
|
138
|
+
child.on('error', () => {});
|
|
139
|
+
child.unref();
|
|
140
|
+
return { opened: true, editor: editor.label };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createAgentFile({
|
|
144
|
+
cwd = process.cwd(),
|
|
145
|
+
name,
|
|
146
|
+
description = '',
|
|
147
|
+
role = 'specialist',
|
|
148
|
+
model = '',
|
|
149
|
+
tools = [],
|
|
150
|
+
prompt = '',
|
|
151
|
+
force = false,
|
|
152
|
+
} = {}) {
|
|
153
|
+
const slug = slugifyAgentName(name);
|
|
154
|
+
const dir = path.join(cwd, '.bahulam', 'agents');
|
|
155
|
+
const filePath = path.join(dir, `${slug}.yaml`);
|
|
156
|
+
if (fs.existsSync(filePath) && !force) {
|
|
157
|
+
throw new Error(`Agent already exists: ${filePath}`);
|
|
158
|
+
}
|
|
159
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
160
|
+
const toolList = Array.isArray(tools) ? tools : String(tools || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
161
|
+
const body = prompt || `You are ${name}, a project-local Bahulam Code sub-agent.\n\nFocus on the assigned task and return a concise handoff with evidence.`;
|
|
162
|
+
const indentedPrompt = body.trim().split('\n').map(line => ` ${line}`).join('\n');
|
|
163
|
+
const lines = [
|
|
164
|
+
'apiVersion: agent.framework/v1',
|
|
165
|
+
'kind: SubAgent',
|
|
166
|
+
'metadata:',
|
|
167
|
+
` name: ${name}`,
|
|
168
|
+
` role: ${role}`,
|
|
169
|
+
` description: ${description || `${name} project agent`}`,
|
|
170
|
+
'agent:',
|
|
171
|
+
' max_tokens: 4096',
|
|
172
|
+
' max_iterations: 10',
|
|
173
|
+
...(model ? [` model: ${model}`] : []),
|
|
174
|
+
' system_prompt: |',
|
|
175
|
+
indentedPrompt,
|
|
176
|
+
'tools:',
|
|
177
|
+
...toolList.map(tool => ` - ${tool}`),
|
|
178
|
+
'',
|
|
179
|
+
];
|
|
180
|
+
fs.writeFileSync(filePath, lines.join('\n'), { mode: 0o600 });
|
|
181
|
+
return { slug, filePath };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function syncAgentsToBackend({
|
|
185
|
+
backendUrl,
|
|
186
|
+
token,
|
|
187
|
+
agents,
|
|
188
|
+
timeoutMs = 15_000,
|
|
189
|
+
} = {}) {
|
|
190
|
+
if (!backendUrl) throw new Error('Missing backend URL');
|
|
191
|
+
if (!token) throw new Error('Not logged in. Run /login first.');
|
|
192
|
+
const payload = {
|
|
193
|
+
agents: agents.map(agent => ({
|
|
194
|
+
slug: agent.slug,
|
|
195
|
+
name: agent.name,
|
|
196
|
+
description: agent.description || '',
|
|
197
|
+
source: agent.source_scope || 'cli',
|
|
198
|
+
spec: agent.spec || agentToSpec(agent),
|
|
199
|
+
content_hash: agent.content_hash || agentContentHash(agent),
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
const resp = await fetch(`${backendUrl}${AGENT_SYNC_ENDPOINT}`, {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers: {
|
|
205
|
+
Authorization: `Bearer ${token}`,
|
|
206
|
+
'Content-Type': 'application/json',
|
|
207
|
+
'X-Product': 'bahulam',
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify(payload),
|
|
210
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
211
|
+
});
|
|
212
|
+
if (!resp.ok) {
|
|
213
|
+
let detail = '';
|
|
214
|
+
try {
|
|
215
|
+
const data = await resp.json();
|
|
216
|
+
detail = data.detail || data.error || JSON.stringify(data);
|
|
217
|
+
} catch {
|
|
218
|
+
detail = await resp.text().catch(() => '');
|
|
219
|
+
}
|
|
220
|
+
throw new Error(`Agent sync failed (${resp.status})${detail ? `: ${detail}` : ''}`);
|
|
221
|
+
}
|
|
222
|
+
return await resp.json();
|
|
223
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Teams — multi-agent coordination.
|
|
3
|
+
*
|
|
4
|
+
* Allows registering named agents that can communicate via messages.
|
|
5
|
+
* Each teammate is an agent loop that can be invoked with a prompt.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class AgentTeams {
|
|
9
|
+
constructor() {
|
|
10
|
+
/** @type {Map<string, { loop: object, role?: string, status: string }>} */
|
|
11
|
+
this.teammates = new Map();
|
|
12
|
+
this.messageLog = [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a named agent.
|
|
17
|
+
* @param {string} name - unique agent name
|
|
18
|
+
* @param {object} agentLoop - agent loop with .run() async generator
|
|
19
|
+
* @param {object} [options]
|
|
20
|
+
* @param {string} [options.role] - agent role description
|
|
21
|
+
*/
|
|
22
|
+
register(name, agentLoop, options = {}) {
|
|
23
|
+
if (this.teammates.has(name)) {
|
|
24
|
+
throw new Error(`Agent "${name}" is already registered`);
|
|
25
|
+
}
|
|
26
|
+
this.teammates.set(name, {
|
|
27
|
+
loop: agentLoop,
|
|
28
|
+
role: options.role || 'general',
|
|
29
|
+
status: 'idle',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unregister an agent.
|
|
35
|
+
* @param {string} name
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
unregister(name) {
|
|
39
|
+
return this.teammates.delete(name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Send a message to a teammate and collect all events.
|
|
44
|
+
* @param {string} to - target agent name
|
|
45
|
+
* @param {string} message - prompt to send
|
|
46
|
+
* @returns {Promise<Array<object>>} collected events
|
|
47
|
+
*/
|
|
48
|
+
async sendMessage(to, message) {
|
|
49
|
+
const agent = this.teammates.get(to);
|
|
50
|
+
if (!agent) throw new Error(`Unknown teammate: ${to}`);
|
|
51
|
+
|
|
52
|
+
agent.status = 'running';
|
|
53
|
+
const results = [];
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
for await (const event of agent.loop.run(message)) {
|
|
57
|
+
results.push(event);
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
agent.status = 'idle';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.messageLog.push({
|
|
64
|
+
to,
|
|
65
|
+
message: message.substring(0, 100),
|
|
66
|
+
resultCount: results.length,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Broadcast a message to all teammates.
|
|
75
|
+
* @param {string} message
|
|
76
|
+
* @returns {Promise<Map<string, Array<object>>>} results per agent
|
|
77
|
+
*/
|
|
78
|
+
async broadcast(message) {
|
|
79
|
+
const results = new Map();
|
|
80
|
+
const promises = [];
|
|
81
|
+
|
|
82
|
+
for (const [name] of this.teammates) {
|
|
83
|
+
promises.push(
|
|
84
|
+
this.sendMessage(name, message)
|
|
85
|
+
.then(events => results.set(name, events))
|
|
86
|
+
.catch(err => results.set(name, [{ type: 'error', message: err.message }]))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await Promise.all(promises);
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* List all registered teammates.
|
|
96
|
+
* @returns {Array<{ name: string, role: string, status: string }>}
|
|
97
|
+
*/
|
|
98
|
+
list() {
|
|
99
|
+
return [...this.teammates.entries()].map(([name, info]) => ({
|
|
100
|
+
name,
|
|
101
|
+
role: info.role,
|
|
102
|
+
status: info.status,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the message log.
|
|
108
|
+
* @param {number} [limit] - max entries to return
|
|
109
|
+
* @returns {Array<object>}
|
|
110
|
+
*/
|
|
111
|
+
getMessageLog(limit) {
|
|
112
|
+
if (limit) return this.messageLog.slice(-limit);
|
|
113
|
+
return [...this.messageLog];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get count of registered teammates.
|
|
118
|
+
* @returns {number}
|
|
119
|
+
*/
|
|
120
|
+
size() {
|
|
121
|
+
return this.teammates.size;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Loader — loads single-agent workflow YAML files from .bahulam/workflows/
|
|
3
|
+
*
|
|
4
|
+
* Workflow YAML files follow the same structure as Kepler SubAgent YAML:
|
|
5
|
+
* metadata: { id, name, description, icon, tags }
|
|
6
|
+
* agent: { system_prompt, model, max_iterations }
|
|
7
|
+
* tools: { include: [...], exclude: [...] }
|
|
8
|
+
* params: { ... }
|
|
9
|
+
* channel: "server"
|
|
10
|
+
*
|
|
11
|
+
* These are submitted to POST /api/templates (backend object = template).
|
|
12
|
+
* The user-facing term is "workflow" everywhere.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { parseAgentDefinition } from './parser.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load a single workflow YAML file and return an API-ready payload.
|
|
21
|
+
* @param {string} filePath - absolute path to .yaml file
|
|
22
|
+
* @returns {object} payload ready for POST /api/templates
|
|
23
|
+
*/
|
|
24
|
+
export function loadWorkflowFromFile(filePath) {
|
|
25
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
26
|
+
if (!['.yaml', '.yml'].includes(ext)) {
|
|
27
|
+
throw new Error(`Unsupported workflow format: ${ext}. Use .yaml or .yml`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
31
|
+
const parsed = parseAgentDefinition(content, ext);
|
|
32
|
+
|
|
33
|
+
return workflowToTemplatePayload(parsed, filePath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Scan a directory for .yaml/.yml files and load each one.
|
|
38
|
+
* @param {string} dir - directory path
|
|
39
|
+
* @returns {Array<{file: string, payload: object}>}
|
|
40
|
+
*/
|
|
41
|
+
export function loadWorkflowsFromDir(dir) {
|
|
42
|
+
const results = [];
|
|
43
|
+
try {
|
|
44
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (!entry.isFile()) continue;
|
|
47
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
48
|
+
if (ext !== '.yaml' && ext !== '.yml') continue;
|
|
49
|
+
const filePath = path.join(dir, entry.name);
|
|
50
|
+
try {
|
|
51
|
+
const payload = loadWorkflowFromFile(filePath);
|
|
52
|
+
results.push({ file: filePath, payload });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (process.env.DEBUG) {
|
|
55
|
+
console.error(`[workflow-loader] Failed to load ${filePath}: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Directory does not exist
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert a parsed agent definition into the API payload expected by POST /api/templates.
|
|
67
|
+
*
|
|
68
|
+
* The parsed agent from parseAgentDefinition() has this shape:
|
|
69
|
+
* { name, description, raw_config: { metadata, agent, tools, params, channel }, ... }
|
|
70
|
+
*
|
|
71
|
+
* The API expects:
|
|
72
|
+
* { slug, name, description, category, tags, icon, system_prompt, model,
|
|
73
|
+
* max_iterations, tools_include, tools_exclude, params, channel }
|
|
74
|
+
*
|
|
75
|
+
* @param {object} agent - parsed agent definition
|
|
76
|
+
* @param {string} [sourcePath] - optional file path for slug derivation
|
|
77
|
+
* @returns {object} API payload
|
|
78
|
+
*/
|
|
79
|
+
export function workflowToTemplatePayload(agent, sourcePath) {
|
|
80
|
+
const raw = agent.raw_config || {};
|
|
81
|
+
const metadata = raw.metadata || {};
|
|
82
|
+
const agentConfig = raw.agent || {};
|
|
83
|
+
|
|
84
|
+
// Derive slug: metadata.id > filename stem > agent name
|
|
85
|
+
let slug = metadata.id || '';
|
|
86
|
+
if (!slug && sourcePath) {
|
|
87
|
+
slug = path.basename(sourcePath, path.extname(sourcePath));
|
|
88
|
+
}
|
|
89
|
+
if (!slug) {
|
|
90
|
+
slug = (agent.name || 'unnamed').toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Tools: include/exclude
|
|
94
|
+
const tools = raw.tools || {};
|
|
95
|
+
let toolsInclude = [];
|
|
96
|
+
let toolsExclude = [];
|
|
97
|
+
if (Array.isArray(tools)) {
|
|
98
|
+
toolsInclude = tools;
|
|
99
|
+
} else {
|
|
100
|
+
toolsInclude = tools.include || [];
|
|
101
|
+
toolsExclude = tools.exclude || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Params
|
|
105
|
+
const params = raw.params || {};
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
slug,
|
|
109
|
+
name: metadata.name || agent.name || slug,
|
|
110
|
+
description: metadata.description || agent.description || '',
|
|
111
|
+
category: metadata.category || 'custom',
|
|
112
|
+
tags: metadata.tags || [],
|
|
113
|
+
icon: metadata.icon || 'code',
|
|
114
|
+
system_prompt: agentConfig.system_prompt || agent.prompt || '',
|
|
115
|
+
model: agentConfig.model || agent.model || null,
|
|
116
|
+
max_iterations: agentConfig.max_iterations || agent.max_iterations || 20,
|
|
117
|
+
tools_include: toolsInclude,
|
|
118
|
+
tools_exclude: toolsExclude,
|
|
119
|
+
params,
|
|
120
|
+
channel: raw.channel || 'server',
|
|
121
|
+
};
|
|
122
|
+
}
|