@bahulam/code 0.1.23 → 0.1.24

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bahulam/code",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "Bahulam Code — abundance, in your terminal. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -152,8 +152,13 @@ export function createToolExecutor({
152
152
  return project.resource.root;
153
153
  }
154
154
 
155
- async function commandCwd(args = {}) {
156
- return await resolvePath(args.cwd || null, args);
155
+ async function commandCwd(args = {}, { readOnly = false } = {}) {
156
+ try {
157
+ return await resolvePath(args.cwd || null, args);
158
+ } catch (err) {
159
+ if (readOnly) return args.cwd ? path.resolve(args.cwd) : process.cwd();
160
+ throw err;
161
+ }
157
162
  }
158
163
 
159
164
  function shellTargetPath(cwd, target) {
@@ -1390,7 +1395,7 @@ export function createToolExecutor({
1390
1395
  args._riskReason = classification.reason || shellCheck.reason;
1391
1396
  }
1392
1397
  args._classification = classification.classification; // 'safe' or 'contained'
1393
- const cwd = await commandCwd(args);
1398
+ const cwd = await commandCwd(args, { readOnly: classification.classification === 'safe' });
1394
1399
 
1395
1400
  // Background execution: start via the BackgroundTasks registry
1396
1401
  // and return immediately. Safety checks above still apply;
@@ -755,6 +755,13 @@ export class LocalAgentRelay {
755
755
  _makeWorkspaceSessionSubstrate(pluginRegistry) {
756
756
  return (agent, node, instruction, { scopedExecutor } = {}) => (async function* (relay) {
757
757
  const execContext = await relay._buildExecContext(instruction);
758
+ // Per-agent model override from YAML — same pattern as agents.mjs:330.
759
+ // If the agent definition declares a `model` field, it wins over
760
+ // the session default for this sub-agent's turn.
761
+ if (agent.model) execContext.model_override = agent.model;
762
+ if (agent.models && typeof agent.models === 'object' && Object.keys(agent.models).length) {
763
+ execContext.model_overrides = { ...(execContext.model_overrides || {}), ...agent.models };
764
+ }
758
765
  const slug = agent.slug || agent.command || agent.name || node?.agent_slug || node?.id || 'agent';
759
766
  execContext.sub_agent = {
760
767
  slug,
@@ -238,6 +238,24 @@ const EXACT_SAFE = new Set([
238
238
  'python --version', 'python3 --version',
239
239
  ]);
240
240
 
241
+ /** Tools commonly invoked with -version/--version to probe the installed version.
242
+ * These are pure reads (no file access, no network) and should never require
243
+ * project-scoping — they probe the local system's SDK/toolchain. */
244
+ const VERSION_PROBE_TOOLS = new Set([
245
+ 'java', 'javac', 'python', 'python3', 'node', 'ruby', 'go', 'rustc',
246
+ 'deno', 'bun', 'php', 'perl', 'gcc', 'clang', 'make', 'cmake', 'mvn',
247
+ 'gradle', 'pip', 'npm', 'yarn', 'pnpm', 'cargo', 'swift', 'kotlin',
248
+ ]);
249
+
250
+ /** Absolute paths to system probes that are safe to invoke for read-only
251
+ * version/path queries. These live outside any project root and should not
252
+ * trigger project-scope errors. */
253
+ const SAFE_ABS_PROBE_PATHS = new Set([
254
+ '/usr/libexec/java_home',
255
+ '/usr/bin/xcode-select',
256
+ '/usr/bin/which',
257
+ ]);
258
+
241
259
  /**
242
260
  * Commands with allowed flags — allowlist approach.
243
261
  * Key: command name (or "git diff" for multi-word).
@@ -682,6 +700,23 @@ function classifySingleCommand(command) {
682
700
  return { classification: 'safe', reason: 'Echo without expansion' };
683
701
  }
684
702
 
703
+ // ── Version probe: <tool> -version / --version (pure OS read, no project needed) ──
704
+ if (VERSION_PROBE_TOOLS.has(baseCmd)) {
705
+ const token = tokenize(trimmed);
706
+ const flag = token[1] || '';
707
+ if (/^--?v(ersion)?$/.test(flag)) {
708
+ return { classification: 'safe', reason: `Version probe: ${baseCmd}` };
709
+ }
710
+ }
711
+
712
+ // ── Known system-probe absolute paths (e.g. /usr/libexec/java_home -V) ──
713
+ if (SAFE_ABS_PROBE_PATHS.has(baseCmd)) {
714
+ const rest = trimmed.slice(baseCmd.length).trim();
715
+ if (!rest || /^--?[a-z]/i.test(rest)) {
716
+ return { classification: 'safe', reason: `System probe: ${baseCmd}` };
717
+ }
718
+ }
719
+
685
720
  // ── Default: contained (unknown command, not explicitly blocked) ──
686
721
  return { classification: 'contained', reason: `Unknown command, defaulting to contained: ${baseCmd}` };
687
722
  }
@@ -10,6 +10,17 @@
10
10
  */
11
11
  import { spawn } from 'child_process';
12
12
 
13
+ /**
14
+ * Resolve shell binary and args for the current platform.
15
+ * Windows uses cmd.exe; Linux/macOS use bash.
16
+ */
17
+ function shellSpawnArgs(command) {
18
+ if (process.platform === 'win32') {
19
+ return ['cmd.exe', ['/d', '/s', '/c', command]];
20
+ }
21
+ return ['bash', ['-c', command]];
22
+ }
23
+
13
24
  // Strip ANSI escape sequences
14
25
  function stripAnsi(str) {
15
26
  // eslint-disable-next-line no-control-regex
@@ -73,7 +84,8 @@ export const BashTool = {
73
84
  let killTimer = null;
74
85
  let settled = false;
75
86
 
76
- const proc = spawn('bash', ['-c', input.command], {
87
+ const [shellBin, shellArgs] = shellSpawnArgs(input.command);
88
+ const proc = spawn(shellBin, shellArgs, {
77
89
  cwd: input.cwd,
78
90
  env: { ...process.env },
79
91
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -221,7 +233,8 @@ let bgJobId = 0;
221
233
 
222
234
  function runBackground(command, cwd) {
223
235
  const id = ++bgJobId;
224
- const proc = spawn('bash', ['-c', command], {
236
+ const [shellBin, shellArgs] = shellSpawnArgs(command);
237
+ const proc = spawn(shellBin, shellArgs, {
225
238
  cwd,
226
239
  detached: true,
227
240
  stdio: ['ignore', 'pipe', 'pipe'],