@bahulam/code 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +222 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,222 @@
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
+ },
208
+ body: JSON.stringify(payload),
209
+ signal: AbortSignal.timeout(timeoutMs),
210
+ });
211
+ if (!resp.ok) {
212
+ let detail = '';
213
+ try {
214
+ const data = await resp.json();
215
+ detail = data.detail || data.error || JSON.stringify(data);
216
+ } catch {
217
+ detail = await resp.text().catch(() => '');
218
+ }
219
+ throw new Error(`Agent sync failed (${resp.status})${detail ? `: ${detail}` : ''}`);
220
+ }
221
+ return await resp.json();
222
+ }
@@ -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
+ }