@bahulam/code 2.6.13 → 2.6.15
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 +4 -4
- package/src/agents/scaffold.mjs +1 -0
- package/src/commands/agent.mjs +3 -2
- package/src/core/approval-log.mjs +45 -4
- package/src/core/approval.mjs +265 -41
- package/src/core/file-diff.mjs +1 -1
- package/src/core/headless.mjs +14 -3
- package/src/core/local-agent.mjs +3 -2
- package/src/core/risk-tier.mjs +53 -2
- package/src/core/safety.mjs +61 -4
- package/src/core/tool-executor.mjs +38 -16
- package/src/core/trust.mjs +5 -3
- package/src/index.mjs +1 -1
- package/src/terminal/agents.mjs +194 -18
- package/src/terminal/repl-render.mjs +126 -11
- package/src/terminal/repl-state.mjs +2 -0
- package/src/terminal/repl.mjs +553 -88
- package/src/terminal/tool-display.mjs +154 -2
- package/src/ui/approval.mjs +211 -14
- package/src/ui/icons.mjs +11 -5
- package/src/ui/input-dock.mjs +214 -29
- package/src/ui/slash-commands.mjs +10 -0
- package/src/ui/tool-card.mjs +261 -30
- package/src/ui/tool-details.mjs +206 -14
- package/src/ui/transcript-block.mjs +2 -3
package/src/ui/tool-details.mjs
CHANGED
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
import { paint } from './palette.mjs';
|
|
15
15
|
import { icon, toolFamily } from './icons.mjs';
|
|
16
|
-
import { toolDisplayLabel } from '../terminal/tool-display.mjs';
|
|
16
|
+
import { shellCommandProfile, toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
17
|
+
import { isSensitiveConfigPath } from '../core/safety.mjs';
|
|
17
18
|
|
|
18
19
|
const MAX_DETAIL_LINES = 60;
|
|
20
|
+
const MAX_SHELL_DETAIL_LINES = 220;
|
|
19
21
|
const MAX_LINE_WIDTH = 220;
|
|
20
22
|
|
|
21
23
|
// ── Dispatch ─────────────────────────────────────────────────────────────
|
|
@@ -49,6 +51,10 @@ function renderBody(card) {
|
|
|
49
51
|
case 'validate_file':
|
|
50
52
|
case 'validate_structure': return detailValidator(card);
|
|
51
53
|
case 'plan': return detailPlan(card);
|
|
54
|
+
case 'Agent':
|
|
55
|
+
case 'agent':
|
|
56
|
+
case 'task': return detailAgent(card);
|
|
57
|
+
case 'sub_agent_tools': return detailSubAgentTools(card);
|
|
52
58
|
case 'explore':
|
|
53
59
|
case 'verify':
|
|
54
60
|
case 'debug':
|
|
@@ -96,6 +102,13 @@ function oneLineArgs(tool, args) {
|
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
function safeDetailArgs(tool, args) {
|
|
105
|
+
if (tool === 'shell') {
|
|
106
|
+
const profile = shellCommandProfile(args?.command || args?.cmd || '');
|
|
107
|
+
return {
|
|
108
|
+
command: profile.summary,
|
|
109
|
+
...(profile.cwdLabel ? { cwd: profile.cwdLabel } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
99
112
|
if (tool === 'write_file') {
|
|
100
113
|
const { content, ...rest } = args || {};
|
|
101
114
|
return {
|
|
@@ -112,8 +125,24 @@ function safeDetailArgs(tool, args) {
|
|
|
112
125
|
})),
|
|
113
126
|
};
|
|
114
127
|
}
|
|
128
|
+
if (tool === 'Agent' || tool === 'agent' || tool === 'task') {
|
|
129
|
+
const prompt = args?.prompt || args?.task || args?.query || args?.description || args?.instruction || '';
|
|
130
|
+
return {
|
|
131
|
+
...(args?.subagent_type ? { subagent_type: args.subagent_type } : {}),
|
|
132
|
+
...(args?.agent ? { agent: args.agent } : {}),
|
|
133
|
+
...(args?.name ? { name: args.name } : {}),
|
|
134
|
+
...(Array.isArray(args?.allowed_tools) ? { allowed_tools: args.allowed_tools } : {}),
|
|
135
|
+
...(prompt ? { prompt: `[${String(prompt).split('\n').length} lines] ${String(prompt).split('\n').find(Boolean)?.slice(0, 80) || ''}` } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
115
138
|
if (tool === 'edit_file') {
|
|
116
139
|
const next = { ...args };
|
|
140
|
+
if (isSensitiveConfigPath(next.file_path || next.path)) {
|
|
141
|
+
for (const key of ['search', 'replace', 'old_string', 'new_string']) {
|
|
142
|
+
if (typeof next[key] === 'string') next[key] = '[redacted]';
|
|
143
|
+
}
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
117
146
|
for (const key of ['search', 'replace', 'old_string', 'new_string']) {
|
|
118
147
|
if (typeof next[key] === 'string' && next[key].length > 80) {
|
|
119
148
|
next[key] = `[${next[key].split('\n').length} lines omitted]`;
|
|
@@ -191,7 +220,16 @@ function detailListFiles(card) {
|
|
|
191
220
|
// ── Write / edit ────────────────────────────────────────────────────────
|
|
192
221
|
|
|
193
222
|
function detailEditFile(card) {
|
|
194
|
-
const
|
|
223
|
+
const redacted = redactedFileDiff(card.result?.file_diff)
|
|
224
|
+
|| (isSensitiveConfigPath(card.args?.file_path || card.args?.path)
|
|
225
|
+
? sensitiveFallbackDiff(card)
|
|
226
|
+
: null);
|
|
227
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
228
|
+
|
|
229
|
+
const diff = unifiedForFileDiff(card.result?.file_diff)
|
|
230
|
+
|| card.result?.diff
|
|
231
|
+
|| card.result?.patch
|
|
232
|
+
|| card.result?.output;
|
|
195
233
|
if (diff) return renderDiff(String(diff));
|
|
196
234
|
|
|
197
235
|
const before = card.args?.search;
|
|
@@ -206,7 +244,13 @@ function detailEditFile(card) {
|
|
|
206
244
|
}
|
|
207
245
|
|
|
208
246
|
function detailWriteFile(card) {
|
|
209
|
-
const
|
|
247
|
+
const redacted = redactedFileDiff(card.result?.file_diff)
|
|
248
|
+
|| (isSensitiveConfigPath(card.args?.file_path || card.args?.path)
|
|
249
|
+
? sensitiveFallbackDiff(card)
|
|
250
|
+
: null);
|
|
251
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
252
|
+
|
|
253
|
+
const diff = unifiedForFileDiff(card.result?.file_diff) || card.result?.diff;
|
|
210
254
|
if (diff) return renderDiff(String(diff));
|
|
211
255
|
const content = card.args?.content;
|
|
212
256
|
if (!content) return paint.text.dim(' (no content)');
|
|
@@ -216,7 +260,12 @@ function detailWriteFile(card) {
|
|
|
216
260
|
function detailWriteProject(card) {
|
|
217
261
|
const diffs = card.result?.file_diffs || [];
|
|
218
262
|
if (diffs.length) {
|
|
219
|
-
return
|
|
263
|
+
return diffs.map(diff => {
|
|
264
|
+
const redacted = redactedFileDiff(diff);
|
|
265
|
+
if (redacted) return renderRedactedDiff(redacted);
|
|
266
|
+
const unified = unifiedForFileDiff(diff);
|
|
267
|
+
return unified ? renderDiff(unified) : '';
|
|
268
|
+
}).filter(Boolean).join('\n');
|
|
220
269
|
}
|
|
221
270
|
const files = card.args?.files || [];
|
|
222
271
|
if (!files.length) return paint.text.dim(' (no files)');
|
|
@@ -227,6 +276,26 @@ function detailWriteProject(card) {
|
|
|
227
276
|
}).join('\n');
|
|
228
277
|
}
|
|
229
278
|
|
|
279
|
+
function redactedFileDiff(diff) {
|
|
280
|
+
return diff?.redacted ? diff : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sensitiveFallbackDiff(card) {
|
|
284
|
+
return {
|
|
285
|
+
relative_path: card.args?.file_path || card.args?.path || 'sensitive config',
|
|
286
|
+
lines_added: card.result?.lines_added,
|
|
287
|
+
lines_removed: card.result?.lines_removed,
|
|
288
|
+
redacted: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderRedactedDiff(diff = {}) {
|
|
293
|
+
const file = diff.relative_path || diff.path || 'sensitive config';
|
|
294
|
+
const add = diff.lines_added ?? 0;
|
|
295
|
+
const rem = diff.lines_removed ?? 0;
|
|
296
|
+
return ` ${paint.brand.primary(file)} ${paint.text.dim(`+${add} −${rem}`)}\n ${paint.text.dim('diff redacted for sensitive config')}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
230
299
|
function detailDeleteFile(card) {
|
|
231
300
|
const p = card.args?.file_path || card.args?.path || '';
|
|
232
301
|
return ` ${paint.state.danger('✗')} ${paint.text.primary(p)}`;
|
|
@@ -235,17 +304,37 @@ function detailDeleteFile(card) {
|
|
|
235
304
|
// ── Shell / validators ──────────────────────────────────────────────────
|
|
236
305
|
|
|
237
306
|
function detailShell(card) {
|
|
307
|
+
const command = String(card.args?.command || card.args?.cmd || '').trim();
|
|
238
308
|
const stdout = String(card.result?.stdout ?? card.result?.output ?? '');
|
|
239
309
|
const stderr = String(card.result?.stderr ?? '');
|
|
240
310
|
const out = [];
|
|
311
|
+
if (command) {
|
|
312
|
+
const profile = shellCommandProfile(command);
|
|
313
|
+
if (profile.cwdLabel) {
|
|
314
|
+
out.push(paint.text.dim(' cwd'));
|
|
315
|
+
out.push(` ${paint.brand.data(profile.cwdLabel)}`);
|
|
316
|
+
out.push('');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
out.push(paint.text.dim(' command'));
|
|
320
|
+
if (profile.script?.body) {
|
|
321
|
+
out.push(` ${paint.text.primary(profile.script.invocation || profile.command.split('\n')[0] || profile.command)}`);
|
|
322
|
+
out.push('');
|
|
323
|
+
out.push(paint.text.dim(' script'));
|
|
324
|
+
out.push(numbered(profile.script.body, 1, { maxLines: MAX_SHELL_DETAIL_LINES }));
|
|
325
|
+
} else {
|
|
326
|
+
out.push(clip(profile.command, paint.text.primary, { maxLines: MAX_SHELL_DETAIL_LINES }));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
241
329
|
if (stdout) {
|
|
330
|
+
if (out.length) out.push('');
|
|
242
331
|
out.push(paint.text.dim(' stdout'));
|
|
243
|
-
out.push(clip(stdout));
|
|
332
|
+
out.push(clip(stdout, paint.text.primary, { maxLines: MAX_DETAIL_LINES }));
|
|
244
333
|
}
|
|
245
334
|
if (stderr) {
|
|
246
335
|
if (out.length) out.push('');
|
|
247
336
|
out.push(paint.state.warn(' stderr'));
|
|
248
|
-
out.push(clip(stderr, paint.state.danger));
|
|
337
|
+
out.push(clip(stderr, paint.state.danger, { maxLines: MAX_DETAIL_LINES }));
|
|
249
338
|
}
|
|
250
339
|
return out.length ? out.join('\n') : paint.text.dim(' (no output)');
|
|
251
340
|
}
|
|
@@ -267,6 +356,56 @@ function detailPlan(card) {
|
|
|
267
356
|
}).join('\n');
|
|
268
357
|
}
|
|
269
358
|
|
|
359
|
+
function detailAgent(card) {
|
|
360
|
+
const args = card.args || {};
|
|
361
|
+
const prompt = args.prompt || args.task || args.query || args.description || args.instruction || '';
|
|
362
|
+
const out = [];
|
|
363
|
+
const meta = [
|
|
364
|
+
args.subagent_type ? `type ${args.subagent_type}` : '',
|
|
365
|
+
args.agent || args.name || '',
|
|
366
|
+
Array.isArray(args.allowed_tools) && args.allowed_tools.length
|
|
367
|
+
? `${args.allowed_tools.length} allowed tools`
|
|
368
|
+
: '',
|
|
369
|
+
].filter(Boolean).join(' · ');
|
|
370
|
+
if (meta) out.push(` ${paint.text.dim(meta)}`);
|
|
371
|
+
if (prompt) {
|
|
372
|
+
out.push(paint.text.dim(' prompt'));
|
|
373
|
+
out.push(clip(prompt, paint.text.primary, { maxLines: MAX_DETAIL_LINES }));
|
|
374
|
+
}
|
|
375
|
+
const result = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
376
|
+
if (result) {
|
|
377
|
+
if (out.length) out.push('');
|
|
378
|
+
out.push(paint.text.dim(' result'));
|
|
379
|
+
out.push(clip(result));
|
|
380
|
+
}
|
|
381
|
+
return out.length ? out.join('\n') : detailGenericOutput(card);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function detailSubAgentTools(card) {
|
|
385
|
+
const entries = Array.isArray(card.result?.tools) ? card.result.tools : [];
|
|
386
|
+
if (!entries.length) return paint.text.dim(' (no folded tool calls)');
|
|
387
|
+
|
|
388
|
+
const out = [];
|
|
389
|
+
entries.forEach((entry, index) => {
|
|
390
|
+
const tool = entry.tool || 'tool';
|
|
391
|
+
const args = entry.args || {};
|
|
392
|
+
const summary = entry.summary || toolDisplaySummary(tool, args);
|
|
393
|
+
const outcome = entry.outcome ? ` ${paint.text.dim('—')} ${paint.text.muted(entry.outcome)}` : '';
|
|
394
|
+
const duration = entry.durationMs != null ? ` ${paint.text.dim('· ' + formatDuration(entry.durationMs))}` : '';
|
|
395
|
+
out.push(` ${paint.text.dim(`${index + 1}.`)} ${icon(tool)} ${toolDisplayLabel(tool)}${summary ? ` ${paint.text.muted(summary)}` : ''}${outcome}${duration}`);
|
|
396
|
+
|
|
397
|
+
const child = {
|
|
398
|
+
tool,
|
|
399
|
+
args,
|
|
400
|
+
result: entry.result || null,
|
|
401
|
+
durationMs: entry.durationMs ?? null,
|
|
402
|
+
};
|
|
403
|
+
const detail = renderBody(child);
|
|
404
|
+
if (detail) out.push(indentBlock(detail, ' '));
|
|
405
|
+
});
|
|
406
|
+
return out.join('\n');
|
|
407
|
+
}
|
|
408
|
+
|
|
270
409
|
function detailGenericOutput(card) {
|
|
271
410
|
const out = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
272
411
|
return clip(out);
|
|
@@ -274,28 +413,35 @@ function detailGenericOutput(card) {
|
|
|
274
413
|
|
|
275
414
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
276
415
|
|
|
277
|
-
function numbered(text, start) {
|
|
416
|
+
function numbered(text, start, { maxLines = MAX_DETAIL_LINES } = {}) {
|
|
278
417
|
const lines = String(text).split('\n');
|
|
279
418
|
const total = lines.length;
|
|
280
419
|
const width = String(start + total - 1).length;
|
|
281
|
-
return lines.slice(0,
|
|
420
|
+
return lines.slice(0, maxLines).map((line, i) => {
|
|
282
421
|
const n = String(start + i).padStart(width);
|
|
283
422
|
return ` ${paint.text.dim(n)} ${paint.text.primary(line.slice(0, MAX_LINE_WIDTH))}`;
|
|
284
|
-
}).join('\n') + (total >
|
|
285
|
-
? `\n ${paint.text.dim(`… ${total -
|
|
423
|
+
}).join('\n') + (total > maxLines
|
|
424
|
+
? `\n ${paint.text.dim(`… ${total - maxLines} more line(s)`)}`
|
|
286
425
|
: '');
|
|
287
426
|
}
|
|
288
427
|
|
|
289
|
-
function clip(text, painter = paint.text.primary) {
|
|
428
|
+
function clip(text, painter = paint.text.primary, { maxLines = MAX_DETAIL_LINES } = {}) {
|
|
290
429
|
if (!text) return paint.text.dim(' (empty)');
|
|
291
430
|
const lines = String(text).split('\n');
|
|
292
|
-
const head = lines.slice(0,
|
|
293
|
-
if (lines.length >
|
|
294
|
-
head.push(` ${paint.text.dim(`… ${lines.length -
|
|
431
|
+
const head = lines.slice(0, maxLines).map(l => ` ${painter(l.slice(0, MAX_LINE_WIDTH))}`);
|
|
432
|
+
if (lines.length > maxLines) {
|
|
433
|
+
head.push(` ${paint.text.dim(`… ${lines.length - maxLines} more line(s)`)}`);
|
|
295
434
|
}
|
|
296
435
|
return head.join('\n');
|
|
297
436
|
}
|
|
298
437
|
|
|
438
|
+
function indentBlock(text, prefix) {
|
|
439
|
+
return String(text || '')
|
|
440
|
+
.split('\n')
|
|
441
|
+
.map(line => `${prefix}${line.trimStart()}`)
|
|
442
|
+
.join('\n');
|
|
443
|
+
}
|
|
444
|
+
|
|
299
445
|
function renderDiff(text) {
|
|
300
446
|
return text.split('\n').slice(0, MAX_DETAIL_LINES).map(line => {
|
|
301
447
|
if (line.startsWith('+++') || line.startsWith('---')) return ` ${paint.bold(paint.text.muted(line))}`;
|
|
@@ -306,6 +452,52 @@ function renderDiff(text) {
|
|
|
306
452
|
}).join('\n');
|
|
307
453
|
}
|
|
308
454
|
|
|
455
|
+
function unifiedForFileDiff(diff) {
|
|
456
|
+
if (!diff) return '';
|
|
457
|
+
if (diff.unified) return String(diff.unified);
|
|
458
|
+
const hunks = Array.isArray(diff.hunks) ? diff.hunks : [];
|
|
459
|
+
if (!hunks.length) return '';
|
|
460
|
+
const file = diff.relative_path || diff.path || 'file';
|
|
461
|
+
const out = [`--- a/${file}`, `+++ b/${file}`];
|
|
462
|
+
for (const hunk of hunks) {
|
|
463
|
+
const lines = hunkLines(hunk);
|
|
464
|
+
if (!lines.length) continue;
|
|
465
|
+
const oldCount = hunk.old_count ?? hunk.old_lines ?? countLinesForSide(lines, 'old');
|
|
466
|
+
const newCount = hunk.new_count ?? hunk.new_lines ?? countLinesForSide(lines, 'new');
|
|
467
|
+
out.push(`@@ -${hunk.old_start ?? 1},${oldCount} +${hunk.new_start ?? 1},${newCount} @@`);
|
|
468
|
+
for (const line of lines) out.push(toUnifiedLine(line));
|
|
469
|
+
}
|
|
470
|
+
return out.length > 2 ? out.join('\n') : '';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function hunkLines(hunk = {}) {
|
|
474
|
+
if (Array.isArray(hunk.lines)) return hunk.lines;
|
|
475
|
+
if (typeof hunk.body !== 'string') return [];
|
|
476
|
+
return hunk.body.replace(/\r\n?/g, '\n').split('\n').filter(line => line && !line.startsWith('@@'));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function toUnifiedLine(line) {
|
|
480
|
+
if (typeof line === 'string') {
|
|
481
|
+
if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) return line;
|
|
482
|
+
return ` ${line}`;
|
|
483
|
+
}
|
|
484
|
+
const type = String(line?.type || '').toLowerCase();
|
|
485
|
+
const text = String(line?.text ?? line?.content ?? '');
|
|
486
|
+
if (type === 'add' || type === 'added') return `+${text}`;
|
|
487
|
+
if (type === 'remove' || type === 'removed' || type === 'delete') return `-${text}`;
|
|
488
|
+
return ` ${text}`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function countLinesForSide(lines, side) {
|
|
492
|
+
return lines.filter(line => {
|
|
493
|
+
const type = typeof line === 'string'
|
|
494
|
+
? (line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context')
|
|
495
|
+
: String(line?.type || 'context').toLowerCase();
|
|
496
|
+
if (side === 'old') return type !== 'add' && type !== 'added';
|
|
497
|
+
return type !== 'remove' && type !== 'removed' && type !== 'delete';
|
|
498
|
+
}).length;
|
|
499
|
+
}
|
|
500
|
+
|
|
309
501
|
function formatDuration(ms) {
|
|
310
502
|
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
311
503
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -6,12 +6,11 @@ function tonePaint(tone) {
|
|
|
6
6
|
|
|
7
7
|
export function transcriptHeader(label, { tone = 'assistant' } = {}) {
|
|
8
8
|
const p = tonePaint(tone);
|
|
9
|
-
return
|
|
9
|
+
return `${paint.bold(p(label))} ${p('›')}`;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function transcriptLine(line = '', { tone = 'assistant' } = {}) {
|
|
13
|
-
|
|
14
|
-
return ` ${p('│')} ${line}`;
|
|
13
|
+
return ` ${line}`;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export function transcriptLines(text, opts = {}) {
|