@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,330 @@
1
+ /**
2
+ * Multi-Agent Workflow Loader — loads multi-agent workflow YAML files
3
+ * from .bahulam/workflows/ and produces API payloads for POST /api/workflows.
4
+ *
5
+ * Multi-agent workflow YAML schema:
6
+ *
7
+ * ```yaml
8
+ * metadata:
9
+ * name: Code Review Pipeline
10
+ * description: Sequential review by specialist agents
11
+ *
12
+ * orchestration:
13
+ * pattern: sequential # sequential | parallel
14
+ * instruction: "Review the PR"
15
+ *
16
+ * agents:
17
+ * - slug: code-explorer
18
+ * label: Explore Codebase
19
+ * config:
20
+ * depth: full
21
+ * - slug: security-reviewer
22
+ * label: Security Audit
23
+ * config: {}
24
+ *
25
+ * edges:
26
+ * - source: trigger
27
+ * target: code-explorer
28
+ * - source: code-explorer
29
+ * target: security-reviewer
30
+ * - source: security-reviewer
31
+ * target: output
32
+ *
33
+ * global_params:
34
+ * repo_url: ""
35
+ * pr_number: ""
36
+ * ```
37
+ *
38
+ * The loader converts this into a graph structure matching the canvas
39
+ * format: { nodes: [...], edges: [...] } with node types trigger/agent/output.
40
+ */
41
+
42
+ import fs from 'fs';
43
+ import path from 'path';
44
+
45
+ /**
46
+ * Load a multi-agent workflow YAML file and return an API-ready payload.
47
+ * @param {string} filePath - absolute path to .yaml file
48
+ * @returns {object} payload ready for POST /api/workflows
49
+ */
50
+ export function loadMultiWorkflowFromFile(filePath) {
51
+ const ext = path.extname(filePath).toLowerCase();
52
+ if (!['.yaml', '.yml'].includes(ext)) {
53
+ throw new Error(`Unsupported workflow format: ${ext}. Use .yaml or .yml`);
54
+ }
55
+
56
+ const content = fs.readFileSync(filePath, 'utf-8');
57
+ const parsed = parseWorkflowYaml(content);
58
+
59
+ if (!parsed) {
60
+ throw new Error('Empty or invalid YAML file');
61
+ }
62
+
63
+ return multiWorkflowToApiPayload(parsed, filePath);
64
+ }
65
+
66
+ /**
67
+ * Convert a parsed multi-agent workflow YAML into the API payload.
68
+ *
69
+ * @param {object} parsed - parsed YAML content
70
+ * @param {string} sourcePath - file path for slug derivation
71
+ * @returns {object} { name, description, graph, global_params, pattern }
72
+ */
73
+ export function multiWorkflowToApiPayload(parsed, sourcePath) {
74
+ const metadata = parsed.metadata || {};
75
+ const orchestration = parsed.orchestration || {};
76
+ const agents = parsed.agents || [];
77
+ const edges = parsed.edges || [];
78
+ const globalParams = parsed.global_params || {};
79
+
80
+ // Validate
81
+ if (!metadata.name && !sourcePath) {
82
+ throw new Error('Workflow must have a name in metadata or a filename');
83
+ }
84
+ if (!Array.isArray(agents) || agents.length === 0) {
85
+ throw new Error('Workflow must define at least one agent in the agents list');
86
+ }
87
+ if (!Array.isArray(edges) || edges.length === 0) {
88
+ throw new Error('Workflow must define at least one edge connecting nodes');
89
+ }
90
+
91
+ const name = metadata.name || path.basename(sourcePath, path.extname(sourcePath));
92
+ const description = metadata.description || '';
93
+
94
+ // Build graph nodes
95
+ // 1. Trigger node
96
+ const nodes = [
97
+ {
98
+ id: 'trigger',
99
+ type: 'trigger',
100
+ position: { x: 0, y: 120 },
101
+ data: {
102
+ label: 'Manual Run',
103
+ icon: 'trigger',
104
+ status: 'idle',
105
+ nodeType: 'trigger',
106
+ triggerType: 'manual',
107
+ },
108
+ },
109
+ ];
110
+
111
+ // 2. Agent nodes from the agents list
112
+ for (const agent of agents) {
113
+ const slug = agent.slug || agent.name || 'agent';
114
+ const label = agent.label || slug;
115
+ // Position agents in a vertical column or horizontal row
116
+ const index = agents.indexOf(agent);
117
+ nodes.push({
118
+ id: slug,
119
+ type: 'agent',
120
+ position: { x: 250, y: 120 + index * 160 },
121
+ data: {
122
+ label,
123
+ subtitle: '',
124
+ icon: 'agent',
125
+ status: 'idle',
126
+ nodeType: 'agent',
127
+ agent_source: 'user',
128
+ agent_slug: slug,
129
+ user_agent_slug: slug,
130
+ model: agent.model || 'auto',
131
+ tools: agent.tools || [],
132
+ config: {
133
+ ...(agent.config || {}),
134
+ agent_slug: slug,
135
+ user_agent_slug: slug,
136
+ },
137
+ },
138
+ });
139
+ }
140
+
141
+ // 3. Output node
142
+ nodes.push({
143
+ id: 'output',
144
+ type: 'output',
145
+ position: { x: 500, y: 120 + (agents.length - 1) * 80 },
146
+ data: {
147
+ label: 'Results',
148
+ icon: 'review',
149
+ status: 'idle',
150
+ nodeType: 'output',
151
+ outputType: 'display',
152
+ },
153
+ });
154
+
155
+ // Build graph edges
156
+ // Use explicit edges from YAML, or auto-wire: trigger → first agent → ... → last agent → output
157
+ let graphEdges;
158
+ if (edges.length > 0) {
159
+ graphEdges = edges.map((e, i) => ({
160
+ id: `e-${i}`,
161
+ source: e.source,
162
+ target: e.target,
163
+ }));
164
+ } else {
165
+ // Auto-wire sequential
166
+ graphEdges = [];
167
+ const agentIds = agents.map(a => a.slug || a.name || 'agent');
168
+ graphEdges.push({ id: 'e-trigger-first', source: 'trigger', target: agentIds[0] });
169
+ for (let i = 0; i < agentIds.length - 1; i++) {
170
+ graphEdges.push({ id: `e-${i}-${i + 1}`, source: agentIds[i], target: agentIds[i + 1] });
171
+ }
172
+ graphEdges.push({ id: 'e-last-output', source: agentIds[agentIds.length - 1], target: 'output' });
173
+ }
174
+
175
+ const graph = {
176
+ nodes,
177
+ edges: graphEdges,
178
+ };
179
+
180
+ const pattern = orchestration.pattern || 'sequential';
181
+
182
+ return {
183
+ name,
184
+ description,
185
+ graph,
186
+ global_params: globalParams,
187
+ orchestration_pattern: pattern,
188
+ pattern,
189
+ };
190
+ }
191
+
192
+ function stripComment(line) {
193
+ let quote = null;
194
+ for (let i = 0; i < line.length; i++) {
195
+ const ch = line[i];
196
+ if ((ch === '"' || ch === "'") && line[i - 1] !== '\\') {
197
+ quote = quote === ch ? null : quote || ch;
198
+ }
199
+ if (ch === '#' && !quote) return line.slice(0, i);
200
+ }
201
+ return line;
202
+ }
203
+
204
+ function parseScalar(value) {
205
+ const raw = stripComment(String(value || '')).trim();
206
+ if (raw === '{}') return {};
207
+ if (raw === '[]') return [];
208
+ if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
209
+ return raw.slice(1, -1);
210
+ }
211
+ if (raw.startsWith('[') && raw.endsWith(']')) {
212
+ return raw.slice(1, -1).split(',').map(s => parseScalar(s)).filter(v => v !== '');
213
+ }
214
+ if (raw === 'true') return true;
215
+ if (raw === 'false') return false;
216
+ if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
217
+ return raw;
218
+ }
219
+
220
+ function assignNested(target, key, value) {
221
+ if (key.includes('.')) {
222
+ const parts = key.split('.');
223
+ let cursor = target;
224
+ for (const part of parts.slice(0, -1)) {
225
+ cursor[part] ||= {};
226
+ cursor = cursor[part];
227
+ }
228
+ cursor[parts[parts.length - 1]] = value;
229
+ return;
230
+ }
231
+ target[key] = value;
232
+ }
233
+
234
+ function parseWorkflowYaml(content) {
235
+ const root = {};
236
+ const lines = String(content || '').split(/\r?\n/);
237
+ let section = null;
238
+ let currentItem = null;
239
+ let currentNested = null;
240
+
241
+ for (const originalLine of lines) {
242
+ if (!originalLine.trim() || originalLine.trimStart().startsWith('#')) continue;
243
+ const line = stripComment(originalLine);
244
+ if (!line.trim()) continue;
245
+
246
+ const top = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
247
+ if (top) {
248
+ const key = top[1];
249
+ const value = top[2].trim();
250
+ section = key;
251
+ currentItem = null;
252
+ currentNested = null;
253
+ if (value) root[key] = parseScalar(value);
254
+ else root[key] = key === 'agents' || key === 'edges' ? [] : {};
255
+ continue;
256
+ }
257
+
258
+ if (!section) continue;
259
+
260
+ const listItem = line.match(/^\s{2}-\s*([^:]+):\s*(.*)$/);
261
+ if (listItem && Array.isArray(root[section])) {
262
+ currentItem = {};
263
+ currentNested = null;
264
+ assignNested(currentItem, listItem[1].trim(), parseScalar(listItem[2]));
265
+ root[section].push(currentItem);
266
+ continue;
267
+ }
268
+
269
+ const listScalar = line.match(/^\s{2}-\s*(.+)$/);
270
+ if (listScalar && Array.isArray(root[section])) {
271
+ root[section].push(parseScalar(listScalar[1]));
272
+ continue;
273
+ }
274
+
275
+ const nestedField = line.match(/^\s{4}([^:]+):\s*(.*)$/);
276
+ if (nestedField && currentNested) {
277
+ assignNested(currentNested, nestedField[1].trim(), parseScalar(nestedField[2]));
278
+ continue;
279
+ }
280
+
281
+ const objectField = line.match(/^\s{2}([^:]+):\s*(.*)$/);
282
+ if (objectField) {
283
+ const key = objectField[1].trim();
284
+ const value = objectField[2].trim();
285
+ if (currentItem) {
286
+ if (!value) {
287
+ currentItem[key] = {};
288
+ currentNested = currentItem[key];
289
+ } else {
290
+ assignNested(currentItem, key, parseScalar(value));
291
+ currentNested = null;
292
+ }
293
+ } else if (typeof root[section] === 'object' && !Array.isArray(root[section])) {
294
+ assignNested(root[section], key, parseScalar(value));
295
+ }
296
+ continue;
297
+ }
298
+ }
299
+
300
+ return root;
301
+ }
302
+
303
+ /**
304
+ * Scan a directory for multi-agent workflow YAML files.
305
+ * @param {string} dir - directory path
306
+ * @returns {Array<{file: string, payload: object}>}
307
+ */
308
+ export function loadMultiWorkflowsFromDir(dir) {
309
+ const results = [];
310
+ try {
311
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
312
+ for (const entry of entries) {
313
+ if (!entry.isFile()) continue;
314
+ const ext = path.extname(entry.name).toLowerCase();
315
+ if (ext !== '.yaml' && ext !== '.yml') continue;
316
+ const filePath = path.join(dir, entry.name);
317
+ try {
318
+ const payload = loadMultiWorkflowFromFile(filePath);
319
+ results.push({ file: filePath, payload });
320
+ } catch (err) {
321
+ if (process.env.DEBUG) {
322
+ console.error(`[multi-workflow-loader] Failed to load ${filePath}: ${err.message}`);
323
+ }
324
+ }
325
+ }
326
+ } catch {
327
+ // Directory does not exist
328
+ }
329
+ return results;
330
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Agent Parser — parses agent definitions from native YAML, JSON, and Markdown.
3
+ *
4
+ * JSON format:
5
+ * {
6
+ * "name": "my-agent",
7
+ * "description": "Does things",
8
+ * "model": "claude-sonnet-4-6",
9
+ * "tools": ["Bash", "Read", "Write"],
10
+ * "hooks": { ... },
11
+ * "prompt": "You are a specialized agent..."
12
+ * }
13
+ *
14
+ * Markdown format (YAML frontmatter):
15
+ * ---
16
+ * name: my-agent
17
+ * description: Does things
18
+ * model: claude-sonnet-4-6
19
+ * tools: [Bash, Read, Write]
20
+ * ---
21
+ * You are a specialized agent...
22
+ */
23
+
24
+ /**
25
+ * Parse an agent definition from file content.
26
+ * @param {string} content - file content
27
+ * @param {string} ext - file extension (.yaml, .yml, .json, or .md)
28
+ * @returns {object} agent definition
29
+ */
30
+ export function parseAgentDefinition(content, ext) {
31
+ if (ext === '.yaml' || ext === '.yml') {
32
+ return parseYamlAgent(content);
33
+ }
34
+ if (ext === '.json') {
35
+ return parseJsonAgent(content);
36
+ }
37
+ if (ext === '.md') {
38
+ return parseMarkdownAgent(content);
39
+ }
40
+ throw new Error(`Unsupported agent format: ${ext}`);
41
+ }
42
+
43
+ function parseJsonAgent(content) {
44
+ const data = JSON.parse(content);
45
+ return normalizeAgent(data);
46
+ }
47
+
48
+ function parseMarkdownAgent(content) {
49
+ const { frontmatter, body } = parseFrontmatter(content);
50
+ return normalizeAgent({ ...frontmatter, prompt: body });
51
+ }
52
+
53
+ function parseYamlAgent(content) {
54
+ const config = parseKeplerSubAgentYaml(content);
55
+ return normalizeAgent({
56
+ raw_config: config,
57
+ apiVersion: config.apiVersion,
58
+ kind: config.kind,
59
+ ...(config.metadata || {}),
60
+ ...(config.agent?.model ? { model: config.agent.model } : {}),
61
+ ...(config.agent?.models ? { models: config.agent.models } : {}),
62
+ max_iterations: config.agent?.max_iterations,
63
+ max_tokens: config.agent?.max_tokens,
64
+ prompt: config.agent?.system_prompt || '',
65
+ tools: config.tools || [],
66
+ });
67
+ }
68
+
69
+ function parseScalar(value) {
70
+ const raw = String(value || '').trim();
71
+ if (raw.startsWith('"') && raw.endsWith('"')) return raw.slice(1, -1);
72
+ if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
73
+ if (raw.startsWith('[') && raw.endsWith(']')) {
74
+ return raw.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
75
+ }
76
+ if (raw === 'true') return true;
77
+ if (raw === 'false') return false;
78
+ if (/^\d+$/.test(raw)) return Number.parseInt(raw, 10);
79
+ return raw;
80
+ }
81
+
82
+ function parseKeplerSubAgentYaml(content) {
83
+ const root = {};
84
+ const lines = String(content || '').split(/\r?\n/);
85
+ let section = root;
86
+ let sectionName = null;
87
+ for (let i = 0; i < lines.length; i++) {
88
+ const line = lines[i];
89
+ if (!line.trim() || line.trimStart().startsWith('#')) continue;
90
+ if (!line.startsWith(' ')) {
91
+ const match = line.match(/^([^:]+):\s*(.*)$/);
92
+ if (!match) continue;
93
+ const key = match[1].trim();
94
+ const value = match[2].trim();
95
+ if (value === '') {
96
+ root[key] = key === 'tools' ? [] : {};
97
+ section = root[key];
98
+ sectionName = key;
99
+ } else {
100
+ root[key] = parseScalar(value);
101
+ section = root;
102
+ sectionName = null;
103
+ }
104
+ continue;
105
+ }
106
+
107
+ if (sectionName === 'tools') {
108
+ const item = line.trim().match(/^-\s*(.+)$/);
109
+ if (item) section.push(parseScalar(item[1]));
110
+ continue;
111
+ }
112
+
113
+ const nested = line.match(/^\s{2}([^:]+):\s*(.*)$/);
114
+ if (!nested || typeof section !== 'object' || Array.isArray(section)) continue;
115
+ const key = nested[1].trim();
116
+ const value = nested[2].trim();
117
+ if (value === '|') {
118
+ const block = [];
119
+ const baseIndent = (lines[i + 1]?.match(/^(\s*)/)?.[1]?.length ?? 4);
120
+ while (i + 1 < lines.length) {
121
+ const next = lines[i + 1];
122
+ if (!next.trim()) {
123
+ block.push('');
124
+ i++;
125
+ continue;
126
+ }
127
+ const indent = next.match(/^(\s*)/)?.[1]?.length ?? 0;
128
+ if (indent < baseIndent) break;
129
+ block.push(next.slice(baseIndent));
130
+ i++;
131
+ }
132
+ section[key] = block.join('\n').trimEnd();
133
+ } else if (value === '') {
134
+ section[key] = {};
135
+ } else {
136
+ section[key] = parseScalar(value);
137
+ }
138
+ }
139
+ return root;
140
+ }
141
+
142
+ /**
143
+ * Parse YAML-like frontmatter from markdown.
144
+ * Simple key-value parser (no full YAML dependency).
145
+ */
146
+ function parseFrontmatter(content) {
147
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
148
+ if (!match) {
149
+ return { frontmatter: {}, body: content };
150
+ }
151
+
152
+ const yamlBlock = match[1];
153
+ const body = match[2].trim();
154
+ const frontmatter = {};
155
+
156
+ for (const line of yamlBlock.split('\n')) {
157
+ const colonIdx = line.indexOf(':');
158
+ if (colonIdx === -1) continue;
159
+
160
+ const key = line.slice(0, colonIdx).trim();
161
+ let value = line.slice(colonIdx + 1).trim();
162
+
163
+ // Parse arrays: [a, b, c]
164
+ if (value.startsWith('[') && value.endsWith(']')) {
165
+ value = value.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
166
+ }
167
+ // Parse booleans
168
+ else if (value === 'true') value = true;
169
+ else if (value === 'false') value = false;
170
+ // Parse numbers
171
+ else if (/^\d+$/.test(value)) value = parseInt(value, 10);
172
+
173
+ frontmatter[key] = value;
174
+ }
175
+
176
+ return { frontmatter, body };
177
+ }
178
+
179
+ function normalizeAgent(data) {
180
+ const config = data.raw_config || null;
181
+ const metadata = config?.metadata || {};
182
+ const agent = config?.agent || {};
183
+ return {
184
+ slug: data.slug || data.id || metadata.slug || metadata.name || data.name || 'unnamed',
185
+ id: data.id || data.slug || metadata.slug || metadata.name || data.name || 'unnamed',
186
+ name: data.name || metadata.name || 'unnamed',
187
+ description: data.description || metadata.description || '',
188
+ role: data.role || metadata.role || 'specialist',
189
+ model: data.model || agent.model || null,
190
+ models: data.models && typeof data.models === 'object' ? data.models : {},
191
+ tools: Array.isArray(data.tools) ? data.tools : Array.isArray(config?.tools) ? config.tools : [],
192
+ capabilities: Array.isArray(data.capabilities) ? data.capabilities : Array.isArray(metadata.capabilities) ? metadata.capabilities : [],
193
+ domains: Array.isArray(data.domains) ? data.domains : Array.isArray(metadata.domains) ? metadata.domains : [],
194
+ hooks: data.hooks || {},
195
+ prompt: data.prompt || agent.system_prompt || '',
196
+ maxTokens: data.max_tokens || agent.max_tokens || 4096,
197
+ maxTurns: data.maxTurns || data.max_turns || data.max_iterations || agent.max_iterations || 10,
198
+ max_iterations: data.max_iterations || data.maxTurns || data.max_turns || agent.max_iterations || 10,
199
+ can_delegate: data.can_delegate ?? false,
200
+ can_be_delegated_to: data.can_be_delegated_to ?? true,
201
+ temperature: data.temperature || undefined,
202
+ raw_config: config,
203
+ config,
204
+ };
205
+ }