@bahulam/code 0.1.11 → 0.1.12

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.
@@ -58,9 +58,13 @@ export const AgentTool = {
58
58
  _backgroundAgents: new Map(),
59
59
  _nextBgId: 0,
60
60
 
61
- async call(input) {
61
+ async call(input, options = {}) {
62
62
  const model = input.model || process.env.SUBAGENT_MODEL || 'claude-sonnet-4-6';
63
- const tools = createToolRegistry();
63
+ const tools = createToolRegistry({
64
+ pluginRegistry: options.pluginRegistry || null,
65
+ stateEmit: options.stateEmit || null,
66
+ exposePluginTools: true,
67
+ });
64
68
  const permissions = createPermissionChecker({ defaultMode: 'bypassPermissions' });
65
69
 
66
70
  // Build type-specific system prompt prefix
@@ -39,6 +39,7 @@ import { ExploreTool, PlanTool, VerifyTool, DebugTool, RefactorTool } from './me
39
39
  import { RememberTool } from './remember.mjs';
40
40
  import { GenerateImageTool } from './generate-image.mjs';
41
41
  import { AnalyzeImageTool } from './analyze-image.mjs';
42
+ import { loadPluginTool } from '../plugins/executor.mjs';
42
43
 
43
44
  const BUILTIN_TOOLS = [
44
45
  BashTool,
@@ -82,12 +83,95 @@ const BUILTIN_TOOLS = [
82
83
  RefactorTool,
83
84
  ];
84
85
 
85
- export function createToolRegistry() {
86
+ export function createToolRegistry({
87
+ pluginRegistry = null,
88
+ stateEmit = null,
89
+ exposePluginTools = false,
90
+ } = {}) {
86
91
  const tools = new Map();
87
92
  for (const Tool of BUILTIN_TOOLS) {
88
- tools.set(Tool.name, Tool);
93
+ if (Tool === AgentTool) {
94
+ tools.set(Tool.name, {
95
+ ...Tool,
96
+ async call(input, options = {}) {
97
+ return Tool.call(input, {
98
+ ...options,
99
+ pluginRegistry: options.pluginRegistry || pluginRegistry,
100
+ stateEmit: options.stateEmit || stateEmit,
101
+ });
102
+ },
103
+ });
104
+ } else {
105
+ tools.set(Tool.name, Tool);
106
+ }
89
107
  }
90
108
 
109
+ const pluginStateHandles = new Map();
110
+ async function pluginStateFor(pluginName) {
111
+ if (!pluginName) return null;
112
+ if (pluginStateHandles.has(pluginName)) return pluginStateHandles.get(pluginName);
113
+ const { makePluginState } = await import('../plugins/state.mjs');
114
+ const state = makePluginState(pluginName, { emit: stateEmit });
115
+ pluginStateHandles.set(pluginName, state);
116
+ return state;
117
+ }
118
+
119
+ function registerPluginToolsFromRegistry() {
120
+ if (!pluginRegistry) return;
121
+ for (const toolDef of pluginRegistry.listTools?.() || []) {
122
+ const name = String(toolDef.name || '').trim();
123
+ if (!name || tools.has(name)) continue;
124
+ const pluginName = toolDef._plugin_name || toolDef.plugin_name || null;
125
+ tools.set(name, {
126
+ name,
127
+ description: toolDef.description || '',
128
+ inputSchema: toolDef.input_schema || toolDef.parameters || { type: 'object', properties: {} },
129
+ validateInput() { return []; },
130
+ async call(input, options = {}) {
131
+ const handler = await loadPluginTool(toolDef._plugin_dir, toolDef.tool || toolDef.handler);
132
+ if (!handler) {
133
+ return {
134
+ success: false,
135
+ output: `Plugin tool module could not be loaded: ${name}`,
136
+ _tool: name,
137
+ _plugin: pluginName,
138
+ };
139
+ }
140
+ const handlerOpts = {
141
+ ...options,
142
+ pluginName,
143
+ get state() {
144
+ if (this._stateP) return this._stateP;
145
+ this._stateP = pluginStateFor(pluginName);
146
+ return this._stateP;
147
+ },
148
+ };
149
+ try {
150
+ const result = await handler.call(input || {}, handlerOpts);
151
+ if (result && typeof result === 'object' && 'success' in result) {
152
+ return { ...result, _tool: name, _plugin: pluginName };
153
+ }
154
+ return {
155
+ success: true,
156
+ output: typeof result === 'string' ? result : JSON.stringify(result),
157
+ _tool: name,
158
+ _plugin: pluginName,
159
+ };
160
+ } catch (err) {
161
+ return {
162
+ success: false,
163
+ output: `Plugin tool error (${name}): ${err.message}`,
164
+ _tool: name,
165
+ _plugin: pluginName,
166
+ };
167
+ }
168
+ },
169
+ });
170
+ }
171
+ }
172
+
173
+ if (exposePluginTools) registerPluginToolsFromRegistry();
174
+
91
175
  const registry = {
92
176
  list() {
93
177
  return [...tools.values()].map(t => ({
@@ -97,12 +181,12 @@ export function createToolRegistry() {
97
181
  }));
98
182
  },
99
183
 
100
- async call(name, input) {
184
+ async call(name, input, options = {}) {
101
185
  const tool = tools.get(name);
102
186
  if (!tool) throw new Error(`Unknown tool: ${name}`);
103
187
  const errors = tool.validateInput?.(input) || [];
104
188
  if (errors.length > 0) return `Validation error: ${errors.join(', ')}`;
105
- const result = await tool.call(input);
189
+ const result = await tool.call(input, options);
106
190
 
107
191
  return result;
108
192
  },
@@ -143,7 +143,7 @@ export const HELP_GROUPS = [
143
143
  ['/agents', 'List built-in and local agents'],
144
144
  ['/agents create <name>', 'Create .bahulam/agents/<name>.yaml'],
145
145
  ['/agents edit <name>', 'Open local agent YAML'],
146
- ['/agents sync [name]', 'Sync all or one local agent to cloud'],
146
+ ['/agents sync [name]', 'Publish local agents to backend/account reuse'],
147
147
  ['/run <agent> [instruction]', 'Run a local or built-in agent'],
148
148
  ['/explore <instruction>', 'Explore code'],
149
149
  ['/review <instruction>', 'Review code'],
@@ -70,11 +70,12 @@ export function displayQuery(query, max = 140) {
70
70
 
71
71
  export function renderSubAgentOpen({ id, type, query, parentDepth } = {}) {
72
72
  const t = type || 'sub-agent';
73
- const depthBefore = _stack.length;
74
- _stack.push({ id: id || `${t}-${depthBefore}-${tag()}`, type: t, startedAt: Date.now() });
73
+ const depthBefore = Number.isFinite(parentDepth) ? Math.max(0, parentDepth) : _stack.length;
74
+ _stack.push({ id: id || `${t}-${depthBefore}-${tag()}`, type: t, depth: depthBefore, startedAt: Date.now() });
75
75
 
76
76
  const indent = ' '.repeat(2 + depthBefore * 3);
77
- const iconChar = SUB_ICONS[t] || icons.subAgent;
77
+ // Ordinal labels (explore#2) still get their base type's icon.
78
+ const iconChar = SUB_ICONS[t] || SUB_ICONS[String(t).replace(/#\d+$/, '')] || icons.subAgent;
78
79
  const shown = displayQuery(query);
79
80
  const head = `${indent}${iconChar} ${paint.brand.data(t)} ${paint.text.dim(`"${shown}"`)}`;
80
81
  const tag1 = paint.text.dim('▸ running');
@@ -95,6 +96,7 @@ export function renderSubAgentOpen({ id, type, query, parentDepth } = {}) {
95
96
  * and any of `{ costUsd, tokens, durationS, toolCalls, iterations }`.
96
97
  */
97
98
  export function renderSubAgentClose({
99
+ id,
98
100
  type,
99
101
  success = true,
100
102
  summary = '',
@@ -105,12 +107,16 @@ export function renderSubAgentClose({
105
107
  iterations,
106
108
  error,
107
109
  } = {}) {
108
- // Match-pop: if the type doesn't match the top of stack we still pop the
109
- // top entry backends never emit interleaved open/close, so this is the
110
- // safe behavior.
111
- const opened = _stack.pop();
110
+ // Parallel sub-agents can complete out of stack order. Prefer exact id,
111
+ // then type, and fall back to the latest open entry for legacy streams.
112
+ let idx = -1;
113
+ if (id) idx = _stack.findLastIndex(entry => entry.id === id);
114
+ if (idx < 0 && type) idx = _stack.findLastIndex(entry => entry.type === type);
115
+ if (idx < 0) idx = _stack.length - 1;
116
+ const opened = idx >= 0 ? _stack.splice(idx, 1)[0] : null;
112
117
  const t = type || opened?.type || 'sub-agent';
113
- const indent = ' '.repeat(2 + _stack.length * 3);
118
+ const closeDepth = Number.isFinite(opened?.depth) ? opened.depth : _stack.length;
119
+ const indent = ' '.repeat(2 + closeDepth * 3);
114
120
 
115
121
  if (!success) {
116
122
  const line = `${indent}${paint.text.dim('└')} ${paint.state.danger('✗')} ${paint.text.dim(`${t} agent failed`)}`;