@bahulam/code 0.1.11 → 0.1.13

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/auth/tarang-auth.mjs +313 -0
  3. package/src/commands/agent.mjs +7 -7
  4. package/src/commands/install.mjs +295 -0
  5. package/src/commands/plugin-manage.mjs +280 -88
  6. package/src/config/cli-args.mjs +16 -0
  7. package/src/config/settings-loader.mjs +15 -0
  8. package/src/core/background-tasks.mjs +186 -0
  9. package/src/core/headless.mjs +54 -3
  10. package/src/core/local-agent.mjs +10 -1
  11. package/src/core/risk-tier.mjs +1 -0
  12. package/src/core/stream-client.mjs +95 -15
  13. package/src/core/tool-executor.mjs +266 -15
  14. package/src/local-service/agent-relay.mjs +1 -1
  15. package/src/local-service/server.mjs +116 -14
  16. package/src/orchestration/approval.mjs +30 -0
  17. package/src/orchestration/completion-triggers.mjs +40 -0
  18. package/src/orchestration/dispatch.mjs +118 -0
  19. package/src/orchestration/events.mjs +19 -0
  20. package/src/orchestration/graph.mjs +126 -0
  21. package/src/orchestration/node-runner.mjs +193 -0
  22. package/src/orchestration/runner.mjs +200 -0
  23. package/src/plugins/executor.mjs +2 -2
  24. package/src/plugins/manifest.mjs +30 -27
  25. package/src/plugins/pi-compat/loader-hook.mjs +45 -0
  26. package/src/plugins/pi-compat/probe.mjs +294 -0
  27. package/src/plugins/pi-compat/scaffold.mjs +487 -0
  28. package/src/plugins/pi-compat/shim.mjs +134 -0
  29. package/src/plugins/pi-compose.mjs +147 -0
  30. package/src/plugins/preflight.mjs +35 -10
  31. package/src/plugins/registry.mjs +6 -0
  32. package/src/terminal/agents.mjs +8 -3
  33. package/src/terminal/main.mjs +39 -7
  34. package/src/terminal/paste-input.mjs +23 -0
  35. package/src/terminal/repl-render.mjs +65 -10
  36. package/src/terminal/repl-state.mjs +4 -2
  37. package/src/terminal/repl.mjs +624 -103
  38. package/src/tools/agent.mjs +6 -2
  39. package/src/tools/registry.mjs +107 -4
  40. package/src/ui/input-dock.mjs +5 -2
  41. package/src/ui/slash-commands.mjs +1 -1
  42. package/src/ui/sub-agent.mjs +14 -8
@@ -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,114 @@ 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
+ if (toolDef._composed?.kind === 'pi') {
126
+ tools.set(name, {
127
+ name,
128
+ description: toolDef.description || '',
129
+ inputSchema: toolDef.input_schema || toolDef.parameters || { type: 'object', properties: {} },
130
+ validateInput() { return []; },
131
+ async call() {
132
+ return {
133
+ success: false,
134
+ output: `Composed pi tool '${name}' is registered but pi runtime execution is not wired yet`,
135
+ _tool: name,
136
+ _plugin: pluginName,
137
+ _composed: toolDef._composed,
138
+ _blocked: true,
139
+ };
140
+ },
141
+ });
142
+ continue;
143
+ }
144
+ tools.set(name, {
145
+ name,
146
+ description: toolDef.description || '',
147
+ inputSchema: toolDef.input_schema || toolDef.parameters || { type: 'object', properties: {} },
148
+ validateInput() { return []; },
149
+ async call(input, options = {}) {
150
+ const handler = await loadPluginTool(toolDef._plugin_dir, toolDef.tool || toolDef.handler);
151
+ if (!handler) {
152
+ return {
153
+ success: false,
154
+ output: `Plugin tool module could not be loaded: ${name}`,
155
+ _tool: name,
156
+ _plugin: pluginName,
157
+ };
158
+ }
159
+ const handlerOpts = {
160
+ ...options,
161
+ pluginName,
162
+ get state() {
163
+ if (this._stateP) return this._stateP;
164
+ this._stateP = pluginStateFor(pluginName);
165
+ return this._stateP;
166
+ },
167
+ };
168
+ try {
169
+ const result = await handler.call(input || {}, handlerOpts);
170
+ if (result && typeof result === 'object' && 'success' in result) {
171
+ return { ...result, _tool: name, _plugin: pluginName };
172
+ }
173
+ return {
174
+ success: true,
175
+ output: typeof result === 'string' ? result : JSON.stringify(result),
176
+ _tool: name,
177
+ _plugin: pluginName,
178
+ };
179
+ } catch (err) {
180
+ return {
181
+ success: false,
182
+ output: `Plugin tool error (${name}): ${err.message}`,
183
+ _tool: name,
184
+ _plugin: pluginName,
185
+ };
186
+ }
187
+ },
188
+ });
189
+ }
190
+ }
191
+
192
+ if (exposePluginTools) registerPluginToolsFromRegistry();
193
+
91
194
  const registry = {
92
195
  list() {
93
196
  return [...tools.values()].map(t => ({
@@ -97,12 +200,12 @@ export function createToolRegistry() {
97
200
  }));
98
201
  },
99
202
 
100
- async call(name, input) {
203
+ async call(name, input, options = {}) {
101
204
  const tool = tools.get(name);
102
205
  if (!tool) throw new Error(`Unknown tool: ${name}`);
103
206
  const errors = tool.validateInput?.(input) || [];
104
207
  if (errors.length > 0) return `Validation error: ${errors.join(', ')}`;
105
- const result = await tool.call(input);
208
+ const result = await tool.call(input, options);
106
209
 
107
210
  return result;
108
211
  },
@@ -672,10 +672,13 @@ export function clearInputPrompt() {
672
672
  return true;
673
673
  }
674
674
 
675
- export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null } = {}) {
675
+ export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null, fixedRows = null } = {}) {
676
676
  if (!mounted) return false;
677
677
  contentTrackingActive = false;
678
- setInputRowsTo(computeInputRowsForBuffer(prefix, value));
678
+ const requestedRows = fixedRows == null
679
+ ? computeInputRowsForBuffer(prefix, value)
680
+ : Math.max(MIN_INPUT_ROWS, Math.min(inputRowsMax, Math.floor(Number(fixedRows) || MIN_INPUT_ROWS)));
681
+ setInputRowsTo(requestedRows);
679
682
  renderFrame({ context, tips, meta, prefix, value, cursor, overlayLines: null });
680
683
  const layout = layoutInput(prefix, value);
681
684
  drawInputLines(layout.lines);
@@ -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`)}`;