@bahulam/code 0.1.16 → 0.1.18

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.
@@ -59,7 +59,7 @@ import * as telemetry from '../telemetry/index.mjs';
59
59
  import { resolveBackendUrl } from '../core/backend-url.mjs';
60
60
  import { formatMessageWindow, lowWindowStatus, messagesRemaining } from '../core/rate-limit-display.mjs';
61
61
  import { formatAgentErrorGuidance } from '../core/error-guidance.mjs';
62
- import { BUILTIN_AGENTS, findBuiltinAgent, localAgentMatches, runAgent, runAgentDefinition } from './agents.mjs';
62
+ import { BUILTIN_AGENTS, runAgentDefinition } from './agents.mjs';
63
63
  import { SkillInstaller } from '../skills/installer.mjs';
64
64
  import { SkillsLoader } from '../skills/loader.mjs';
65
65
  import { openSkillsPicker, formatSkillsList } from './skills-picker.mjs';
@@ -921,23 +921,35 @@ function printAgentsUsage() {
921
921
  process.stderr.write(` /agents sync [name]\n`);
922
922
  }
923
923
 
924
- function printAgentsList() {
925
- const local = listLocalAgents(safeCwd());
926
- process.stderr.write(`\n ${c.bold('Built-in Agents')}\n`);
924
+ function printAgentsList(ctx = {}) {
925
+ const agents = ctx.toolExecutor?.filterAgents?.({}) || [
926
+ ...listLocalAgents(safeCwd()),
927
+ ...BUILTIN_AGENTS.map(agent => ({
928
+ slug: agent.command,
929
+ name: agent.name,
930
+ description: agent.description,
931
+ source_scope: 'platform',
932
+ })),
933
+ ];
934
+ const groups = new Map();
935
+ for (const agent of agents) {
936
+ const scope = agent.source_scope || 'unknown';
937
+ if (!groups.has(scope)) groups.set(scope, []);
938
+ groups.get(scope).push(agent);
939
+ }
940
+
941
+ process.stderr.write(`\n ${c.bold('Agents')} ${c.dim('platform + project + global + admitted plugin')}\n`);
927
942
  process.stderr.write(` ${c.gray('─'.repeat(44))}\n`);
928
- for (const agent of BUILTIN_AGENTS) {
929
- process.stderr.write(` ${c.brand(('/' + agent.command).padEnd(14))} ${agent.description}\n`);
930
- }
931
-
932
- process.stderr.write(`\n ${c.bold('Local Agents')} ${c.dim('.bahulam/agents + ~/.bahulam/agents')}\n`);
933
- process.stderr.write(` ${c.gray('─'.repeat(44))}\n`);
934
- if (!local.length) {
943
+ if (!agents.length) {
935
944
  process.stderr.write(` ${c.dim('(none)')}\n`);
936
945
  } else {
937
- for (const agent of local) {
938
- const scope = agent.source_scope === 'project' ? c.green('project') : c.dim(agent.source_scope);
939
- const model = agent.model ? c.dim(` · ${agent.model}`) : '';
940
- process.stderr.write(` ${c.brand(agent.slug.padEnd(18))} ${scope} ${agent.description || ''}${model}\n`);
946
+ for (const [scope, items] of groups) {
947
+ process.stderr.write(` ${c.dim(scope)}\n`);
948
+ for (const agent of items) {
949
+ const model = agent.model ? c.dim(` · ${agent.model}`) : '';
950
+ const desc = agent.description ? ` ${agent.description}` : '';
951
+ process.stderr.write(` ${c.brand(String(agent.slug || agent.command || agent.name).padEnd(18))}${desc}${model}\n`);
952
+ }
941
953
  }
942
954
  }
943
955
  process.stderr.write('\n');
@@ -949,7 +961,7 @@ async function handleAgentsCommand(rest = '', ctx) {
949
961
  const action = (parts.shift() || 'list').toLowerCase();
950
962
 
951
963
  if (action === 'list' || action === 'ls') {
952
- printAgentsList();
964
+ printAgentsList(ctx);
953
965
  return;
954
966
  }
955
967
 
@@ -1323,8 +1335,8 @@ async function _checkForUpgradeAndAnnounce() {
1323
1335
  const [x, y, z] = asTuple(current);
1324
1336
  const newer = (a > x) || (a === x && b > y) || (a === x && b === y && c1 > z);
1325
1337
  if (!newer) return;
1326
- process.stderr.write(` ${c.brand('◆')} ${c.dim('New version available:')} ${c.bold(c.green(latest))} ${c.dim(`(current ${current})`)}\n`);
1327
- process.stderr.write(` ${c.dim(' Upgrade:')} ${c.dim('npm install -g ' + pkgName + '@latest')}\n\n`);
1338
+ process.stderr.write(` ${c.brand('◆')} ${c.dim('Update available:')} ${c.bold(c.green(`${pkgName}@${latest}`))} ${c.dim(`(installed ${current})`)}\n`);
1339
+ process.stderr.write(` ${c.dim(' Install:')} ${c.dim('npm install -g ' + pkgName + '@latest')}\n\n`);
1328
1340
  }
1329
1341
 
1330
1342
  // ── Prompt Chrome ──
@@ -1373,6 +1385,8 @@ function buildContextStrip() {
1373
1385
  // volume + elapsed. Historical rate calc was double-counting the cache tokens
1374
1386
  // vs OpenRouter's convention (see computeCacheTotals) which was misleading.
1375
1387
  const parts = [];
1388
+ const model = compactDockModel(activeDockModel());
1389
+ if (model) parts.push(c.dim(`model ${model}`));
1376
1390
  // ctx: last turn's cumulative input tokens — approximates the CURRENT
1377
1391
  // prompt size. Only shown once we've completed at least one turn (so
1378
1392
  // the initial banner doesn't read '0 ctx'). This is the number the
@@ -1385,13 +1399,27 @@ function buildContextStrip() {
1385
1399
  return parts.join(c.dim(' · '));
1386
1400
  }
1387
1401
 
1388
- // ── Dock meta line (model · cwd ⎇ branch · turn N) ─────────────────────
1402
+ // ── Dock meta line (cwd ⎇ branch · turn N) ─────────────────────────────
1389
1403
  //
1390
1404
  // The dock's meta row shows durable session context. Git branch is cached
1391
1405
  // so we don't shell out on every keystroke; refreshed at most every 5s.
1392
1406
 
1393
1407
  const _dockGitCache = { branch: null, at: 0, cwd: null };
1394
1408
 
1409
+ function activeDockModel() {
1410
+ return session.modelOverrides?.reasoning
1411
+ || session.model
1412
+ || session.modelLimits?.coder?.model
1413
+ || session.user?.default_reasoning_model
1414
+ || null;
1415
+ }
1416
+
1417
+ function compactDockModel(model) {
1418
+ const value = String(model || '').trim();
1419
+ if (!value) return '';
1420
+ return value.replace(/^(anthropic|openai|google|deepseek|xai|meta)\//, '');
1421
+ }
1422
+
1395
1423
  function _probeGitBranch(cwd) {
1396
1424
  const now = Date.now();
1397
1425
  if (_dockGitCache.cwd === cwd && (now - _dockGitCache.at) < 5000) {
@@ -1422,11 +1450,6 @@ function buildDockMeta() {
1422
1450
  parts.push(`turn ${session.turns}`);
1423
1451
  }
1424
1452
 
1425
- const totalTokens = session.inputTokens + session.outputTokens;
1426
- if (totalTokens > 0) {
1427
- parts.push(`${formatTokens(totalTokens)} tok`);
1428
- }
1429
-
1430
1453
  return parts.join(' · ');
1431
1454
  }
1432
1455
 
@@ -3186,6 +3209,23 @@ async function prepareDirectAgentRunContext(ctx, instruction = '') {
3186
3209
  return execContext;
3187
3210
  }
3188
3211
 
3212
+ function makeDispatchContext(ctx) {
3213
+ const creds = ctx.auth?.loadCredentials?.() || {};
3214
+ return {
3215
+ toolExecutor: ctx.toolExecutor,
3216
+ listRunnables: () => ctx.toolExecutor?.listRunnables?.() || [],
3217
+ listLocalWorkflows: () => listLocalWorkflows(safeCwd()),
3218
+ renderEvent,
3219
+ sessionSubstrate: makeSessionSubstrate(ctx),
3220
+ auth: { token: creds.token || null },
3221
+ credentials: {
3222
+ apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
3223
+ openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
3224
+ },
3225
+ cwd: safeCwd(),
3226
+ };
3227
+ }
3228
+
3189
3229
  function stripWrappingQuotes(value = '') {
3190
3230
  const text = String(value || '').trim();
3191
3231
  if (text.length >= 2) {
@@ -3215,10 +3255,7 @@ function printRunUsage(ctx) {
3215
3255
  process.stderr.write(` ${c.gray('Example: /run docker-analyzer Analyze all running Docker containers')}\n`);
3216
3256
 
3217
3257
  const targets = new Map();
3218
- for (const agent of listLocalAgents(safeCwd())) addRunTarget(targets, agent);
3219
- for (const agent of BUILTIN_AGENTS) addRunTarget(targets, agent);
3220
3258
  for (const agent of ctx.toolExecutor?.listRunnables?.() || []) addRunTarget(targets, agent);
3221
- for (const agent of pluginRegistry?.listAgents?.() || []) addRunTarget(targets, agent);
3222
3259
 
3223
3260
  const agents = [...targets.values()].filter(item => item.kind === 'agent').slice(0, 12);
3224
3261
  if (agents.length) {
@@ -3251,28 +3288,8 @@ async function handleRunCommand(rest = '', ctx) {
3251
3288
  return;
3252
3289
  }
3253
3290
 
3254
- const localAgent = listLocalAgents(safeCwd()).find(agent => localAgentMatches(agent, target));
3255
- const builtinAgent = findBuiltinAgent(target);
3256
- const registeredAgent = ctx.toolExecutor?.listRunnables?.()
3257
- ?.find(agent => localAgentMatches(agent, target));
3258
- const pluginAgent = pluginRegistry?.listAgents?.()
3259
- ?.find(agent => localAgentMatches(agent, target));
3260
- const runnableAgent = localAgent || builtinAgent || registeredAgent || pluginAgent;
3261
-
3262
- const creds = ctx.auth?.loadCredentials?.() || {};
3263
- const dispatchCtx = {
3264
- toolExecutor: ctx.toolExecutor,
3265
- listRunnables: () => ctx.toolExecutor?.listRunnables?.() || [],
3266
- listLocalWorkflows: () => listLocalWorkflows(safeCwd()),
3267
- renderEvent,
3268
- sessionSubstrate: makeSessionSubstrate(ctx),
3269
- auth: { token: creds.token || null },
3270
- credentials: {
3271
- apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
3272
- openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
3273
- },
3274
- cwd: safeCwd(),
3275
- };
3291
+ const runnableAgent = ctx.toolExecutor?.findAgent?.(target) || null;
3292
+ const dispatchCtx = makeDispatchContext(ctx);
3276
3293
 
3277
3294
  try {
3278
3295
  if (!runnableAgent) process.stderr.write(` ${c.dim(`Running workflow '${target}'...`)}\n`);
@@ -4254,7 +4271,7 @@ async function handleCommand(input, ctx) {
4254
4271
  process.stderr.write(` ${c.gray(`Example: ${cmd} ${cmd === '/explore' ? 'how does authentication work?' : cmd === '/review' ? 'check src/core/ for bugs' : 'design a caching layer'}`)}\n`);
4255
4272
  return;
4256
4273
  }
4257
- return await runAgent(cmd.slice(1), rest, ctx, session, renderEvent);
4274
+ return await handleRunCommand(`${cmd.slice(1)} ${rest}`, ctx);
4258
4275
  }
4259
4276
 
4260
4277
  case '/logout': {
@@ -4335,6 +4352,18 @@ export async function startTerminalRepl() {
4335
4352
  return res;
4336
4353
  };
4337
4354
 
4355
+ async function runDelegateFromTool({ agent, slug, instruction, context = {}, options = {} }) {
4356
+ const dispatchCtx = makeDispatchContext(ctx);
4357
+ return await dispatch({
4358
+ type: 'invoke',
4359
+ source: 'tool:delegate',
4360
+ target: { kind: 'agent', slug: slug || agent?.slug, agent },
4361
+ params: { instruction, context },
4362
+ channel: 'local',
4363
+ signal: options.signal,
4364
+ }, dispatchCtx);
4365
+ }
4366
+
4338
4367
  function makeToolExecutor({ showIndexStatus = false } = {}) {
4339
4368
  const shouldShowIndexStatus = showIndexStatus && process.stderr.isTTY && !term().plain;
4340
4369
  let stopIndexSpinner = null;
@@ -4342,6 +4371,7 @@ export async function startTerminalRepl() {
4342
4371
  checkpoints,
4343
4372
  hookRunner,
4344
4373
  pluginRegistry,
4374
+ delegateRunner: runDelegateFromTool,
4345
4375
  interactionHandler: askUserInteraction,
4346
4376
  onAutoRegisterStart: shouldShowIndexStatus ? (root) => {
4347
4377
  const name = path.basename(root || safeCwd()) || root || 'project';
@@ -4378,20 +4408,7 @@ export async function startTerminalRepl() {
4378
4408
  // agent through the trigger funnel when they exit. The ctx builder runs
4379
4409
  // lazily at fire time so it sees the live tool executor.
4380
4410
  registerJobCompletionDispatch(() => {
4381
- const creds = ctx.auth?.loadCredentials?.() || {};
4382
- return {
4383
- toolExecutor: ctx.toolExecutor,
4384
- listRunnables: () => ctx.toolExecutor?.listRunnables?.() || [],
4385
- listLocalWorkflows: () => listLocalWorkflows(safeCwd()),
4386
- renderEvent,
4387
- sessionSubstrate: makeSessionSubstrate(ctx),
4388
- auth: { token: creds.token || null },
4389
- credentials: {
4390
- apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
4391
- openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
4392
- },
4393
- cwd: safeCwd(),
4394
- };
4411
+ return makeDispatchContext(ctx);
4395
4412
  });
4396
4413
 
4397
4414
  let startupOutputRow = 1;
@@ -16,6 +16,15 @@ function stripAnsi(str) {
16
16
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
17
17
  }
18
18
 
19
+ function trimShellBackgroundOperator(command) {
20
+ const raw = String(command || '');
21
+ let i = raw.length - 1;
22
+ while (i >= 0 && /\s/.test(raw[i])) i -= 1;
23
+ if (raw[i] !== '&') return raw;
24
+ if (raw[i - 1] === '&' || raw[i - 1] === '\\') return raw;
25
+ return raw.slice(0, i).trimEnd();
26
+ }
27
+
19
28
  const MAX_OUTPUT_BYTES = 1024 * 1024; // 1MB
20
29
  const TIMEOUT_TAIL_BYTES = 64 * 1024;
21
30
  const TRUNCATION_MARKER = '\n[output truncated at 1MB]';
@@ -44,7 +53,7 @@ export const BashTool = {
44
53
  const abortSignal = input.signal || input._signal || null;
45
54
 
46
55
  if (input.run_in_background) {
47
- return runBackground(input.command, input.cwd);
56
+ return runBackground(trimShellBackgroundOperator(input.command), input.cwd);
48
57
  }
49
58
 
50
59
  if (abortSignal?.aborted) {
@@ -200,6 +209,12 @@ function killProcess(proc, signal) {
200
209
  try { proc.kill(signal); } catch { /* already exited */ }
201
210
  }
202
211
 
212
+ function unrefChildStdio(proc) {
213
+ for (const stream of [proc?.stdout, proc?.stderr]) {
214
+ try { stream?.unref?.(); } catch { /* best effort */ }
215
+ }
216
+ }
217
+
203
218
  // Background jobs store
204
219
  const backgroundJobs = new Map();
205
220
  let bgJobId = 0;
@@ -227,6 +242,7 @@ function runBackground(command, cwd) {
227
242
  });
228
243
 
229
244
  proc.unref();
245
+ unrefChildStdio(proc);
230
246
  return `Background job started: id=${id}, pid=${proc.pid}`;
231
247
  }
232
248
 
@@ -704,11 +704,12 @@ export function clearInputPrompt() {
704
704
  export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null, fixedRows = null } = {}) {
705
705
  if (!mounted) return false;
706
706
  contentTrackingActive = false;
707
+ lastFrame = { ...lastFrame, context, tips, meta, prefix, value, cursor, overlayLines: null };
707
708
  const requestedRows = fixedRows == null
708
709
  ? computeInputRowsForBuffer(prefix, value)
709
710
  : Math.max(MIN_INPUT_ROWS, Math.min(inputRowsMax, Math.floor(Number(fixedRows) || MIN_INPUT_ROWS)));
710
711
  setInputRowsTo(requestedRows);
711
- renderFrame({ context, tips, meta, prefix, value, cursor, overlayLines: null });
712
+ renderFrame(lastFrame);
712
713
  const layout = layoutInput(prefix, value);
713
714
  drawInputLines(layout.lines);
714
715
  focusDockInput(prefix, value, cursor);