brass-runtime 1.20.0 → 1.21.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.0 - Agent Intelligence Layer
4
+
5
+ ### Features
6
+
7
+ - **LLM Budget Optimization** (`src/agent/core/llmBudget/`): Token budget tracking,
8
+ response confidence estimation, and complexity-based model routing for brass-agent.
9
+ - `resolveBudgetConfig` / `validateBudgetConfig`: merge goal+config budgets with defaults,
10
+ reject invalid inputs at construction time.
11
+ - `BudgetState` tracking: immutable accumulation of token usage across LLM calls with
12
+ three-zone classification (under / warning / exceeded).
13
+ - `estimateTokens`: chars/4 fallback when providers don't report usage.
14
+ - `estimateConfidence`: heuristic scoring (diff blocks, conciseness, goal references,
15
+ hedging language) for response quality signals.
16
+ - `routeModel`: complexity-based "small" vs "large" tier selection using goal length,
17
+ files read, search matches, validation errors, and repair attempts.
18
+ - Budget gate in `runAgent`: intercepts `llm.complete` actions, emits structured events
19
+ (`budget.usage`, `budget.routed`, `budget.confidence`, `budget.warning`, `budget.exceeded`),
20
+ and gracefully terminates on hard cap with phase-aware summaries.
21
+ - `LearningStore` persistence: cross-run history at `.brass/llm-budget.json` with cap
22
+ enforcement and corrupt-file recovery.
23
+
24
+ - **Adaptive Context Budget** (`src/agent/core/contextBudget/`): Multi-armed bandit
25
+ prioritization for context discovery actions using Thompson Sampling.
26
+ - Bandit engine with Beta-distributed arms, Bayesian updates, and configurable
27
+ exploration/exploitation tradeoff.
28
+ - Context action scoring and ranking based on historical reward signals.
29
+ - State persistence at `.brass/context-budget.json` with graceful degradation.
30
+
31
+ - **Adaptive Patch Strategy** (`src/agent/core/patchStrategy/`): Feedback-driven patch
32
+ generation strategy selection using reward-weighted history.
33
+ - Strategy selector choosing between diff formats based on historical success rates.
34
+ - Reward tracking with exponential decay for recency weighting.
35
+ - Integration with the patch generation pipeline in `decide.ts`.
36
+
37
+ - **Agent Host LLM Refactor** (`src/agent/core/`): Restructured LLM interaction layer
38
+ for cleaner separation between host profile detection and LLM provider routing.
39
+ - Unified host profile inference with transport-aware defaults.
40
+ - Cleaner LLM request/response pipeline with typed purpose discrimination.
41
+
42
+ ### Testing
43
+
44
+ - 14 property-based tests (fast-check) covering budget config, state accumulation,
45
+ immutability, zone classification, token estimation, confidence bounds/signals,
46
+ model routing, and persistence invariants.
47
+ - 5 integration tests for the budget-aware runner (warning zone, hard cap, no-budget
48
+ passthrough, no-LLM graceful degradation, subsequent call blocking).
49
+ - Full property test suites for context budget bandit engine, patch strategy selector,
50
+ and host LLM refactor modules.
51
+
52
+ ### Validation
53
+
54
+ - `npm run test:types` — 0 errors
55
+ - `npm test` — 1860+ tests passing
56
+
3
57
  ## 1.19.0 - Bare-Metal HTTP Mode
4
58
 
5
59
  ### Features
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict"; function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
3
 
4
4
 
5
5
 
@@ -17,7 +17,14 @@
17
17
 
18
18
 
19
19
 
20
- var _chunkIHY2EJTTcjs = require('../../chunk-IHY2EJTT.cjs');
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+ var _chunkEX4VEKUFcjs = require('../../chunk-EX4VEKUF.cjs');
21
28
  require('../../chunk-SA6HUJVI.cjs');
22
29
 
23
30
 
@@ -28,7 +35,9 @@ var _chunkJKHBEWQAcjs = require('../../chunk-JKHBEWQA.cjs');
28
35
 
29
36
  var _chunkMVGUEJ5Zcjs = require('../../chunk-MVGUEJ5Z.cjs');
30
37
  require('../../chunk-KPOL2YEO.cjs');
31
- require('../../chunk-OBGZSXTJ.cjs');
38
+
39
+
40
+ var _chunkOBGZSXTJcjs = require('../../chunk-OBGZSXTJ.cjs');
32
41
 
33
42
  // src/agent/cli/approvals.ts
34
43
  var dynamicImport = new Function("specifier", "return import(specifier)");
@@ -56,7 +65,7 @@ var makeCliApprovalService = (options = {}) => ({
56
65
  _optionalChain([output, 'optionalAccess', _ => _.write, 'optionalCall', _2 => _2(`
57
66
  Approval required (${request.risk})
58
67
  `)]);
59
- _optionalChain([output, 'optionalAccess', _3 => _3.write, 'optionalCall', _4 => _4(`Action: ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, request.action)}
68
+ _optionalChain([output, 'optionalAccess', _3 => _3.write, 'optionalCall', _4 => _4(`Action: ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, request.action)}
60
69
  `)]);
61
70
  _optionalChain([output, 'optionalAccess', _5 => _5.write, 'optionalCall', _6 => _6(`Reason: ${request.reason}
62
71
  `)]);
@@ -242,8 +251,8 @@ var llmCheck = (config) => {
242
251
  return {
243
252
  id: "llm",
244
253
  label: "LLM provider",
245
- status: keyPresent ? "ok" : "fail",
246
- message: keyPresent ? `Google/Gemini provider is configured (${provider}).` : "Google/Gemini provider is selected but no API key env var is set. Export GEMINI_API_KEY or put it in .env/.brass-agent.env."
254
+ status: keyPresent ? "ok" : "warn",
255
+ message: keyPresent ? `Google/Gemini provider is configured (${provider}).` : "No LLM provider configured. Tool-only workflows will work; planning requires LLM credentials."
247
256
  };
248
257
  }
249
258
  if (provider === "openai" || provider === "openai-compatible") {
@@ -252,8 +261,8 @@ var llmCheck = (config) => {
252
261
  return {
253
262
  id: "llm",
254
263
  label: "LLM provider",
255
- status: endpointPresent && keyPresent ? "ok" : "fail",
256
- message: endpointPresent && keyPresent ? `OpenAI-compatible provider is configured (${provider}).` : "OpenAI-compatible provider is selected but endpoint or API key env var is missing. Export BRASS_LLM_API_KEY or put it in .env/.brass-agent.env."
264
+ status: endpointPresent && keyPresent ? "ok" : "warn",
265
+ message: endpointPresent && keyPresent ? `OpenAI-compatible provider is configured (${provider}).` : "No LLM provider configured. Tool-only workflows will work; planning requires LLM credentials."
257
266
  };
258
267
  }
259
268
  if (hasEnv("BRASS_GOOGLE_API_KEY") || hasEnv("GOOGLE_API_KEY") || hasEnv("GEMINI_API_KEY")) {
@@ -276,7 +285,7 @@ var llmCheck = (config) => {
276
285
  id: "llm",
277
286
  label: "LLM provider",
278
287
  status: "warn",
279
- message: "No real LLM credentials found. The CLI will fall back to the fake provider unless config selects a real provider."
288
+ message: "No LLM provider configured. Tool-only workflows will work; planning requires LLM credentials."
280
289
  };
281
290
  };
282
291
  var workspaceSettingsCommand = (cwd) => {
@@ -967,6 +976,73 @@ var printAgentInitResult = (result) => {
967
976
  }
968
977
  };
969
978
 
979
+ // src/agent/core/patchStrategy/reward.ts
980
+ var observationsAfterPatch = (state) => {
981
+ const patchIndex = [...state.observations].map((obs) => obs.type).lastIndexOf("patch.applied");
982
+ return patchIndex < 0 ? [] : state.observations.slice(patchIndex + 1);
983
+ };
984
+ var hasPatchProduced = (state) => state.observations.some((obs) => obs.type === "patch.applied");
985
+ var computeReward = (state) => {
986
+ if (!hasPatchProduced(state)) {
987
+ return 0;
988
+ }
989
+ const commands = _chunkEX4VEKUFcjs.discoverValidationCommands.call(void 0, state).validationCommands;
990
+ const status = _chunkEX4VEKUFcjs.patchValidationStatus.call(void 0, commands, observationsAfterPatch(state));
991
+ const quality = _chunkEX4VEKUFcjs.patchQualitySummary.call(void 0, state);
992
+ if (status.type === "not-run") {
993
+ return 1;
994
+ }
995
+ if (status.type === "pending") {
996
+ return 0;
997
+ }
998
+ if (status.type === "passed") {
999
+ if (quality.repairAttemptsUsed > 0 && quality.maxRepairAttempts > 0) {
1000
+ const reward = 1 - quality.repairAttemptsUsed / quality.maxRepairAttempts;
1001
+ return Math.max(0, Math.min(1, reward));
1002
+ }
1003
+ return 1;
1004
+ }
1005
+ return 0;
1006
+ };
1007
+
1008
+ // src/agent/core/patchStrategy/store.ts
1009
+
1010
+
1011
+ var STORE_PATH = ".brass/patch-strategy.json";
1012
+ var loadRewardStore = async (cwd) => {
1013
+ try {
1014
+ const filePath = _path.join.call(void 0, cwd, STORE_PATH);
1015
+ const raw = await _promises.readFile.call(void 0, filePath, "utf-8");
1016
+ const data = JSON.parse(raw);
1017
+ if (typeof data !== "object" || data === null || !("version" in data) || data.version !== 1) {
1018
+ return [];
1019
+ }
1020
+ const stored = data;
1021
+ if (!Array.isArray(stored.entries)) {
1022
+ return [];
1023
+ }
1024
+ return stored.entries;
1025
+ } catch (err) {
1026
+ return [];
1027
+ }
1028
+ };
1029
+ var flushRewardStore = async (cwd, entries) => {
1030
+ try {
1031
+ const filePath = _path.join.call(void 0, cwd, STORE_PATH);
1032
+ const data = {
1033
+ version: 1,
1034
+ entries
1035
+ };
1036
+ await _promises.mkdir.call(void 0, _path.dirname.call(void 0, filePath), { recursive: true });
1037
+ await _promises.writeFile.call(void 0, filePath, JSON.stringify(data, null, 2), "utf-8");
1038
+ } catch (err) {
1039
+ console.warn(
1040
+ "[patchStrategy] Failed to flush reward store:",
1041
+ err instanceof Error ? err.message : String(err)
1042
+ );
1043
+ }
1044
+ };
1045
+
970
1046
  // src/agent/cli/main.ts
971
1047
  var dynamicImport2 = new Function("specifier", "return import(specifier)");
972
1048
  var readPatchFile = async (cwd, patchFile) => {
@@ -999,6 +1075,7 @@ var parseCliArgs = (argv) => {
999
1075
  let modeSpecified = false;
1000
1076
  let showHelp = false;
1001
1077
  let output = "human";
1078
+ let outputSpecified = false;
1002
1079
  let approval = "auto";
1003
1080
  let approvalSpecified = false;
1004
1081
  let configPath;
@@ -1020,6 +1097,7 @@ var parseCliArgs = (argv) => {
1020
1097
  let initProfile = "default";
1021
1098
  let initDryRun = false;
1022
1099
  let language;
1100
+ let hostProfile = false;
1023
1101
  const goalParts = [];
1024
1102
  for (let index = 0; index < argv.length; index += 1) {
1025
1103
  const arg = argv[index];
@@ -1035,6 +1113,10 @@ var parseCliArgs = (argv) => {
1035
1113
  doctor = true;
1036
1114
  continue;
1037
1115
  }
1116
+ if (arg === "--host-profile") {
1117
+ hostProfile = true;
1118
+ continue;
1119
+ }
1038
1120
  if (arg === "--where" || arg === "--print-workspace") {
1039
1121
  where = true;
1040
1122
  continue;
@@ -1087,14 +1169,17 @@ var parseCliArgs = (argv) => {
1087
1169
  }
1088
1170
  if (arg === "--json") {
1089
1171
  output = "json";
1172
+ outputSpecified = true;
1090
1173
  continue;
1091
1174
  }
1092
1175
  if (arg === "--events-json") {
1093
1176
  output = "events-json";
1177
+ outputSpecified = true;
1094
1178
  continue;
1095
1179
  }
1096
1180
  if (arg === "--protocol-json") {
1097
1181
  output = "protocol-json";
1182
+ outputSpecified = true;
1098
1183
  continue;
1099
1184
  }
1100
1185
  if (arg === "--protocol-full-patches") {
@@ -1103,7 +1188,7 @@ var parseCliArgs = (argv) => {
1103
1188
  }
1104
1189
  if (arg === "--preset" || arg.startsWith("--preset=")) {
1105
1190
  const [value, nextIndex] = readFlagValue(argv, index, "--preset");
1106
- if (!_chunkIHY2EJTTcjs.isAgentPreset.call(void 0, value)) {
1191
+ if (!_chunkEX4VEKUFcjs.isAgentPreset.call(void 0, value)) {
1107
1192
  throw new Error("--preset requires one of: fix-tests, inspect, typecheck, lint");
1108
1193
  }
1109
1194
  preset = value;
@@ -1241,6 +1326,7 @@ var parseCliArgs = (argv) => {
1241
1326
  modeSpecified,
1242
1327
  showHelp,
1243
1328
  output,
1329
+ outputSpecified,
1244
1330
  approval,
1245
1331
  approvalSpecified,
1246
1332
  ...configPath ? { configPath } : {},
@@ -1259,6 +1345,7 @@ var parseCliArgs = (argv) => {
1259
1345
  initForce,
1260
1346
  initProfile,
1261
1347
  initDryRun,
1348
+ hostProfile,
1262
1349
  ...language ? { language } : {},
1263
1350
  ...saveRunDir ? { saveRunDir } : {},
1264
1351
  ...patchFile ? { patchFile } : {}
@@ -1279,7 +1366,7 @@ var parseBatchGoal = (value, path) => {
1279
1366
  if (cwd !== void 0 && typeof cwd !== "string") throw new Error(`${path}.cwd must be a string.`);
1280
1367
  if (patchFile !== void 0 && typeof patchFile !== "string") throw new Error(`${path}.patchFile must be a string.`);
1281
1368
  if (saveRunDir !== void 0 && typeof saveRunDir !== "string") throw new Error(`${path}.saveRunDir must be a string.`);
1282
- if (preset !== void 0 && (typeof preset !== "string" || !_chunkIHY2EJTTcjs.isAgentPreset.call(void 0, preset))) {
1369
+ if (preset !== void 0 && (typeof preset !== "string" || !_chunkEX4VEKUFcjs.isAgentPreset.call(void 0, preset))) {
1283
1370
  throw new Error(`${path}.preset must be one of: fix-tests, inspect, typecheck, lint.`);
1284
1371
  }
1285
1372
  if (mode !== void 0 && (typeof mode !== "string" || !isAgentMode(mode))) {
@@ -1323,7 +1410,7 @@ var readBatchFile = async (cwd, batchFile) => {
1323
1410
  var resolveBatchGoalText = (item, fallbackPatchFile) => {
1324
1411
  if (typeof item === "string") return item;
1325
1412
  if (item.goal) return item.goal;
1326
- if (item.preset) return _chunkIHY2EJTTcjs.goalForAgentPreset.call(void 0, item.preset);
1413
+ if (item.preset) return _chunkEX4VEKUFcjs.goalForAgentPreset.call(void 0, item.preset);
1327
1414
  if (_nullishCoalesce(item.patchFile, () => ( fallbackPatchFile))) return "apply supplied patch";
1328
1415
  return "";
1329
1416
  };
@@ -1350,7 +1437,7 @@ var resolveBatchRuns = (items, parsed, config) => items.map((item, index) => {
1350
1437
  };
1351
1438
  });
1352
1439
  var resolveParsedConfig = async (parsed) => {
1353
- const workspaceDiscovery = _chunkIHY2EJTTcjs.discoverNodeWorkspaceRoot.call(void 0, parsed.cwd, {
1440
+ const workspaceDiscovery = _chunkEX4VEKUFcjs.discoverNodeWorkspaceRoot.call(void 0, parsed.cwd, {
1354
1441
  enabled: parsed.discoverWorkspace
1355
1442
  });
1356
1443
  const cwdResolved = workspaceDiscovery.cwd;
@@ -1358,7 +1445,7 @@ var resolveParsedConfig = async (parsed) => {
1358
1445
  ...parsed,
1359
1446
  cwd: cwdResolved
1360
1447
  };
1361
- const loaded = await _chunkIHY2EJTTcjs.loadNodeAgentConfig.call(void 0, {
1448
+ const loaded = await _chunkEX4VEKUFcjs.loadNodeAgentConfig.call(void 0, {
1362
1449
  cwd: cwdResolved,
1363
1450
  configPath: parsed.configPath,
1364
1451
  noConfig: parsed.noConfig
@@ -1374,7 +1461,7 @@ var resolveParsedConfig = async (parsed) => {
1374
1461
  const batchRuns = resolveBatchRuns(batchItems, parsedAtWorkspace, loaded.config);
1375
1462
  return {
1376
1463
  ...parsedAtWorkspace,
1377
- goalText: parsed.goalText || (parsed.preset ? _chunkIHY2EJTTcjs.goalForAgentPreset.call(void 0, parsed.preset) : parsed.patchFile ? "apply supplied patch" : parsed.goalText),
1464
+ goalText: parsed.goalText || (parsed.preset ? _chunkEX4VEKUFcjs.goalForAgentPreset.call(void 0, parsed.preset) : parsed.patchFile ? "apply supplied patch" : parsed.goalText),
1378
1465
  mode: parsed.modeSpecified ? parsed.mode : parsed.preset === "inspect" ? "read-only" : _nullishCoalesce(loaded.config.mode, () => ( parsed.mode)),
1379
1466
  approval: parsed.approvalSpecified ? parsed.approval : _nullishCoalesce(loaded.config.approval, () => ( parsed.approval)),
1380
1467
  config: loaded.config,
@@ -1494,11 +1581,96 @@ var printHelp = () => {
1494
1581
  " BRASS_LLM_PROVIDER=openai-compatible BRASS_LLM_ENDPOINT=... BRASS_LLM_API_KEY=..."
1495
1582
  ].join("\n"));
1496
1583
  };
1584
+ var KNOWN_WORKSPACE_MARKERS = [".vscode", ".cursor", ".kiro", ".git", ".github", ".gitlab-ci.yml", "Jenkinsfile"];
1585
+ var scanWorkspaceMarkers = (cwd) => {
1586
+ try {
1587
+ const fs = _chunkOBGZSXTJcjs.__require.call(void 0, "fs");
1588
+ const path = _chunkOBGZSXTJcjs.__require.call(void 0, "path");
1589
+ const found = [];
1590
+ for (const marker of KNOWN_WORKSPACE_MARKERS) {
1591
+ if (fs.existsSync(path.join(cwd, marker))) {
1592
+ found.push(marker);
1593
+ }
1594
+ }
1595
+ return found;
1596
+ } catch (e4) {
1597
+ return [];
1598
+ }
1599
+ };
1600
+ var buildHostProfileFromProcess = (resolved) => {
1601
+ const workspaceMarkers = scanWorkspaceMarkers(resolved.cwd);
1602
+ const configPaths = [];
1603
+ if (resolved.resolvedConfigPath) {
1604
+ configPaths.push(resolved.resolvedConfigPath);
1605
+ }
1606
+ const input = {
1607
+ argv: process.argv,
1608
+ env: process.env,
1609
+ stdoutIsTTY: _nullishCoalesce(process.stdout.isTTY, () => ( false)),
1610
+ stdinIsTTY: _nullishCoalesce(process.stdin.isTTY, () => ( false)),
1611
+ ttyColumns: process.stdout.columns,
1612
+ parentProcessName: void 0,
1613
+ workspaceMarkers,
1614
+ stdinFirstLine: void 0,
1615
+ configPaths
1616
+ };
1617
+ return _chunkEX4VEKUFcjs.buildHostProfile.call(void 0, input);
1618
+ };
1619
+ var printHostProfileHuman = (profile) => {
1620
+ console.log("Transport: " + profile.transport);
1621
+ console.log("");
1622
+ console.log("Capabilities:");
1623
+ const caps = profile.capabilities;
1624
+ console.log(" hasOwnLLM: " + caps.hasOwnLLM);
1625
+ console.log(" wantsJson: " + caps.wantsJson);
1626
+ console.log(" supportsStreamingEvents: " + caps.supportsStreamingEvents);
1627
+ console.log(" supportsMcp: " + caps.supportsMcp);
1628
+ console.log(" canAskApproval: " + caps.canAskApproval);
1629
+ console.log(" canRenderDiff: " + caps.canRenderDiff);
1630
+ console.log(" canApplyPatch: " + caps.canApplyPatch);
1631
+ console.log(" interactiveTty: " + caps.interactiveTty);
1632
+ console.log("");
1633
+ console.log("Constraints:");
1634
+ const con = profile.constraints;
1635
+ console.log(" readOnlyByDefault: " + con.readOnlyByDefault);
1636
+ console.log(" patchPreviewRequired: " + con.patchPreviewRequired);
1637
+ console.log(" requireNoNetwork: " + con.requireNoNetwork);
1638
+ console.log("");
1639
+ console.log("Identity:");
1640
+ if (profile.identity) {
1641
+ console.log(" name: " + profile.identity.name);
1642
+ console.log(" confidence: " + profile.identity.confidence);
1643
+ } else {
1644
+ console.log(" (none detected)");
1645
+ }
1646
+ };
1647
+ var applyCapabilityDefaults = (resolved) => {
1648
+ const profile = buildHostProfileFromProcess(resolved);
1649
+ const caps = profile.capabilities;
1650
+ let output = resolved.output;
1651
+ let approval = resolved.approval;
1652
+ if (!resolved.outputSpecified) {
1653
+ if (caps.supportsStreamingEvents) {
1654
+ output = "events-json";
1655
+ } else if (caps.wantsJson) {
1656
+ output = "json";
1657
+ }
1658
+ }
1659
+ if (!resolved.approvalSpecified) {
1660
+ if (!caps.canAskApproval) {
1661
+ approval = "deny";
1662
+ }
1663
+ }
1664
+ if (output === resolved.output && approval === resolved.approval) {
1665
+ return resolved;
1666
+ }
1667
+ return { ...resolved, output, approval };
1668
+ };
1497
1669
  var envByName = (name) => name ? process.env[name] : void 0;
1498
1670
  var makeGoogleLLMFromEnv = (config) => {
1499
1671
  const apiKey = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(envByName(_optionalChain([config, 'optionalAccess', _39 => _39.apiKeyEnv])), () => ( process.env.BRASS_GOOGLE_API_KEY)), () => ( process.env.GOOGLE_API_KEY)), () => ( process.env.GEMINI_API_KEY));
1500
1672
  if (!apiKey) return void 0;
1501
- return _chunkIHY2EJTTcjs.makeGoogleGenerativeAILLM.call(void 0, {
1673
+ return _chunkEX4VEKUFcjs.makeGoogleGenerativeAILLM.call(void 0, {
1502
1674
  apiKey,
1503
1675
  model: _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(process.env.BRASS_GOOGLE_MODEL, () => ( process.env.BRASS_LLM_MODEL)), () => ( _optionalChain([config, 'optionalAccess', _40 => _40.model]))), () => ( "gemini-2.5-flash")),
1504
1676
  apiVersion: _nullishCoalesce(_nullishCoalesce(process.env.BRASS_GOOGLE_API_VERSION, () => ( _optionalChain([config, 'optionalAccess', _41 => _41.apiVersion]))), () => ( "v1beta")),
@@ -1516,12 +1688,12 @@ var makeOpenAICompatibleLLMFromEnv = (config) => {
1516
1688
  const apiKey = _nullishCoalesce(envByName(_optionalChain([config, 'optionalAccess', _50 => _50.apiKeyEnv])), () => ( process.env.BRASS_LLM_API_KEY));
1517
1689
  const model = _nullishCoalesce(_nullishCoalesce(process.env.BRASS_LLM_MODEL, () => ( _optionalChain([config, 'optionalAccess', _51 => _51.model]))), () => ( "gpt-4.1"));
1518
1690
  if (!endpoint || !apiKey) return void 0;
1519
- return _chunkIHY2EJTTcjs.makeOpenAICompatibleLLM.call(void 0, { endpoint, apiKey, model });
1691
+ return _chunkEX4VEKUFcjs.makeOpenAICompatibleLLM.call(void 0, { endpoint, apiKey, model });
1520
1692
  };
1521
- var makeLLMFromEnv = (config) => {
1693
+ var makeLLMFromEnvOptional = (config) => {
1522
1694
  const provider = _optionalChain([(_nullishCoalesce(process.env.BRASS_LLM_PROVIDER, () => ( _optionalChain([config, 'optionalAccess', _52 => _52.provider])))), 'optionalAccess', _53 => _53.trim, 'call', _54 => _54(), 'access', _55 => _55.toLowerCase, 'call', _56 => _56()]);
1523
1695
  const fakeResponse = _nullishCoalesce(process.env.BRASS_FAKE_LLM_RESPONSE, () => ( _optionalChain([config, 'optionalAccess', _57 => _57.fakeResponse])));
1524
- if (provider === "fake") return _chunkIHY2EJTTcjs.makeFakeLLM.call(void 0, { content: fakeResponse });
1696
+ if (provider === "fake") return _chunkEX4VEKUFcjs.makeFakeLLM.call(void 0, { content: fakeResponse });
1525
1697
  if (provider === "google" || provider === "gemini") {
1526
1698
  const google = makeGoogleLLMFromEnv(config);
1527
1699
  if (!google) {
@@ -1543,7 +1715,7 @@ var makeLLMFromEnv = (config) => {
1543
1715
  if (provider) {
1544
1716
  throw new Error(`Unsupported LLM provider: ${provider}`);
1545
1717
  }
1546
- return _nullishCoalesce(_nullishCoalesce(makeGoogleLLMFromEnv(config), () => ( makeOpenAICompatibleLLMFromEnv(config))), () => ( _chunkIHY2EJTTcjs.makeFakeLLM.call(void 0, { content: fakeResponse })));
1718
+ return _nullishCoalesce(makeGoogleLLMFromEnv(config), () => ( makeOpenAICompatibleLLMFromEnv(config)));
1547
1719
  };
1548
1720
  var parseApprovalModeFromEnv = () => {
1549
1721
  const raw = _optionalChain([process, 'access', _58 => _58.env, 'access', _59 => _59.BRASS_AGENT_APPROVAL, 'optionalAccess', _60 => _60.trim, 'call', _61 => _61(), 'access', _62 => _62.toLowerCase, 'call', _63 => _63()]);
@@ -1566,9 +1738,9 @@ var makeApprovalServiceFromCli = (parsed) => {
1566
1738
  const mode = resolveApprovalMode(parsed);
1567
1739
  switch (mode) {
1568
1740
  case "approve":
1569
- return _chunkIHY2EJTTcjs.autoApproveApprovals;
1741
+ return _chunkEX4VEKUFcjs.autoApproveApprovals;
1570
1742
  case "deny":
1571
- return _chunkIHY2EJTTcjs.makeAutoDenyApprovals.call(void 0, "Approval rejected because the CLI is running without interactive input. Use --yes to auto-approve.");
1743
+ return _chunkEX4VEKUFcjs.makeAutoDenyApprovals.call(void 0, "Approval rejected because the CLI is running without interactive input. Use --yes to auto-approve.");
1572
1744
  case "interactive":
1573
1745
  return makeCliApprovalService();
1574
1746
  }
@@ -1700,32 +1872,32 @@ var createHumanEventSink = (configPath) => ({
1700
1872
  console.log("");
1701
1873
  break;
1702
1874
  case "agent.action.started":
1703
- console.log(`\u2192 ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)}`);
1875
+ console.log(`\u2192 ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)}`);
1704
1876
  break;
1705
1877
  case "agent.action.completed": {
1706
- const status = _chunkIHY2EJTTcjs.observationStatus.call(void 0, event.observation);
1707
- console.log(`${statusIcon2(status)} ${_chunkIHY2EJTTcjs.summarizeAgentObservation.call(void 0, event.observation)} ${formatDuration(event.durationMs)}`);
1878
+ const status = _chunkEX4VEKUFcjs.observationStatus.call(void 0, event.observation);
1879
+ console.log(`${statusIcon2(status)} ${_chunkEX4VEKUFcjs.summarizeAgentObservation.call(void 0, event.observation)} ${formatDuration(event.durationMs)}`);
1708
1880
  break;
1709
1881
  }
1710
1882
  case "agent.action.failed":
1711
1883
  if (event.error._tag !== "ToolTimeout" && event.error._tag !== "PermissionDenied" && event.error._tag !== "ApprovalRejected") {
1712
- console.log(`\u2717 ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)} failed with ${event.error._tag} ${formatDuration(event.durationMs)}`);
1884
+ console.log(`\u2717 ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)} failed with ${event.error._tag} ${formatDuration(event.durationMs)}`);
1713
1885
  }
1714
1886
  break;
1715
1887
  case "agent.tool.timeout":
1716
- console.log(`! ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)} timed out after ${event.timeoutMs}ms`);
1888
+ console.log(`! ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)} timed out after ${event.timeoutMs}ms`);
1717
1889
  break;
1718
1890
  case "agent.permission.denied":
1719
- console.log(`\u2717 ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)} denied: ${event.reason}`);
1891
+ console.log(`\u2717 ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)} denied: ${event.reason}`);
1720
1892
  break;
1721
1893
  case "agent.approval.requested":
1722
- console.log(`? approval required for ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)} (${event.risk})`);
1894
+ console.log(`? approval required for ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)} (${event.risk})`);
1723
1895
  break;
1724
1896
  case "agent.approval.resolved":
1725
1897
  if (event.approved) {
1726
- console.log(`\u2713 approval granted for ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)}`);
1898
+ console.log(`\u2713 approval granted for ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)}`);
1727
1899
  } else {
1728
- console.log(`\u2717 approval rejected for ${_chunkIHY2EJTTcjs.summarizeAgentAction.call(void 0, event.action)}${event.reason ? `: ${event.reason}` : ""}`);
1900
+ console.log(`\u2717 approval rejected for ${_chunkEX4VEKUFcjs.summarizeAgentAction.call(void 0, event.action)}${event.reason ? `: ${event.reason}` : ""}`);
1729
1901
  }
1730
1902
  break;
1731
1903
  case "agent.patch.applied":
@@ -1830,13 +2002,14 @@ var printHumanFinalSummary = (state) => {
1830
2002
  };
1831
2003
  var makeEventsSink = (parsed, compactOptions) => parsed.output === "human" ? createHumanEventSink(parsed.resolvedConfigPath) : parsed.output === "events-json" ? createJsonEventSink(compactOptions) : parsed.output === "protocol-json" ? createProtocolEventSink(compactOptions) : void 0;
1832
2004
  var makeAgentEnv = (parsed, events) => {
1833
- const shell = _chunkIHY2EJTTcjs.NodeShell;
2005
+ const shell = _chunkEX4VEKUFcjs.NodeShell;
2006
+ const llm = makeLLMFromEnvOptional(parsed.config.llm);
1834
2007
  return {
1835
2008
  shell,
1836
- fs: _chunkIHY2EJTTcjs.makeNodeFileSystem.call(void 0, shell),
1837
- patch: _chunkIHY2EJTTcjs.makeNodePatchService.call(void 0, shell),
1838
- llm: makeLLMFromEnv(parsed.config.llm),
1839
- permissions: _chunkIHY2EJTTcjs.makeConfiguredPermissions.call(void 0, parsed.config.permissions),
2009
+ fs: _chunkEX4VEKUFcjs.makeNodeFileSystem.call(void 0, shell),
2010
+ patch: _chunkEX4VEKUFcjs.makeNodePatchService.call(void 0, shell),
2011
+ llm,
2012
+ permissions: _chunkEX4VEKUFcjs.makeConfiguredPermissions.call(void 0, parsed.config.permissions),
1840
2013
  approvals: makeApprovalServiceFromCli(parsed),
1841
2014
  ...events ? { events } : {},
1842
2015
  ...parsed.config.tools ? { toolPolicies: parsed.config.tools } : {}
@@ -1855,21 +2028,44 @@ var runCliAgent = async (parsed, run, compactOptions, events) => {
1855
2028
  const env = makeAgentEnv(parsed, events);
1856
2029
  const runtime = new (0, _chunkJKHBEWQAcjs.Runtime)({ env });
1857
2030
  const initialPatch = run.patchFile ? await readPatchFile(run.cwd, run.patchFile) : void 0;
2031
+ const rewardHistory = await loadRewardStore(run.cwd);
1858
2032
  const state = await runtime.toPromise(
1859
- _chunkIHY2EJTTcjs.runAgent.call(void 0, runtime, {
2033
+ _chunkEX4VEKUFcjs.runAgent.call(void 0, runtime, {
1860
2034
  id: `agent-${Date.now()}-${run.index + 1}`,
1861
2035
  cwd: run.cwd,
1862
2036
  text: run.goalText,
1863
2037
  mode: run.mode,
2038
+ llmAvailable: env.llm !== void 0,
1864
2039
  ...parsed.config.project ? { project: parsed.config.project } : {},
1865
2040
  ...parsed.config.context ? { context: parsed.config.context } : {},
1866
2041
  ...parsed.config.patchQuality ? { patchQuality: parsed.config.patchQuality } : {},
1867
2042
  ...parsed.config.rollback ? { rollback: parsed.config.rollback } : {},
1868
2043
  ...parsed.config.redaction ? { redaction: parsed.config.redaction } : {},
1869
2044
  ...parsed.language ? { language: { response: parsed.language } } : parsed.config.language ? { language: parsed.config.language } : {},
1870
- ...initialPatch ? { initialPatch, initialPatchMode: run.patchFileMode } : {}
2045
+ ...initialPatch ? { initialPatch, initialPatchMode: run.patchFileMode } : {},
2046
+ ...parsed.config.patchStrategy ? { patchStrategy: parsed.config.patchStrategy } : {},
2047
+ rewardHistory
1871
2048
  })
1872
2049
  );
2050
+ try {
2051
+ if (_optionalChain([state, 'access', _79 => _79.goal, 'access', _80 => _80.patchStrategy, 'optionalAccess', _81 => _81.enabled]) !== false) {
2052
+ const reward = computeReward(state);
2053
+ const signals = _chunkEX4VEKUFcjs.extractSignals.call(void 0, state);
2054
+ const selectedArm = _chunkEX4VEKUFcjs.selectStrategy.call(void 0,
2055
+ signals,
2056
+ state.goal.patchStrategy,
2057
+ rewardHistory,
2058
+ { sampleBeta: (a, b) => _chunkEX4VEKUFcjs.sampleBeta.call(void 0, a, b, Math.random), random: Math.random }
2059
+ );
2060
+ const newEntry = {
2061
+ arm: selectedArm,
2062
+ reward,
2063
+ timestamp: Date.now()
2064
+ };
2065
+ await flushRewardStore(run.cwd, [...rewardHistory, newEntry]);
2066
+ }
2067
+ } catch (e5) {
2068
+ }
1873
2069
  if (run.saveRunDir) {
1874
2070
  await writeRunArtifacts(state, run.saveRunDir, compactOptions);
1875
2071
  }
@@ -1925,7 +2121,8 @@ var printWorkspaceWhere = (parsed) => {
1925
2121
  if (result.envFiles.length > 0) console.log(`env: ${result.envFiles.join(", ")}`);
1926
2122
  };
1927
2123
  var main = async () => {
1928
- const parsed = await resolveParsedConfig(parseCliArgs(process.argv.slice(2)));
2124
+ const rawParsed = await resolveParsedConfig(parseCliArgs(process.argv.slice(2)));
2125
+ const parsed = applyCapabilityDefaults(rawParsed);
1929
2126
  const isBatch = parsed.batchRuns.length > 0;
1930
2127
  if (parsed.showHelp) {
1931
2128
  printHelp();
@@ -1965,6 +2162,22 @@ var main = async () => {
1965
2162
  process.exitCode = report.status === "fail" ? 1 : 0;
1966
2163
  return;
1967
2164
  }
2165
+ if (parsed.hostProfile) {
2166
+ try {
2167
+ const profile = buildHostProfileFromProcess(parsed);
2168
+ if (parsed.output === "json") {
2169
+ console.log(JSON.stringify(profile, null, 2));
2170
+ } else {
2171
+ printHostProfileHuman(profile);
2172
+ }
2173
+ process.exit(0);
2174
+ } catch (err) {
2175
+ const message = err instanceof Error ? err.message : String(err);
2176
+ process.stderr.write(`Error: host profile detection failed: ${message}
2177
+ `);
2178
+ process.exit(1);
2179
+ }
2180
+ }
1968
2181
  if (!parsed.goalText && !isBatch) {
1969
2182
  printHelp();
1970
2183
  process.exit(1);
@@ -2021,7 +2234,16 @@ var main = async () => {
2021
2234
  process.exitCode = result.exitCode;
2022
2235
  }
2023
2236
  };
2024
- main().catch((error) => {
2025
- console.error(error);
2026
- process.exit(1);
2027
- });
2237
+ if (typeof process !== "undefined" && !process.env.VITEST) {
2238
+ main().catch((error) => {
2239
+ console.error(error);
2240
+ process.exit(1);
2241
+ });
2242
+ }
2243
+
2244
+
2245
+
2246
+
2247
+
2248
+
2249
+ exports.applyCapabilityDefaults = applyCapabilityDefaults; exports.buildHostProfileFromProcess = buildHostProfileFromProcess; exports.makeLLMFromEnvOptional = makeLLMFromEnvOptional; exports.parseCliArgs = parseCliArgs; exports.printHostProfileHuman = printHostProfileHuman;