@bahulam/code 0.1.15 → 0.1.17

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.
@@ -76,7 +76,28 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
76
76
  const { dispatch } = await import('../orchestration/dispatch.mjs');
77
77
  const { listLocalWorkflows } = await import('../agents/workflow_scaffold.mjs');
78
78
  const pluginRegistry = new PluginRegistry().scan();
79
- const toolExecutor = createToolExecutor({ pluginRegistry });
79
+ let toolExecutor = null;
80
+ const runDelegateFromTool = async ({ agent: targetAgent, slug, instruction: task, options = {} }) => {
81
+ const outcome = await dispatch({
82
+ type: 'invoke',
83
+ source: 'tool:delegate',
84
+ target: { kind: 'agent', slug: slug || targetAgent?.slug, agent: targetAgent },
85
+ params: { instruction: task || '' },
86
+ channel: 'local',
87
+ substrate: 'direct',
88
+ signal: options.signal,
89
+ }, {
90
+ toolExecutor,
91
+ listRunnables: () => toolExecutor.listRunnables(),
92
+ listLocalWorkflows: () => listLocalWorkflows(process.cwd()),
93
+ renderEvent: (event) => emit({ type: event.type, ...event.data }),
94
+ credentials: { apiKey: anthKey, openRouterKey: orKey },
95
+ defaultModel: model || null,
96
+ cwd: process.cwd(),
97
+ });
98
+ return outcome;
99
+ };
100
+ toolExecutor = createToolExecutor({ pluginRegistry, delegateRunner: runDelegateFromTool });
80
101
  const timer = setTimeout(() => {
81
102
  emit({ type: 'timeout', duration_s: timeout });
82
103
  process.exit(2);
@@ -121,7 +142,30 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
121
142
  const pluginRegistry = new PluginRegistry().scan();
122
143
 
123
144
  // Projects are registered and indexed only when the agent requests an overview.
124
- const toolExecutor = createToolExecutor({ pluginRegistry });
145
+ let toolExecutor = null;
146
+ const runDelegateFromTool = async ({ agent: targetAgent, slug, instruction: task, options = {} }) => {
147
+ const { dispatch } = await import('../orchestration/dispatch.mjs');
148
+ const { listLocalWorkflows } = await import('../agents/workflow_scaffold.mjs');
149
+ const outcome = await dispatch({
150
+ type: 'invoke',
151
+ source: 'tool:delegate',
152
+ target: { kind: 'agent', slug: slug || targetAgent?.slug, agent: targetAgent },
153
+ params: { instruction: task || '' },
154
+ channel: 'local',
155
+ substrate: 'direct',
156
+ signal: options.signal,
157
+ }, {
158
+ toolExecutor,
159
+ listRunnables: () => toolExecutor.listRunnables(),
160
+ listLocalWorkflows: () => listLocalWorkflows(process.cwd()),
161
+ renderEvent: (event) => emit({ type: event.type, ...event.data }),
162
+ credentials: { apiKey: anthKey, openRouterKey: orKey },
163
+ defaultModel: model || null,
164
+ cwd: process.cwd(),
165
+ });
166
+ return outcome;
167
+ };
168
+ toolExecutor = createToolExecutor({ pluginRegistry, delegateRunner: runDelegateFromTool });
125
169
 
126
170
  // Auto-approve everything — no prompts
127
171
  const approval = new ApprovalManager({ autoApprove: true });
@@ -226,6 +226,7 @@ export class BahulamStreamClient {
226
226
  };
227
227
 
228
228
  for (const agent of clientAgents || []) addAgent(agent);
229
+ for (const agent of context?.agent_context?.available_agents || []) addAgent(agent);
229
230
  for (const agent of context?.agent_ctx?.available_agents || []) addAgent(agent);
230
231
  for (const agent of context?.available_agents || []) addAgent(agent);
231
232
  if (context?.sub_agent) addAgent(context.sub_agent);
@@ -16,15 +16,14 @@ import { analyzeCode } from '../context/ast-parser.mjs';
16
16
  import { ProjectRegistry } from '../tools/project-overview.mjs';
17
17
  import { SkillInstaller } from '../skills/installer.mjs';
18
18
  import { SkillsLoader } from '../skills/loader.mjs';
19
- import { agentToSpec, createAgentFile, listLocalAgents, syncAgentsToBackend } from '../agents/scaffold.mjs';
19
+ import { createAgentFile, listLocalAgents, syncAgentsToBackend } from '../agents/scaffold.mjs';
20
+ import { compactAgentMetadata, createAgentRegistry } from '../agents/registry.mjs';
20
21
  import { createWorkflowFile, listLocalWorkflows, WORKFLOW_SYNC_ENDPOINT, slugifyWorkflowName } from '../agents/workflow_scaffold.mjs';
21
22
  import { BahulamAuth } from '../auth/bahulam-auth.mjs';
22
23
  import { detectImageFile } from './attachments.mjs';
23
24
  import { streamResponse } from './streaming.mjs';
24
25
  import { sendApprovalDecision, sendCallback } from './callback-client.mjs';
25
26
  import { HookRunner } from '../config/hook-runner.mjs';
26
- import { loadBahulamSettings } from '../config/settings-loader.mjs';
27
- import { BUILTIN_AGENTS } from '../terminal/agents.mjs';
28
27
  import { buildFileDiff } from './file-diff.mjs';
29
28
  import { buildWorkScope } from './work-scope.mjs';
30
29
  import { loadDiskMemory, ensureBahulamDir, globalMemoryPath, projectMemoryPath } from './memory-disk.mjs';
@@ -60,6 +59,7 @@ export function createToolExecutor({
60
59
  // REPL/headless callers leave this null — state still works, just
61
60
  // no reactive pulse.
62
61
  stateEmit = null,
62
+ delegateRunner = null,
63
63
  // Execution channel. 'main' (REPL/headless/CLI): plugin agents are
64
64
  // workspace-scoped and excluded from listings and the agent-context
65
65
  // envelope unless allowlisted in settings plugins.agent_allowlist.
@@ -81,6 +81,12 @@ export function createToolExecutor({
81
81
  // guards it, but doing it here means the first read is a plain fs stat
82
82
  // rather than a mkdir round-trip.
83
83
  try { ensureBahulamDir('global'); } catch { /* ignore */ }
84
+ let activeDelegateRunner = delegateRunner;
85
+ const agentRegistry = createAgentRegistry({
86
+ cwd: () => process.cwd(),
87
+ pluginRegistry,
88
+ channel,
89
+ });
84
90
  let _memoryCache = null; // { key: string, facts: Fact[], digest: string }
85
91
  function _readMemorySnapshot() {
86
92
  const gPath = globalMemoryPath();
@@ -232,169 +238,16 @@ export function createToolExecutor({
232
238
  return ['read_file', 'search_code', 'list_files'];
233
239
  }
234
240
 
235
- function agentMatches(agent, query) {
236
- const needle = String(query || '').trim().toLowerCase();
237
- if (!needle) return true;
238
- return [
239
- agent.slug,
240
- agent.name,
241
- agent.description,
242
- agent.role,
243
- agent.model,
244
- ...(Array.isArray(agent.tools) ? agent.tools : []),
245
- ...(Array.isArray(agent.capabilities) ? agent.capabilities : []),
246
- ...(Array.isArray(agent.domains) ? agent.domains : []),
247
- ].some(value => String(value || '').toLowerCase().includes(needle));
248
- }
249
-
250
- function compactAgentMetadata(agent) {
251
- return {
252
- slug: agent.slug,
253
- name: agent.name,
254
- description: agent.description || '',
255
- role: agent.role || 'specialist',
256
- model: agent.model || null,
257
- models: agent.models && Object.keys(agent.models).length ? agent.models : undefined,
258
- tools: Array.isArray(agent.tools) ? agent.tools : [],
259
- capabilities: Array.isArray(agent.capabilities) ? agent.capabilities : [],
260
- domains: Array.isArray(agent.domains) ? agent.domains : [],
261
- source_scope: agent.source_scope || 'unknown',
262
- source: agent.source || '',
263
- content_hash: agent.content_hash || '',
264
- runnable: agent.runnable !== false,
265
- };
266
- }
267
-
268
- function pluginAgentToLocalShape(agentDef) {
269
- const pluginName = agentDef._plugin_name
270
- || String(agentDef.source || '').replace(/^plugin:/, '')
271
- || 'unknown';
272
- const source = `plugin:${pluginName}`;
273
- const base = {
274
- ...agentDef,
275
- slug: agentDef.slug || agentDef.name || '',
276
- name: agentDef.name || agentDef.slug || '',
277
- description: agentDef.description || '',
278
- role: agentDef.role || 'specialist',
279
- model: agentDef.model || null,
280
- models: agentDef.models || undefined,
281
- tools: Array.isArray(agentDef.tools)
282
- ? agentDef.tools
283
- : (Array.isArray(agentDef.agent_tools) ? agentDef.agent_tools : []),
284
- capabilities: Array.isArray(agentDef.capabilities) ? agentDef.capabilities : [],
285
- domains: Array.isArray(agentDef.domains) ? agentDef.domains : [],
286
- system_prompt: agentDef.system_prompt || agentDef.prompt || agentDef.instructions || '',
287
- prompt: agentDef.prompt || agentDef.system_prompt || agentDef.instructions || '',
288
- source_scope: 'plugin',
289
- source,
290
- };
291
- const spec = {
292
- ...agentToSpec(base),
293
- source,
294
- source_scope: 'plugin',
295
- plugin_name: pluginName,
296
- };
297
- if (spec.config?.metadata && typeof spec.config.metadata === 'object') {
298
- spec.config.metadata.source = source;
299
- spec.config.metadata.source_scope = 'plugin';
300
- }
301
- const content = JSON.stringify(spec);
302
- return {
303
- ...base,
304
- slug: spec.slug,
305
- spec,
306
- source,
307
- source_scope: 'plugin',
308
- content_hash: crypto.createHash('sha256').update(content).digest('hex'),
309
- };
310
- }
311
-
312
- function listPluginAgents() {
313
- if (!pluginRegistry) return [];
314
- return pluginRegistry.listAgents()
315
- .map(pluginAgentToLocalShape)
316
- .filter(agent => agent.slug);
317
- }
318
-
319
- // Plugin agents are workspace-scoped entities. They enter the
320
- // main-loop registry only via an explicit settings allowlist.
321
- function pluginAgentAllowlist() {
322
- try {
323
- const { settings } = loadBahulamSettings({ cwd: process.cwd() });
324
- const list = settings?.plugins?.agent_allowlist;
325
- return Array.isArray(list) ? list.map(item => String(item)) : [];
326
- } catch {
327
- return [];
328
- }
329
- }
330
-
331
- const BUILTIN_RUNNABLES = BUILTIN_AGENTS.map(def => ({
332
- slug: def.command,
333
- name: def.name,
334
- description: def.description || '',
335
- role: 'builtin',
336
- model: null,
337
- models: undefined,
338
- tools: [],
339
- capabilities: [],
340
- domains: [],
341
- source_scope: 'builtin',
342
- source: 'builtin',
343
- content_hash: '',
344
- read_only: Boolean(def.readOnly),
345
- runnable: true,
346
- }));
347
-
348
- // The deterministic sub-agent registry. Resolution precedence:
349
- // project agent → global agent → builtin → allowlisted plugin agent.
350
- // In workspace-channel executors the session plugin's agents are
351
- // runnable without an allowlist entry.
352
241
  function listRunnables() {
353
- const bySlug = new Map();
354
- for (const agent of listLocalAgents(process.cwd())) {
355
- if (agent.slug && !bySlug.has(agent.slug)) {
356
- bySlug.set(agent.slug, { ...agent, runnable: true });
357
- }
358
- }
359
- for (const builtin of BUILTIN_RUNNABLES) {
360
- if (!bySlug.has(builtin.slug)) bySlug.set(builtin.slug, builtin);
361
- }
362
- const allowlist = new Set(pluginAgentAllowlist());
363
- for (const agent of listPluginAgents()) {
364
- if (!agent.slug || bySlug.has(agent.slug)) continue;
365
- if (channel === 'workspace' || allowlist.has(agent.slug)) {
366
- bySlug.set(agent.slug, { ...agent, runnable: true });
367
- }
368
- }
369
- return [...bySlug.values()];
370
- }
371
-
372
- // Installed plugin agents NOT admitted to the main-loop registry —
373
- // still discoverable (scope:'plugin') but flagged not runnable.
374
- function listWorkspaceScopedPluginAgents() {
375
- const runnableSlugs = new Set(listRunnables().map(agent => agent.slug));
376
- return listPluginAgents()
377
- .filter(agent => agent.slug && !runnableSlugs.has(agent.slug))
378
- .map(agent => ({ ...agent, runnable: false }));
242
+ return agentRegistry.listRunnables();
379
243
  }
380
244
 
381
- // Agent-context envelope population: the runnable registry minus
382
- // builtins (the backend has its own delegation vocabulary for those;
383
- // adding them to available_agents would change wire behavior).
384
245
  function listAvailableAgents() {
385
- return listRunnables().filter(agent => agent.source_scope !== 'builtin');
246
+ return agentRegistry.listAvailableAgents();
386
247
  }
387
248
 
388
249
  function filterLocalAgents(args = {}) {
389
- const scope = String(args.scope || '').trim();
390
- if (scope && !['project', 'global', 'plugin', 'builtin'].includes(scope)) {
391
- throw new Error('scope must be "project", "global", "plugin", or "builtin"');
392
- }
393
- const pool = scope === 'plugin'
394
- ? [...listRunnables(), ...listWorkspaceScopedPluginAgents()]
395
- : listRunnables();
396
- const combined = pool.filter(agent => !scope || agent.source_scope === scope);
397
- return combined.filter(agent => agentMatches(agent, args.query || args.name || ''));
250
+ return agentRegistry.filterAgents(args);
398
251
  }
399
252
 
400
253
  function selectAgentsForSync(args = {}) {
@@ -1067,6 +920,57 @@ export function createToolExecutor({
1067
920
  };
1068
921
  },
1069
922
 
923
+ // Reserved meta-tool adapter. Cloud backends may implement Delegate
924
+ // natively; local callbacks use this to route through the exact same
925
+ // registry + dispatch funnel as /run and workflows.
926
+ delegate: async (args = {}, options = {}) => {
927
+ throwIfAborted(options.signal);
928
+ const target = String(args.agent || args.name || args.slug || args.sub_agent || '').trim();
929
+ const instruction = String(args.instruction || args.task || args.prompt || args.request || '').trim();
930
+ if (!target) {
931
+ return { success: false, output: 'delegate requires an agent slug or name.', _tool: 'delegate' };
932
+ }
933
+ if (!instruction) {
934
+ return { success: false, output: 'delegate requires an instruction.', _tool: 'delegate' };
935
+ }
936
+ const agent = agentRegistry.findAgent(target);
937
+ if (!agent) {
938
+ return {
939
+ success: false,
940
+ output: `Unknown delegate target '${target}'. Available agents: ${listRunnables().map(item => item.slug).join(', ') || '(none)'}`,
941
+ _tool: 'delegate',
942
+ };
943
+ }
944
+ if (typeof activeDelegateRunner !== 'function') {
945
+ return {
946
+ success: false,
947
+ output: 'Local delegate execution is not wired for this surface. Use /run <agent> "<task>" or delegate from a cloud execute session.',
948
+ _tool: 'delegate',
949
+ agent: compactAgentMetadata(agent),
950
+ };
951
+ }
952
+ const delegated = await activeDelegateRunner({
953
+ agent,
954
+ slug: agent.slug,
955
+ instruction,
956
+ context: args.context && typeof args.context === 'object' ? args.context : {},
957
+ options,
958
+ });
959
+ const payload = delegated?.result || delegated || {};
960
+ const output = payload.output
961
+ || payload.final_response
962
+ || payload.result
963
+ || (typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2));
964
+ return {
965
+ success: delegated?.dispatched === false ? false : payload.success !== false,
966
+ output: String(output || ''),
967
+ agent: compactAgentMetadata(agent),
968
+ run_id: payload.run_id || payload.graph_run_id || null,
969
+ node_results: payload.node_results || undefined,
970
+ _tool: 'delegate',
971
+ };
972
+ },
973
+
1070
974
  analyze_image: async (args, options = {}) => {
1071
975
  throwIfAborted(options.signal);
1072
976
  const tool = occRegistry.get('analyze_image');
@@ -2807,6 +2711,22 @@ export function createToolExecutor({
2807
2711
 
2808
2712
  listRunnables,
2809
2713
 
2714
+ findAgent(target) {
2715
+ return agentRegistry.findAgent(target);
2716
+ },
2717
+
2718
+ filterAgents(args = {}) {
2719
+ return agentRegistry.filterAgents(args);
2720
+ },
2721
+
2722
+ getSubAgentObservability() {
2723
+ return agentRegistry.observability();
2724
+ },
2725
+
2726
+ setDelegateRunner(fn) {
2727
+ activeDelegateRunner = typeof fn === 'function' ? fn : null;
2728
+ },
2729
+
2810
2730
  // Plugin tool schemas (name/description/input_schema) for callers
2811
2731
  // that compose model-facing tool lists — e.g. the graph engine's
2812
2732
  // direct substrate giving a plugin agent its declared tools.
@@ -2849,6 +2769,7 @@ export function createToolExecutor({
2849
2769
  source: agent.source,
2850
2770
  spec: agent.spec,
2851
2771
  })),
2772
+ sub_agent_observability: agentRegistry.observability(),
2852
2773
  // Background jobs the model should know about. Stable fields
2853
2774
  // only (no durations) so the entry — and the prompt cache —
2854
2775
  // changes on status transitions, not every turn.
@@ -19,6 +19,8 @@ import {
19
19
  import { BahulamStreamClient } from '../core/stream-client.mjs';
20
20
  import { createToolExecutor } from '../core/tool-executor.mjs';
21
21
  import { buildWorkScope } from '../core/work-scope.mjs';
22
+ import { listLocalWorkflows } from '../agents/workflow_scaffold.mjs';
23
+ import { dispatch } from '../orchestration/dispatch.mjs';
22
24
  import { BrowserApprovalManager } from './approval-bridge.mjs';
23
25
 
24
26
  const __require = createRequire(import.meta.url);
@@ -617,7 +619,31 @@ export class LocalAgentRelay {
617
619
  try { this.emit('plugin_state_changed', evt); }
618
620
  catch { /* never let SSE failure break a tool call */ }
619
621
  };
620
- const toolExecutor = createToolExecutor({ pluginRegistry, stateEmit, channel: 'workspace' });
622
+ let toolExecutor = null;
623
+ const runDelegateFromTool = async ({ agent, slug, instruction, options = {} }) => {
624
+ const turnId = `delegate_${Date.now().toString(36)}`;
625
+ return await dispatch({
626
+ type: 'invoke',
627
+ source: 'tool:delegate',
628
+ target: { kind: 'agent', slug: slug || agent?.slug, agent },
629
+ params: { instruction: instruction || '' },
630
+ channel: 'local',
631
+ signal: options.signal,
632
+ }, {
633
+ toolExecutor,
634
+ listRunnables: () => toolExecutor?.listRunnables?.() || [],
635
+ listLocalWorkflows: () => listLocalWorkflows(this.session.root_path),
636
+ renderEvent: event => this._emitAgentEvent(event, { turnId, contentText: event?.data?.text || event?.data?.content || null }),
637
+ sessionSubstrate: this._makeWorkspaceSessionSubstrate(pluginRegistry),
638
+ auth: { token: creds.token || null },
639
+ credentials: {
640
+ apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
641
+ openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
642
+ },
643
+ cwd: this.session.root_path,
644
+ });
645
+ };
646
+ toolExecutor = createToolExecutor({ pluginRegistry, stateEmit, channel: 'workspace', delegateRunner: runDelegateFromTool });
621
647
  await toolExecutor.waitForAutoRegister?.();
622
648
  await toolExecutor.registerProjectRoots?.([this.session.root_path], { forceRefresh: false });
623
649
 
@@ -726,6 +752,35 @@ export class LocalAgentRelay {
726
752
  return execContext;
727
753
  }
728
754
 
755
+ _makeWorkspaceSessionSubstrate(pluginRegistry) {
756
+ return (agent, node, instruction, { scopedExecutor } = {}) => (async function* (relay) {
757
+ const execContext = await relay._buildExecContext(instruction);
758
+ const slug = agent.slug || agent.command || agent.name || node?.agent_slug || node?.id || 'agent';
759
+ execContext.sub_agent = {
760
+ slug,
761
+ name: agent.name || slug,
762
+ role: agent.role || 'specialist',
763
+ description: agent.description || '',
764
+ tools: Array.isArray(agent.tools) ? agent.tools : [],
765
+ source: agent.source || '',
766
+ };
767
+ const systemPrompt = agent.systemPrompt || agent.system_prompt || agent.prompt || `You are ${agent.name || slug}, a Bahulam Code sub-agent.`;
768
+ const fullInstruction = `${systemPrompt}\n\n---\n\nUser request: ${instruction || 'Run your assigned task now.'}`;
769
+ const client = new BahulamStreamClient({
770
+ baseUrl: relay.creds.backendUrl,
771
+ token: relay.creds.token,
772
+ toolExecutor: scopedExecutor || relay.toolExecutor,
773
+ approvalManager: relay.approvalManager,
774
+ mode: 'remote',
775
+ pluginRegistry,
776
+ });
777
+ if (relay.resumeSessionId) client.sessionId = relay.resumeSessionId;
778
+ for await (const event of client.execute(fullInstruction, execContext)) {
779
+ yield event;
780
+ }
781
+ })(this);
782
+ }
783
+
729
784
  _buildInstruction(prompt, currentPath, attachments = []) {
730
785
  const lines = [
731
786
  'You are running inside Bahulam Local IDE.',
@@ -538,7 +538,7 @@ async function routeRequest({ req, res, sessionId, token, events, sseClients, em
538
538
  }
539
539
 
540
540
  // Plugin workspace views (PRD-101 §4.6): panels declared under
541
- // spec.workspace.views in plugin.yaml, rendered as sandboxed iframes.
541
+ // config.views in plugin.yaml, rendered as sandboxed iframes.
542
542
  // Generic infra — ANY installed plugin (project-local or ~/.bahulam)
543
543
  // contributes tabs; its whole directory is served statically so views
544
544
  // can ship css/js/assets with relative paths.
@@ -650,7 +650,7 @@ function scanPlugins(session) {
650
650
  if (!pluginName || !manifest._dir) continue;
651
651
  if (scope !== '__all__' && pluginName.toLowerCase() !== scope) continue;
652
652
  dirs.set(pluginName, manifest._dir);
653
- (manifest.spec?.workspace?.views || []).forEach((view) => {
653
+ (manifest.config?.views || []).forEach((view) => {
654
654
  const source = String(view?.source || '').trim().replace(/^\.\//, '');
655
655
  if (!source) return;
656
656
  views.push({
@@ -70,7 +70,7 @@ function resolveAgentBySlug(slug, ctx) {
70
70
 
71
71
  /**
72
72
  * Bare-name resolution order (uniform across surfaces, preserves today's
73
- * /run behavior): project agent → global agent → builtin → allowlisted
73
+ * /run behavior): project agent → global agent → platform → allowlisted
74
74
  * plugin agent → local workflow → synced server workflow fallback.
75
75
  * listRunnables() already returns agents deduped in that precedence.
76
76
  */
@@ -68,7 +68,7 @@ export async function loadPluginTool(pluginDir, handlerPath) {
68
68
  */
69
69
  export async function createPluginToolExecutor(manifest, opts = {}) {
70
70
  const pluginDir = manifest._dir || '';
71
- const tools = manifest.spec?.tools || [];
71
+ const tools = manifest.config?.tools || [];
72
72
  const handlers = new Map(); // name → { handler, toolDef }
73
73
  const pluginName = manifest.metadata?.name || '';
74
74
 
@@ -118,4 +118,4 @@ export async function createPluginToolExecutor(manifest, opts = {}) {
118
118
  /** Get (or open) the plugin's state handle — used by the view API. */
119
119
  get state() { return getState(); },
120
120
  };
121
- }
121
+ }
@@ -7,9 +7,10 @@
7
7
 
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
- import os from 'os';
11
10
  import { execSync } from 'child_process';
12
11
  import { PluginRegistry } from './registry.mjs';
12
+ import { parsePluginManifestFile } from './manifest.mjs';
13
+ import { bahulamHome } from '../core/paths.mjs';
13
14
 
14
15
  export class PluginLoader {
15
16
  /**
@@ -21,11 +22,11 @@ export class PluginLoader {
21
22
  constructor(options = {}) {
22
23
  const { pluginDir, pluginDirs, disabled } = options;
23
24
  this.registry = new PluginRegistry({
24
- pluginDir: pluginDir || path.join(os.homedir(), '.bahulam', 'plugins'),
25
+ pluginDir: pluginDir || path.join(bahulamHome(), 'plugins'),
25
26
  pluginDirs,
26
27
  disabled,
27
28
  });
28
- this.pluginDir = pluginDir || path.join(os.homedir(), '.bahulam', 'plugins');
29
+ this.pluginDir = pluginDir || path.join(bahulamHome(), 'plugins');
29
30
  }
30
31
 
31
32
  /**
@@ -73,7 +74,6 @@ export class PluginLoader {
73
74
  const exists = fs.existsSync(manifestPath) ? manifestPath : (fs.existsSync(altPath) ? altPath : null);
74
75
 
75
76
  if (exists) {
76
- const { parsePluginManifestFile } = await import('./manifest.mjs');
77
77
  const manifest = parsePluginManifestFile(exists);
78
78
  if (manifest) {
79
79
  this.registry.register(manifest);
@@ -135,4 +135,4 @@ export class PluginLoader {
135
135
  count() {
136
136
  return this.registry.count();
137
137
  }
138
- }
138
+ }