@bahulam/code 0.1.7 → 0.1.9
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/README.md +1 -1
- package/package.json +3 -3
- package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +1 -1
- package/src/commands/agent.mjs +3 -3
- package/src/commands/device.mjs +2 -2
- package/src/commands/pair.mjs +2 -2
- package/src/commands/remote.mjs +3 -3
- package/src/commands/workflow.mjs +5 -5
- package/src/config/env.mjs +9 -2
- package/src/config/memory-loader.mjs +1 -1
- package/src/config/settings.mjs +2 -2
- package/src/core/agent-loop.mjs +17 -0
- package/src/core/approval.mjs +1 -1
- package/src/core/attachments.mjs +1 -1
- package/src/core/backend-url.mjs +11 -12
- package/src/core/callback-client.mjs +1 -1
- package/src/core/headless.mjs +4 -4
- package/src/core/jsonl-writer.mjs +15 -15
- package/src/core/local-agent.mjs +26 -5
- package/src/core/local-store.mjs +172 -24
- package/src/core/mode-selector.mjs +1 -1
- package/src/core/output-filter.mjs +1 -1
- package/src/core/project-artifacts.mjs +3 -3
- package/src/core/project-context-loader.mjs +9 -9
- package/src/core/settings-sync.mjs +1 -1
- package/src/core/stagnation.mjs +61 -1
- package/src/core/stream-client.mjs +4 -4
- package/src/core/system-prompt.mjs +2 -2
- package/src/core/tool-executor.mjs +79 -61
- package/src/local-service/agent-relay.mjs +8 -8
- package/src/local-service/server.mjs +2 -2
- package/src/mcp/client.mjs +4 -4
- package/src/onboarding/preflight.mjs +1 -1
- package/src/terminal/agents.mjs +2 -2
- package/src/terminal/analytics.mjs +4 -1
- package/src/terminal/main.mjs +6 -6
- package/src/terminal/repl-render.mjs +26 -4
- package/src/terminal/repl-resume.mjs +3 -3
- package/src/terminal/repl.mjs +18 -18
- package/src/terminal/tool-display.mjs +1 -1
- package/src/tools/agent.mjs +2 -2
- package/src/tools/analyze-image.mjs +2 -2
- package/src/tools/generate-image.mjs +4 -4
- package/src/tools/project-overview.mjs +12 -12
- package/src/ui/formatter.mjs +2 -2
- package/src/ui/input-dock.mjs +7 -7
- package/src/ui/spinner.mjs +1 -1
- package/src/ui/term.mjs +2 -6
- package/src/ui/tool-card.mjs +74 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tool Executor Bridge — maps
|
|
2
|
+
* Tool Executor Bridge — maps Bahulam backend tool names to OCC tool calls.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The Bahulam backend sends tool_request events with its own tool names and arg shapes.
|
|
5
5
|
* This bridge translates those into OCC tool calls and wraps the results.
|
|
6
6
|
*
|
|
7
7
|
* Safety guardrails integrated — prevents destructive operations on source code.
|
|
@@ -18,7 +18,7 @@ import { SkillInstaller } from '../skills/installer.mjs';
|
|
|
18
18
|
import { SkillsLoader } from '../skills/loader.mjs';
|
|
19
19
|
import { createAgentFile, listLocalAgents, syncAgentsToBackend } from '../agents/scaffold.mjs';
|
|
20
20
|
import { createWorkflowFile, listLocalWorkflows, WORKFLOW_SYNC_ENDPOINT, slugifyWorkflowName } from '../agents/workflow_scaffold.mjs';
|
|
21
|
-
import {
|
|
21
|
+
import { BahulamAuth } from '../auth/bahulam-auth.mjs';
|
|
22
22
|
import { detectImageFile } from './attachments.mjs';
|
|
23
23
|
import { streamResponse } from './streaming.mjs';
|
|
24
24
|
import { sendApprovalDecision, sendCallback } from './callback-client.mjs';
|
|
@@ -31,10 +31,10 @@ import * as fs from 'node:fs';
|
|
|
31
31
|
import * as os from 'node:os';
|
|
32
32
|
import * as path from 'node:path';
|
|
33
33
|
import * as crypto from 'node:crypto';
|
|
34
|
-
import { execSync } from 'node:child_process';
|
|
34
|
+
import { exec, execSync } from 'node:child_process';
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Create a tool executor that bridges
|
|
37
|
+
* Create a tool executor that bridges Bahulam tool names to OCC tools.
|
|
38
38
|
* @param {Object} [options]
|
|
39
39
|
* @param {ProjectRegistry} [options.projectRegistry] - session-owned project registry
|
|
40
40
|
* @returns {{ execute(name, args): Promise<Object>, listTools(): string[] }}
|
|
@@ -139,7 +139,7 @@ export function createToolExecutor({
|
|
|
139
139
|
|
|
140
140
|
function blockedShellOutput(reason) {
|
|
141
141
|
const text = String(reason || 'Blocked by shell safety policy').trim();
|
|
142
|
-
const hint = /command substitution|backticks|\$\(
|
|
142
|
+
const hint = /command substitution|backticks|\$\(/i.test(text)
|
|
143
143
|
? 'Retry with separate simple shell commands instead of backticks or $().'
|
|
144
144
|
: 'Work only inside a registered project root.';
|
|
145
145
|
return `BLOCKED: ${text}. ${hint}`;
|
|
@@ -472,7 +472,7 @@ export function createToolExecutor({
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
/**
|
|
475
|
-
* Wrap an OCC string result into
|
|
475
|
+
* Wrap an OCC string result into Bahulam's { success, output } format.
|
|
476
476
|
*/
|
|
477
477
|
function wrapResult(result, toolName) {
|
|
478
478
|
if (typeof result === 'object' && result !== null && 'success' in result) {
|
|
@@ -495,7 +495,24 @@ export function createToolExecutor({
|
|
|
495
495
|
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
496
496
|
function stripAnsi(s) { return String(s || '').replace(ANSI_RE, ''); }
|
|
497
497
|
|
|
498
|
-
function
|
|
498
|
+
function runAutoLintCommand(lint) {
|
|
499
|
+
return new Promise((resolve) => {
|
|
500
|
+
exec(lint.command, {
|
|
501
|
+
encoding: 'utf-8',
|
|
502
|
+
timeout: 15_000,
|
|
503
|
+
cwd: lint.cwd,
|
|
504
|
+
maxBuffer: 1_000_000,
|
|
505
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1', TERM: 'dumb' },
|
|
506
|
+
}, (err, stdout = '', stderr = '') => {
|
|
507
|
+
const output = err
|
|
508
|
+
? stripAnsi(stderr || stdout || '').trim()
|
|
509
|
+
: stripAnsi(stdout || stderr || '').trim();
|
|
510
|
+
resolve(output || null);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function autoLint(filePath) {
|
|
499
516
|
const project = projectRegistry.projectForPath(filePath);
|
|
500
517
|
const lint = resolveLintCommand(filePath, {
|
|
501
518
|
projectRoot: project?.resource?.root || projectRootFor(filePath),
|
|
@@ -504,23 +521,7 @@ export function createToolExecutor({
|
|
|
504
521
|
});
|
|
505
522
|
if (!lint?.command) return null;
|
|
506
523
|
|
|
507
|
-
|
|
508
|
-
const output = execSync(lint.command, {
|
|
509
|
-
encoding: 'utf-8',
|
|
510
|
-
timeout: 15_000,
|
|
511
|
-
cwd: lint.cwd,
|
|
512
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
513
|
-
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1', TERM: 'dumb' },
|
|
514
|
-
});
|
|
515
|
-
const trimmed = stripAnsi(output).trim();
|
|
516
|
-
if (!trimmed) return null;
|
|
517
|
-
return trimmed;
|
|
518
|
-
} catch (err) {
|
|
519
|
-
// Non-zero exit means lint errors found
|
|
520
|
-
const output = stripAnsi(err.stderr || err.stdout || '').trim();
|
|
521
|
-
if (!output) return null;
|
|
522
|
-
return output;
|
|
523
|
-
}
|
|
524
|
+
return runAutoLintCommand(lint);
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
// ── Post-edit verification hint ──────────────────────────────
|
|
@@ -1143,7 +1144,7 @@ export function createToolExecutor({
|
|
|
1143
1144
|
updateProjectIndex(filePath);
|
|
1144
1145
|
|
|
1145
1146
|
// Auto-lint the written file
|
|
1146
|
-
const lintOutput = autoLint(filePath);
|
|
1147
|
+
const lintOutput = await autoLint(filePath);
|
|
1147
1148
|
if (lintOutput) {
|
|
1148
1149
|
wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
|
|
1149
1150
|
wrapped.lint = lintOutput;
|
|
@@ -1236,9 +1237,21 @@ export function createToolExecutor({
|
|
|
1236
1237
|
// 4. edit_file → Edit + auto-lint + auto-fallback to sed
|
|
1237
1238
|
edit_file: async (args) => {
|
|
1238
1239
|
const rawPath = args.file_path || args.path;
|
|
1240
|
+
const searchText = args.search ?? args.old_string ?? args.oldString;
|
|
1241
|
+
const replaceText = args.replace ?? args.new_string ?? args.newString;
|
|
1242
|
+
const replaceAll = args.replace_all === true || args.replaceAll === true;
|
|
1243
|
+
if (!rawPath || rawPath === 'file' || rawPath.length < 3) {
|
|
1244
|
+
return { success: false, output: `Error: Invalid file path "${rawPath || ''}". Register the project, then use an absolute path.`, _tool: 'edit_file' };
|
|
1245
|
+
}
|
|
1246
|
+
if (typeof searchText !== 'string' || searchText.length === 0) {
|
|
1247
|
+
return { success: false, output: 'Error: edit_file requires a non-empty search string.', _tool: 'edit_file' };
|
|
1248
|
+
}
|
|
1249
|
+
if (typeof replaceText !== 'string') {
|
|
1250
|
+
return { success: false, output: 'Error: edit_file requires a replacement string.', _tool: 'edit_file' };
|
|
1251
|
+
}
|
|
1239
1252
|
const filePath = await resolvePath(rawPath, args);
|
|
1240
1253
|
const before = readTextIfExists(filePath);
|
|
1241
|
-
const writeCheck = validateWrite(filePath,
|
|
1254
|
+
const writeCheck = validateWrite(filePath, replaceText, projectRootFor(filePath));
|
|
1242
1255
|
if (!writeCheck.safe) {
|
|
1243
1256
|
return { success: false, output: `BLOCKED: ${writeCheck.reason}`, _tool: 'edit_file', _blocked: true };
|
|
1244
1257
|
}
|
|
@@ -1256,46 +1269,51 @@ export function createToolExecutor({
|
|
|
1256
1269
|
try {
|
|
1257
1270
|
result = await occRegistry.call('edit_file', {
|
|
1258
1271
|
file_path: filePath,
|
|
1259
|
-
search:
|
|
1260
|
-
replace:
|
|
1261
|
-
replace_all:
|
|
1272
|
+
search: searchText,
|
|
1273
|
+
replace: replaceText,
|
|
1274
|
+
replace_all: replaceAll,
|
|
1262
1275
|
});
|
|
1263
1276
|
} catch (editErr) {
|
|
1264
|
-
// OCC Edit failed (string not found) — fallback to
|
|
1277
|
+
// OCC Edit failed (string not found) — fallback to direct text replacement.
|
|
1265
1278
|
try {
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
"`;
|
|
1280
|
-
const fallbackResult = execSync(pyCmd, {
|
|
1281
|
-
encoding: 'utf-8',
|
|
1282
|
-
timeout: 5000,
|
|
1283
|
-
cwd: projectRootFor(filePath),
|
|
1284
|
-
});
|
|
1285
|
-
result = `Edited ${filePath} (via fallback): ${fallbackResult.trim()}`;
|
|
1286
|
-
} catch (sedErr) {
|
|
1287
|
-
return { success: false, output: `edit_file failed: ${editErr?.message || 'unknown'}. Fallback also failed: ${sedErr?.message || 'unknown'}. Try shell(sed) manually.`, _tool: 'edit_file' };
|
|
1279
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1280
|
+
if (!content.includes(searchText)) {
|
|
1281
|
+
throw new Error('search string not found in file');
|
|
1282
|
+
}
|
|
1283
|
+
const nextContent = replaceAll
|
|
1284
|
+
? content.split(searchText).join(replaceText)
|
|
1285
|
+
: content.replace(searchText, replaceText);
|
|
1286
|
+
if (nextContent !== content) {
|
|
1287
|
+
fs.writeFileSync(filePath, nextContent, 'utf-8');
|
|
1288
|
+
}
|
|
1289
|
+
result = `Edited ${filePath} (via fallback): OK: replaced`;
|
|
1290
|
+
} catch (fallbackErr) {
|
|
1291
|
+
return { success: false, output: `edit_file failed: ${editErr?.message || 'unknown'}. Fallback also failed: ${fallbackErr?.message || 'unknown'}. Re-read the target range and provide an exact search string.`, _tool: 'edit_file' };
|
|
1288
1292
|
}
|
|
1289
1293
|
}
|
|
1290
1294
|
|
|
1291
1295
|
const wrapped = wrapResult(result, 'edit_file');
|
|
1292
1296
|
const after = readTextIfExists(filePath);
|
|
1297
|
+
if (wrapped.success !== false && before === after) {
|
|
1298
|
+
const relativePath = path.relative(projectRootFor(filePath), filePath) || path.basename(filePath);
|
|
1299
|
+
return {
|
|
1300
|
+
success: false,
|
|
1301
|
+
output: `edit_file made no changes to ${relativePath}. The search string may already be replaced, the replacement may be identical, or the target content may have drifted. Re-read the target range before trying a different edit.`,
|
|
1302
|
+
_tool: 'edit_file',
|
|
1303
|
+
_no_change: true,
|
|
1304
|
+
no_change: true,
|
|
1305
|
+
file_path: filePath,
|
|
1306
|
+
relative_path: relativePath,
|
|
1307
|
+
lines_added: 0,
|
|
1308
|
+
lines_removed: 0,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1293
1311
|
attachFileDiff(wrapped, filePath, before, after);
|
|
1294
1312
|
updateProjectIndex(filePath);
|
|
1295
1313
|
_hasEdited = true;
|
|
1296
1314
|
|
|
1297
1315
|
// Auto-lint the edited file
|
|
1298
|
-
const lintOutput = autoLint(filePath);
|
|
1316
|
+
const lintOutput = await autoLint(filePath);
|
|
1299
1317
|
if (lintOutput) {
|
|
1300
1318
|
wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
|
|
1301
1319
|
wrapped.lint = lintOutput;
|
|
@@ -1505,7 +1523,7 @@ print('OK: replaced')
|
|
|
1505
1523
|
};
|
|
1506
1524
|
},
|
|
1507
1525
|
|
|
1508
|
-
// ──
|
|
1526
|
+
// ── Bahulam-specific tools (no OCC bridge) ──────────────
|
|
1509
1527
|
|
|
1510
1528
|
// 8. read_files → batch Read (with AST truncation for large files)
|
|
1511
1529
|
read_files: async (args) => {
|
|
@@ -1934,7 +1952,7 @@ print('OK: replaced')
|
|
|
1934
1952
|
const target = args.name || args.slug || '';
|
|
1935
1953
|
throw new Error(target ? `No local agent found: ${target}` : 'No local agents found in .bahulam/agents');
|
|
1936
1954
|
}
|
|
1937
|
-
const creds = new
|
|
1955
|
+
const creds = new BahulamAuth().loadCredentials();
|
|
1938
1956
|
const result = await syncAgentsToBackend({
|
|
1939
1957
|
backendUrl: creds.backendUrl,
|
|
1940
1958
|
token: creds.token,
|
|
@@ -1956,7 +1974,7 @@ print('OK: replaced')
|
|
|
1956
1974
|
const local = filterLocalWorkflows(args).map(compactWorkflowMetadata);
|
|
1957
1975
|
let backend = [];
|
|
1958
1976
|
try {
|
|
1959
|
-
const creds = new
|
|
1977
|
+
const creds = new BahulamAuth().loadCredentials();
|
|
1960
1978
|
if (creds.backendUrl && creds.token) {
|
|
1961
1979
|
const resp = await fetch(`${creds.backendUrl}${WORKFLOW_SYNC_ENDPOINT}`, {
|
|
1962
1980
|
headers: {
|
|
@@ -2022,7 +2040,7 @@ print('OK: replaced')
|
|
|
2022
2040
|
const target = args.name || args.slug || '';
|
|
2023
2041
|
throw new Error(target ? `No local workflow found: ${target}` : 'No local workflows found in .bahulam/workflows');
|
|
2024
2042
|
}
|
|
2025
|
-
const creds = new
|
|
2043
|
+
const creds = new BahulamAuth().loadCredentials();
|
|
2026
2044
|
if (!creds.backendUrl || !creds.token) {
|
|
2027
2045
|
throw new Error('Not logged in. Run bahulam login first.');
|
|
2028
2046
|
}
|
|
@@ -2094,7 +2112,7 @@ print('OK: replaced')
|
|
|
2094
2112
|
if (!target) {
|
|
2095
2113
|
throw new Error('workflow_id is required');
|
|
2096
2114
|
}
|
|
2097
|
-
const creds = new
|
|
2115
|
+
const creds = new BahulamAuth().loadCredentials();
|
|
2098
2116
|
if (!creds.backendUrl || !creds.token) {
|
|
2099
2117
|
throw new Error('Not logged in. Run bahulam login first.');
|
|
2100
2118
|
}
|
|
@@ -2273,8 +2291,8 @@ print('OK: replaced')
|
|
|
2273
2291
|
|
|
2274
2292
|
return {
|
|
2275
2293
|
/**
|
|
2276
|
-
* Execute a
|
|
2277
|
-
* @param {string} name -
|
|
2294
|
+
* Execute a Bahulam tool by name.
|
|
2295
|
+
* @param {string} name - Bahulam tool name
|
|
2278
2296
|
* @param {Object} args - Tool arguments
|
|
2279
2297
|
* @returns {Promise<Object>} - { success, output, ... }
|
|
2280
2298
|
*/
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Browser-local agent relay.
|
|
3
3
|
*
|
|
4
4
|
* Bridges a local workspace browser session to the same CLI-owned remote
|
|
5
|
-
* agent path used by the terminal:
|
|
5
|
+
* agent path used by the terminal: BahulamStreamClient + local ToolExecutor.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
|
-
import {
|
|
10
|
+
import { BahulamAuth } from '../auth/bahulam-auth.mjs';
|
|
11
11
|
import { AgentHistoryTurnBuilder } from '../core/agent-history.mjs';
|
|
12
12
|
import { JsonlWriter } from '../core/jsonl-writer.mjs';
|
|
13
13
|
import {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
getRecentSessions,
|
|
16
16
|
getSessionDetail,
|
|
17
17
|
} from '../core/local-store.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import { BahulamStreamClient } from '../core/stream-client.mjs';
|
|
19
19
|
import { createToolExecutor } from '../core/tool-executor.mjs';
|
|
20
20
|
import { buildWorkScope } from '../core/work-scope.mjs';
|
|
21
21
|
import { BrowserApprovalManager } from './approval-bridge.mjs';
|
|
@@ -208,7 +208,7 @@ export class LocalAgentRelay {
|
|
|
208
208
|
this.agentHistory.length ? this.agentHistory : null,
|
|
209
209
|
)) {
|
|
210
210
|
eventCount += 1;
|
|
211
|
-
writer.
|
|
211
|
+
writer.writeBahulamEvent(event);
|
|
212
212
|
|
|
213
213
|
const contentUpdate = contentDeltaForEvent(event, assistantContent);
|
|
214
214
|
if (contentUpdate) {
|
|
@@ -256,7 +256,7 @@ export class LocalAgentRelay {
|
|
|
256
256
|
this._markUndeliveredFollowupsQueued(wasCancelled ? 'Task cancelled' : '');
|
|
257
257
|
if (!userTurnWritten && (this.client.sessionId || wasCancelled)) writeUserTurn();
|
|
258
258
|
if (wasCancelled) {
|
|
259
|
-
writer.
|
|
259
|
+
writer.writeBahulamEvent({
|
|
260
260
|
type: 'cancelled',
|
|
261
261
|
data: {
|
|
262
262
|
task_id: this.client?.currentTaskId || null,
|
|
@@ -482,7 +482,7 @@ export class LocalAgentRelay {
|
|
|
482
482
|
error: result.error || null,
|
|
483
483
|
http_status: result.httpStatus || null,
|
|
484
484
|
};
|
|
485
|
-
this._ensureJsonlWriter().
|
|
485
|
+
this._ensureJsonlWriter().writeBahulamEvent({
|
|
486
486
|
type: 'user_intervention',
|
|
487
487
|
data: {
|
|
488
488
|
instruction: item.instruction,
|
|
@@ -511,7 +511,7 @@ export class LocalAgentRelay {
|
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
async _initialize() {
|
|
514
|
-
const auth = new
|
|
514
|
+
const auth = new BahulamAuth();
|
|
515
515
|
const creds = auth.loadCredentials();
|
|
516
516
|
if (!creds.token) {
|
|
517
517
|
const err = new Error('Not logged in. Run `bahulam login` from the CLI, then retry.');
|
|
@@ -539,7 +539,7 @@ export class LocalAgentRelay {
|
|
|
539
539
|
this.creds = creds;
|
|
540
540
|
this.toolExecutor = toolExecutor;
|
|
541
541
|
this.approvalManager = approval;
|
|
542
|
-
this.client = new
|
|
542
|
+
this.client = new BahulamStreamClient({
|
|
543
543
|
baseUrl: creds.backendUrl,
|
|
544
544
|
token: creds.token,
|
|
545
545
|
toolExecutor,
|
|
@@ -10,7 +10,7 @@ import * as fs from 'node:fs';
|
|
|
10
10
|
import * as http from 'node:http';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
-
import {
|
|
13
|
+
import { BahulamAuth } from '../auth/bahulam-auth.mjs';
|
|
14
14
|
import { resolveWebUrl } from '../core/backend-url.mjs';
|
|
15
15
|
import { LocalAgentRelay } from './agent-relay.mjs';
|
|
16
16
|
import {
|
|
@@ -722,7 +722,7 @@ function staticContentType(filePath) {
|
|
|
722
722
|
|
|
723
723
|
function localAuthInfo() {
|
|
724
724
|
try {
|
|
725
|
-
const credentials = new
|
|
725
|
+
const credentials = new BahulamAuth().loadCredentials();
|
|
726
726
|
return {
|
|
727
727
|
authenticated: Boolean(credentials.token),
|
|
728
728
|
backendUrl: credentials.backendUrl || '',
|
package/src/mcp/client.mjs
CHANGED
|
@@ -78,7 +78,7 @@ export class McpClient {
|
|
|
78
78
|
const initResult = await this._request('initialize', {
|
|
79
79
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
80
80
|
capabilities: {},
|
|
81
|
-
clientInfo: { name: '
|
|
81
|
+
clientInfo: { name: 'bahulam-code', version: '2.0.0' },
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
this.serverInfo = initResult;
|
|
@@ -115,7 +115,7 @@ export class McpClient {
|
|
|
115
115
|
this.serverInfo = await this.transport.request('initialize', {
|
|
116
116
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
117
117
|
capabilities: {},
|
|
118
|
-
clientInfo: { name: '
|
|
118
|
+
clientInfo: { name: 'bahulam-code', version: '2.0.0' },
|
|
119
119
|
});
|
|
120
120
|
return this.serverInfo;
|
|
121
121
|
}
|
|
@@ -130,7 +130,7 @@ export class McpClient {
|
|
|
130
130
|
this.serverInfo = await this.transport.request('initialize', {
|
|
131
131
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
132
132
|
capabilities: {},
|
|
133
|
-
clientInfo: { name: '
|
|
133
|
+
clientInfo: { name: 'bahulam-code', version: '2.0.0' },
|
|
134
134
|
});
|
|
135
135
|
return this.serverInfo;
|
|
136
136
|
}
|
|
@@ -139,7 +139,7 @@ export class McpClient {
|
|
|
139
139
|
const result = await this._transportRequest('initialize', {
|
|
140
140
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
141
141
|
capabilities: {},
|
|
142
|
-
clientInfo: { name: '
|
|
142
|
+
clientInfo: { name: 'bahulam-code', version: '2.0.0' },
|
|
143
143
|
});
|
|
144
144
|
this.serverInfo = result;
|
|
145
145
|
return result;
|
|
@@ -334,7 +334,7 @@ function formatRow(check) {
|
|
|
334
334
|
* collected check results.
|
|
335
335
|
*
|
|
336
336
|
* @param {object} opts
|
|
337
|
-
* @param {object} opts.auth —
|
|
337
|
+
* @param {object} opts.auth — BahulamAuth instance
|
|
338
338
|
* @param {string} opts.cwd — working directory
|
|
339
339
|
* @param {string} opts.version — package version string
|
|
340
340
|
* @param {boolean} [opts.silent] — if true, do not write (useful for tests)
|
package/src/terminal/agents.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { c } from './ansi.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import { BahulamStreamClient } from '../core/stream-client.mjs';
|
|
15
15
|
|
|
16
16
|
// ── Agent Definitions ────────────────────────────────────────
|
|
17
17
|
|
|
@@ -296,7 +296,7 @@ export async function runAgentDefinition(agentDefinition, instruction, ctx, sess
|
|
|
296
296
|
projectRoot: execContext.project_root || null,
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
const client = new
|
|
299
|
+
const client = new BahulamStreamClient({
|
|
300
300
|
baseUrl: creds.backendUrl,
|
|
301
301
|
token: creds.token,
|
|
302
302
|
toolExecutor,
|
|
@@ -103,6 +103,7 @@ export function formatStatsReport(stats, tools, models, days, paths) {
|
|
|
103
103
|
lines.push(`Messages ${formatNumber(stats.totalUserMessages + stats.totalAssistantMessages)} (${formatNumber(stats.totalUserMessages)} user, ${formatNumber(stats.totalAssistantMessages)} assistant)`);
|
|
104
104
|
lines.push(`Tokens ${formatNumber(stats.totalInputTokens + stats.totalOutputTokens)} (${formatNumber(stats.totalInputTokens)} in, ${formatNumber(stats.totalOutputTokens)} out)`);
|
|
105
105
|
lines.push(`Cache Read ${formatNumber(stats.totalCacheReadTokens)}`);
|
|
106
|
+
lines.push(`Reasoning ${formatNumber(stats.totalReasoningTokens)}`);
|
|
106
107
|
lines.push(`Tool Calls ${formatNumber(stats.totalToolCalls)}`);
|
|
107
108
|
lines.push('');
|
|
108
109
|
|
|
@@ -121,7 +122,9 @@ export function formatStatsReport(stats, tools, models, days, paths) {
|
|
|
121
122
|
lines.push(' none');
|
|
122
123
|
} else {
|
|
123
124
|
for (const model of models.slice(0, 8)) {
|
|
124
|
-
|
|
125
|
+
const tokens = (model.inputTokens || 0) + (model.outputTokens || 0);
|
|
126
|
+
const tokenText = tokens ? ` ${formatNumber(tokens)} tok` : '';
|
|
127
|
+
lines.push(` ${truncate(model.model, 42).padEnd(42)} ${formatNumber(model.sessions)} sessions${tokenText}`);
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
lines.push('');
|
package/src/terminal/main.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
17
17
|
import * as telemetry from '../telemetry/index.mjs';
|
|
18
18
|
import { bahulamHome } from '../core/paths.mjs';
|
|
19
|
-
import {
|
|
19
|
+
import { BahulamAuth as Auth } from '../auth/bahulam-auth.mjs';
|
|
20
20
|
|
|
21
21
|
// ── Subcommands ──
|
|
22
22
|
|
|
@@ -183,8 +183,8 @@ async function main() {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
if (subcommand === 'login') {
|
|
186
|
-
const {
|
|
187
|
-
const auth = new
|
|
186
|
+
const { BahulamAuth } = await import('../auth/bahulam-auth.mjs');
|
|
187
|
+
const auth = new BahulamAuth();
|
|
188
188
|
try {
|
|
189
189
|
telemetry.track('login_shown', { method: 'cli_subcommand' });
|
|
190
190
|
await auth.login();
|
|
@@ -198,8 +198,8 @@ async function main() {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
if (subcommand === 'logout') {
|
|
201
|
-
const {
|
|
202
|
-
const auth = new
|
|
201
|
+
const { BahulamAuth } = await import('../auth/bahulam-auth.mjs');
|
|
202
|
+
const auth = new BahulamAuth();
|
|
203
203
|
const success = auth.logout();
|
|
204
204
|
if (success) {
|
|
205
205
|
process.stderr.write('\x1b[32m✓ Signed out. Credentials cleared.\x1b[0m\n');
|
|
@@ -306,7 +306,7 @@ async function main() {
|
|
|
306
306
|
KEPLER_RECONNECT_MAX_ELAPSED_MS
|
|
307
307
|
Max reconnect window for dropped streams
|
|
308
308
|
BAHULAM_TTY_MODE=stable Scrollback-safe transcript if fixed dock redraws leak
|
|
309
|
-
|
|
309
|
+
BAHULAM_BLOCK_SEPARATOR Tool/content separator: space, dotted, or off
|
|
310
310
|
|
|
311
311
|
\x1b[2mDocs: https://bahulam.ai\x1b[0m
|
|
312
312
|
`);
|
|
@@ -133,7 +133,7 @@ import { safeCwd } from './repl-utils.mjs';
|
|
|
133
133
|
import { transcriptHeader, transcriptLine } from '../ui/transcript-block.mjs';
|
|
134
134
|
|
|
135
135
|
export function blockSeparatorMode() {
|
|
136
|
-
return String(process.env.
|
|
136
|
+
return String(process.env.BAHULAM_BLOCK_SEPARATOR || 'space').toLowerCase();
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
export function renderBlockBoundary(nextBlock, { compactSame = false } = {}) {
|
|
@@ -391,7 +391,7 @@ export function renderToolResult(data, eventType = 'tool_result') {
|
|
|
391
391
|
|
|
392
392
|
if (data._blocked) session.blockedOps++;
|
|
393
393
|
|
|
394
|
-
const { text, tone: t } = summarizeResult(tool, data);
|
|
394
|
+
const { text, tone: t } = summarizeResult(tool, data, data.args || {});
|
|
395
395
|
// Em dash reads more like prose than a system arrow.
|
|
396
396
|
const arrow = shellResultTool(tool)
|
|
397
397
|
? `${paint.text.dim('result')} ${paint.text.dim('—')}`
|
|
@@ -840,16 +840,38 @@ export function transcriptRenderableLines(rendered) {
|
|
|
840
840
|
return lines;
|
|
841
841
|
}
|
|
842
842
|
|
|
843
|
+
function positiveInteger(value) {
|
|
844
|
+
const n = Number(value);
|
|
845
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export function stagnationDisplayCount(data = {}, reason = '') {
|
|
849
|
+
const text = String(reason || data?.reason || data?.message || '');
|
|
850
|
+
const patterns = [
|
|
851
|
+
/\bcalled\s+(\d+)\s+times\b/i,
|
|
852
|
+
/[×x]\s*(\d+)\b/i,
|
|
853
|
+
/\brepeated\s+(\d+)x\b/i,
|
|
854
|
+
/\b(\d+)\s+times\s+without\s+mutation\b/i,
|
|
855
|
+
/\b(\d+)\s+tool\s+calls\s+without\s+mutating\s+state\b/i,
|
|
856
|
+
];
|
|
857
|
+
for (const pattern of patterns) {
|
|
858
|
+
const match = text.match(pattern);
|
|
859
|
+
const count = positiveInteger(match?.[1]);
|
|
860
|
+
if (count) return count;
|
|
861
|
+
}
|
|
862
|
+
return positiveInteger(data?.count) || positiveInteger(data?.repeat_count);
|
|
863
|
+
}
|
|
864
|
+
|
|
843
865
|
export function renderStagnation(data = {}) {
|
|
844
866
|
const rawMessage = data?.message || '';
|
|
845
867
|
const reason = data?.reason || rawMessage.replace(/^Stagnation:\s*/i, '').trim();
|
|
846
868
|
const tool = data?.tool || data?.tool_name || '';
|
|
847
|
-
const count = data
|
|
869
|
+
const count = stagnationDisplayCount(data, reason);
|
|
848
870
|
// Try to extract a target/path from the reason so we can show a
|
|
849
871
|
// compact one-liner. Reason shapes we know about from the framework:
|
|
850
872
|
// "Repeated overlapping <tool> inspections of '<target>' N times without mutation"
|
|
851
873
|
// "..." (fallback: use reason as-is, trimmed to ~80 chars)
|
|
852
|
-
const targetMatch = reason.match(/of\s+['"]([^'"]+)['"]/);
|
|
874
|
+
const targetMatch = reason.match(/(?:of|on)\s+['"]([^'"]+)['"]/);
|
|
853
875
|
const target = targetMatch ? targetMatch[1] : '';
|
|
854
876
|
|
|
855
877
|
// Compose a compact single-line message:
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
writeOverlayFrame,
|
|
34
34
|
eraseOverlayFrame,
|
|
35
35
|
} from './repl-format.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import { BahulamStreamClient } from '../core/stream-client.mjs';
|
|
37
37
|
import { getRecentSessions, getSessionDetail, buildResumeHistory, combineResumeSummaries } from '../core/local-store.mjs';
|
|
38
38
|
import { decideResumeMode, projectedTokensForChoice, formatTokens as formatCtxTokens } from '../core/resume-mode.mjs';
|
|
39
39
|
import { applyCompactSummary, localCompactSummary, parseCompactTailCount, prepareCompactHistory } from '../core/compact-history.mjs';
|
|
@@ -504,7 +504,7 @@ export async function summarizeResumeTranscript({
|
|
|
504
504
|
};
|
|
505
505
|
}
|
|
506
506
|
try {
|
|
507
|
-
const client = new
|
|
507
|
+
const client = new BahulamStreamClient({
|
|
508
508
|
baseUrl: creds.backendUrl,
|
|
509
509
|
token: creds.token,
|
|
510
510
|
toolExecutor,
|
|
@@ -605,7 +605,7 @@ export async function compactCurrentSession(ctx, rest = '') {
|
|
|
605
605
|
|
|
606
606
|
if (ctx.jsonlWriter) {
|
|
607
607
|
progress.update('writing summary checkpoint', 88);
|
|
608
|
-
ctx.jsonlWriter.
|
|
608
|
+
ctx.jsonlWriter.writeBahulamEvent({
|
|
609
609
|
type: 'resume_summary',
|
|
610
610
|
data: {
|
|
611
611
|
session_id: session.id || null,
|