@bahulam/code 0.1.10 → 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.
- package/package.json +5 -2
- package/src/agents/loader.mjs +26 -4
- package/src/auth/bahulam-auth.mjs +2 -13
- package/src/auth/tarang-auth.mjs +313 -0
- package/src/commands/agent.mjs +7 -7
- package/src/commands/plugin-manage.mjs +449 -0
- package/src/commands/plugin.mjs +247 -0
- package/src/config/cli-args.mjs +16 -0
- package/src/config/env.mjs +2 -0
- package/src/config/hook-runner.mjs +8 -8
- package/src/config/memory-loader.mjs +7 -3
- package/src/config/settings-loader.mjs +5 -3
- package/src/core/attachments.mjs +2 -2
- package/src/core/background-tasks.mjs +186 -0
- package/src/core/headless.mjs +59 -3
- package/src/core/local-agent.mjs +10 -1
- package/src/core/local-store.mjs +10 -10
- package/src/core/paths.mjs +10 -96
- package/src/core/policy-resolver.mjs +1 -1
- package/src/core/project-context-loader.mjs +2 -2
- package/src/core/risk-tier.mjs +1 -0
- package/src/core/stream-client.mjs +148 -1
- package/src/core/system-prompt.mjs +31 -12
- package/src/core/tool-executor.mjs +457 -12
- package/src/local-service/agent-relay.mjs +139 -12
- package/src/local-service/server.mjs +345 -20
- package/src/orchestration/approval.mjs +30 -0
- package/src/orchestration/completion-triggers.mjs +40 -0
- package/src/orchestration/dispatch.mjs +118 -0
- package/src/orchestration/events.mjs +19 -0
- package/src/orchestration/graph.mjs +126 -0
- package/src/orchestration/node-runner.mjs +193 -0
- package/src/orchestration/runner.mjs +200 -0
- package/src/plugins/executor.mjs +121 -0
- package/src/plugins/loader.mjs +123 -123
- package/src/plugins/manifest.mjs +291 -0
- package/src/plugins/preflight.mjs +227 -0
- package/src/plugins/registry.mjs +233 -0
- package/src/plugins/state.mjs +290 -0
- package/src/terminal/agents.mjs +26 -4
- package/src/terminal/init.mjs +2 -2
- package/src/terminal/main.mjs +83 -4
- package/src/terminal/repl-explore.mjs +1 -1
- package/src/terminal/repl-render.mjs +67 -12
- package/src/terminal/repl-state.mjs +4 -2
- package/src/terminal/repl.mjs +621 -99
- package/src/tools/agent.mjs +6 -2
- package/src/tools/analyze-image.mjs +1 -1
- package/src/tools/project-overview.mjs +7 -7
- package/src/tools/registry.mjs +88 -4
- package/src/ui/slash-commands.mjs +19 -1
- package/src/ui/sub-agent.mjs +14 -8
package/src/tools/agent.mjs
CHANGED
|
@@ -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
|
|
@@ -92,7 +92,7 @@ export const AnalyzeImageTool = {
|
|
|
92
92
|
if (!creds.backendUrl || !creds.token) {
|
|
93
93
|
return {
|
|
94
94
|
success: false,
|
|
95
|
-
output: 'analyze_image requires CLI auth. Run `bahulam-code login` or set B0_TOKEN
|
|
95
|
+
output: 'analyze_image requires CLI auth. Run `bahulam-code login` or set B0_TOKEN.',
|
|
96
96
|
_tool: 'analyze_image',
|
|
97
97
|
};
|
|
98
98
|
}
|
|
@@ -73,7 +73,7 @@ const PROJECT_MARKERS = [
|
|
|
73
73
|
'pom.xml', 'build.gradle', 'build.gradle.kts', 'settings.gradle', // Java/Kotlin
|
|
74
74
|
'Makefile', 'CMakeLists.txt', // C/C++
|
|
75
75
|
'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
|
|
76
|
-
'AGENTS.md', '
|
|
76
|
+
'AGENTS.md', 'BAHULAM.md', 'CLAUDE.md', // Agent config lives at root
|
|
77
77
|
'.editorconfig', // Broad but a strong "this is a repo" signal
|
|
78
78
|
];
|
|
79
79
|
|
|
@@ -350,7 +350,7 @@ function defaultScratchRoots() {
|
|
|
350
350
|
'/private/tmp',
|
|
351
351
|
os.tmpdir(),
|
|
352
352
|
process.env.TMPDIR,
|
|
353
|
-
...(process.env.
|
|
353
|
+
...(process.env.BAHULAM_SCRATCH_ROOTS || '')
|
|
354
354
|
.split(path.delimiter)
|
|
355
355
|
.map(s => s.trim())
|
|
356
356
|
.filter(Boolean),
|
|
@@ -399,13 +399,13 @@ export class ProjectRegistry {
|
|
|
399
399
|
|
|
400
400
|
// PRD-69 project context is live metadata, not index cache. Re-read it on
|
|
401
401
|
// every registration attempt so repeated get_project_overview calls pick up
|
|
402
|
-
// .bahulam/
|
|
402
|
+
// .bahulam/BAHULAM.md, goal/plan/style, skills, AGENTS.md, etc. changes.
|
|
403
403
|
_attachLiveContext(resource, root) {
|
|
404
|
-
// Resolver — prefers .bahulam/, falls back to .kepler/ for legacy projects.
|
|
405
404
|
const bahulamDir = projectConfigDir(root);
|
|
406
405
|
resource.environment = detectEnvironment();
|
|
407
|
-
resource.project_context = _readIfExists(
|
|
408
|
-
_readIfExists(
|
|
406
|
+
resource.project_context = _readIfExists(root, 'AGENTS.md', 10000) ||
|
|
407
|
+
_readIfExists(bahulamDir, 'BAHULAM.md', 10000) ||
|
|
408
|
+
_readIfExists(root, 'BAHULAM.md', 10000) ||
|
|
409
409
|
_readIfExists(bahulamDir, 'project.md', 8000);
|
|
410
410
|
resource.style = _readIfExists(bahulamDir, 'style.md', 4000);
|
|
411
411
|
resource.goal = _readIfExists(bahulamDir, 'goal.md', 2000);
|
|
@@ -413,7 +413,7 @@ export class ProjectRegistry {
|
|
|
413
413
|
resource.skills_index = _scanSkills(bahulamDir);
|
|
414
414
|
|
|
415
415
|
if (!resource.project_context) {
|
|
416
|
-
for (const name of ['.bahulam.md', 'AGENTS.md', 'CLAUDE.md']) {
|
|
416
|
+
for (const name of ['.bahulam.md', 'BAHULAM.md', 'AGENTS.md', 'CLAUDE.md']) {
|
|
417
417
|
const content = _readIfExists(root, name, 8000);
|
|
418
418
|
if (content) { resource.project_context = content; break; }
|
|
419
419
|
}
|
package/src/tools/registry.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
},
|
|
@@ -47,6 +47,8 @@ export const COMMANDS = {
|
|
|
47
47
|
'/agents': 'List available agents',
|
|
48
48
|
'/subagents': 'Alias of /agents (list/create/edit/sync sub-agents)',
|
|
49
49
|
'/skills': 'List / install / view / remove skills (SKILL.md bundles)',
|
|
50
|
+
'/plugins': 'List / install / info / update / remove plugins (tools + agents + views)',
|
|
51
|
+
'/plugin': 'Alias of /plugins',
|
|
50
52
|
'/run': 'Run a sub-agent or synced workflow',
|
|
51
53
|
'/explore': 'Code explorer agent',
|
|
52
54
|
'/review': 'Code review agent',
|
|
@@ -141,7 +143,7 @@ export const HELP_GROUPS = [
|
|
|
141
143
|
['/agents', 'List built-in and local agents'],
|
|
142
144
|
['/agents create <name>', 'Create .bahulam/agents/<name>.yaml'],
|
|
143
145
|
['/agents edit <name>', 'Open local agent YAML'],
|
|
144
|
-
['/agents sync [name]', '
|
|
146
|
+
['/agents sync [name]', 'Publish local agents to backend/account reuse'],
|
|
145
147
|
['/run <agent> [instruction]', 'Run a local or built-in agent'],
|
|
146
148
|
['/explore <instruction>', 'Explore code'],
|
|
147
149
|
['/review <instruction>', 'Review code'],
|
|
@@ -162,6 +164,22 @@ export const HELP_GROUPS = [
|
|
|
162
164
|
['/skills update <name>', 'Reinstall from the recorded source'],
|
|
163
165
|
],
|
|
164
166
|
},
|
|
167
|
+
{
|
|
168
|
+
key: 'plugins',
|
|
169
|
+
title: 'Plugins',
|
|
170
|
+
summary: 'community-authored tools, agents, and workspace views',
|
|
171
|
+
commands: [
|
|
172
|
+
['/plugins', 'List installed plugins'],
|
|
173
|
+
['/plugins install <git-url|path|name>', 'Install from git URL, local path, or awesome-bahulam-plugins name'],
|
|
174
|
+
['/plugins install <src> --project', 'Install into .bahulam/plugins (project scope)'],
|
|
175
|
+
['/plugins install <src> --ref <tag>', 'Pin a git ref'],
|
|
176
|
+
['/plugins validate <path|name>', 'Preflight a plugin without installing (schema + handlers + collisions)'],
|
|
177
|
+
['/plugins info <name>', 'Show manifest details + install origin'],
|
|
178
|
+
['/plugins enable|disable <name>', 'Toggle without deleting'],
|
|
179
|
+
['/plugins update <name>', 'Pull the latest for git-installed plugins'],
|
|
180
|
+
['/plugins remove <name>', 'Uninstall a plugin'],
|
|
181
|
+
],
|
|
182
|
+
},
|
|
165
183
|
{
|
|
166
184
|
key: 'workflows',
|
|
167
185
|
title: 'Workflows',
|
package/src/ui/sub-agent.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
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`)}`;
|