@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/core/headless.mjs
CHANGED
|
@@ -272,7 +272,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
272
272
|
tool_calls: data?.tool_calls || 0,
|
|
273
273
|
success: data?.success !== false,
|
|
274
274
|
});
|
|
275
|
-
emit({ type: 'sub_agent',
|
|
275
|
+
emit({ ...data, type: 'sub_agent', agent_type: data?.type || '' });
|
|
276
276
|
log(`SubAgent done: ${data?.type} (${data?.tool_calls} tools, ${data?.duration_s}s)`);
|
|
277
277
|
}
|
|
278
278
|
|
|
@@ -360,8 +360,19 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
360
360
|
const subAgentToolBreakdown = countBreakdown(toolCalls.filter(t => t.internal));
|
|
361
361
|
|
|
362
362
|
const subAgentReportedToolCount = subAgents.reduce((sum, sa) => sum + (sa.tool_calls || 0), 0);
|
|
363
|
-
const
|
|
364
|
-
|
|
363
|
+
const observedSubAgentToolCount = Math.max(
|
|
364
|
+
subAgentReportedToolCount,
|
|
365
|
+
subAgentForwardedToolCount,
|
|
366
|
+
);
|
|
367
|
+
const subAgentToolCount = (
|
|
368
|
+
backendSubAgentToolCount && backendSubAgentToolCount > 0
|
|
369
|
+
)
|
|
370
|
+
? backendSubAgentToolCount
|
|
371
|
+
: observedSubAgentToolCount;
|
|
372
|
+
const computedToolCount = primaryToolCount + subAgentToolCount;
|
|
373
|
+
const totalToolCount = backendToolCount != null
|
|
374
|
+
? Math.max(backendToolCount, computedToolCount)
|
|
375
|
+
: computedToolCount;
|
|
365
376
|
const primaryToolTotal = backendPrimaryToolCount ?? (
|
|
366
377
|
backendToolCount != null
|
|
367
378
|
? Math.max(0, totalToolCount - subAgentToolCount)
|
package/src/core/local-agent.mjs
CHANGED
|
@@ -21,11 +21,11 @@ const MAX_ITERATIONS = 50;
|
|
|
21
21
|
const TOOL_SCHEMAS = [
|
|
22
22
|
{
|
|
23
23
|
name: 'shell',
|
|
24
|
-
description: 'Run
|
|
24
|
+
description: 'Run one non-interactive shell command and return stdout/stderr. Do not use command substitution, backticks, or $(); run separate simple shell calls instead.',
|
|
25
25
|
input_schema: {
|
|
26
26
|
type: 'object',
|
|
27
27
|
properties: {
|
|
28
|
-
command: { type: 'string', description: 'The
|
|
28
|
+
command: { type: 'string', description: 'The command to execute. Avoid backticks and $(); split dependent checks into separate commands.' },
|
|
29
29
|
timeout: { type: 'number', description: 'Timeout in milliseconds (default: 120000)' },
|
|
30
30
|
},
|
|
31
31
|
required: ['command'],
|
|
@@ -478,6 +478,7 @@ export class LocalAgent {
|
|
|
478
478
|
'You are Bahulam Code, Bahulam\'s AI coding agent running in local mode.',
|
|
479
479
|
'You have access to tools for reading, writing, and executing code.',
|
|
480
480
|
'Use tools to accomplish the user\'s request. Be concise and direct.',
|
|
481
|
+
'For shell tools, never use command substitution, backticks, or $(). Run separate simple commands and carry values forward in text.',
|
|
481
482
|
];
|
|
482
483
|
if (context.cwd) parts.push(`Working directory: ${context.cwd}`);
|
|
483
484
|
if (context.gitBranch) parts.push(`Git branch: ${context.gitBranch}`);
|
package/src/core/risk-tier.mjs
CHANGED
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
* intent can't be hidden behind a friendly description.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { isApprovalRequiredWritePath, isSensitiveConfigPath } from './safety.mjs';
|
|
18
|
+
|
|
17
19
|
// ── Tier enum ────────────────────────────────────────────────────────────
|
|
18
20
|
|
|
19
21
|
export const TIERS = Object.freeze({
|
|
20
22
|
READ: 'read',
|
|
21
23
|
SENSITIVE_READ: 'sensitive-read',
|
|
22
24
|
LOCAL_EDIT: 'local-edit',
|
|
25
|
+
PROTECTED_EDIT: 'protected-edit',
|
|
23
26
|
SHELL_SAFE: 'shell-safe',
|
|
24
27
|
SHELL_MEDIUM: 'shell-medium',
|
|
25
28
|
SHELL_DANGEROUS: 'shell-dangerous',
|
|
@@ -38,6 +41,7 @@ export const BEHAVIOR = Object.freeze({
|
|
|
38
41
|
[TIERS.READ]: 'auto',
|
|
39
42
|
[TIERS.SENSITIVE_READ]: 'prompt-explicit',
|
|
40
43
|
[TIERS.LOCAL_EDIT]: 'auto-with-undo',
|
|
44
|
+
[TIERS.PROTECTED_EDIT]: 'prompt-explicit',
|
|
41
45
|
[TIERS.SHELL_SAFE]: 'auto',
|
|
42
46
|
[TIERS.SHELL_MEDIUM]: 'prompt-safe',
|
|
43
47
|
[TIERS.SHELL_DANGEROUS]: 'prompt-explicit',
|
|
@@ -77,6 +81,14 @@ const LOCAL_EDIT_TOOLS = new Set([
|
|
|
77
81
|
'workflow_create_multi', 'workflow_sync_multi',
|
|
78
82
|
]);
|
|
79
83
|
|
|
84
|
+
const WRITE_PATH_KEYS = [
|
|
85
|
+
'path', 'file_path', 'file', 'target',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const WRITE_PATH_ARRAY_KEYS = [
|
|
89
|
+
'paths', 'file_paths', 'targets',
|
|
90
|
+
];
|
|
91
|
+
|
|
80
92
|
const DESTRUCTIVE_TOOLS = new Set([
|
|
81
93
|
'delete_file', 'skill_remove',
|
|
82
94
|
]);
|
|
@@ -219,6 +231,7 @@ const TIER_ORDER = [
|
|
|
219
231
|
TIERS.SENSITIVE_READ,
|
|
220
232
|
TIERS.SHELL_SAFE,
|
|
221
233
|
TIERS.LOCAL_EDIT,
|
|
234
|
+
TIERS.PROTECTED_EDIT,
|
|
222
235
|
TIERS.NETWORK,
|
|
223
236
|
TIERS.SHELL_MEDIUM,
|
|
224
237
|
TIERS.DESTRUCTIVE,
|
|
@@ -243,7 +256,9 @@ export function classify(tool, args = {}) {
|
|
|
243
256
|
if (READ_TOOLS.has(tool)) {
|
|
244
257
|
return isSensitiveRead(args) ? TIERS.SENSITIVE_READ : TIERS.READ;
|
|
245
258
|
}
|
|
246
|
-
if (LOCAL_EDIT_TOOLS.has(tool))
|
|
259
|
+
if (LOCAL_EDIT_TOOLS.has(tool)) {
|
|
260
|
+
return isApprovalRequiredWrite(args) ? TIERS.PROTECTED_EDIT : TIERS.LOCAL_EDIT;
|
|
261
|
+
}
|
|
247
262
|
if (DESTRUCTIVE_TOOLS.has(tool)) return TIERS.DESTRUCTIVE;
|
|
248
263
|
if (NETWORK_TOOLS.has(tool)) return TIERS.NETWORK;
|
|
249
264
|
|
|
@@ -273,6 +288,7 @@ export function label(tier) {
|
|
|
273
288
|
case TIERS.READ: return 'READ';
|
|
274
289
|
case TIERS.SENSITIVE_READ: return 'SENSITIVE-READ';
|
|
275
290
|
case TIERS.LOCAL_EDIT: return 'LOCAL-EDIT';
|
|
291
|
+
case TIERS.PROTECTED_EDIT: return 'PROTECTED-EDIT';
|
|
276
292
|
case TIERS.SHELL_SAFE: return 'SHELL-SAFE';
|
|
277
293
|
case TIERS.SHELL_MEDIUM: return 'SHELL-MEDIUM';
|
|
278
294
|
case TIERS.SHELL_DANGEROUS: return 'SHELL-DANGEROUS';
|
|
@@ -310,11 +326,15 @@ export function isSensitiveReadPath(filePath = '') {
|
|
|
310
326
|
|
|
311
327
|
const parts = normalized.split('/').filter(Boolean);
|
|
312
328
|
const base = parts[parts.length - 1] || normalized;
|
|
313
|
-
if (base
|
|
329
|
+
if (isSensitiveConfigPath(base)) return true;
|
|
314
330
|
if (/\.pem$/i.test(base)) return true;
|
|
315
331
|
return parts.includes('secrets');
|
|
316
332
|
}
|
|
317
333
|
|
|
334
|
+
export function isApprovalRequiredWrite(args = {}) {
|
|
335
|
+
return extractWritePaths(args).some(isApprovalRequiredWritePath);
|
|
336
|
+
}
|
|
337
|
+
|
|
318
338
|
function extractReadPaths(args = {}) {
|
|
319
339
|
const paths = [];
|
|
320
340
|
for (const key of READ_PATH_KEYS) {
|
|
@@ -335,3 +355,34 @@ function extractReadPaths(args = {}) {
|
|
|
335
355
|
}
|
|
336
356
|
return paths;
|
|
337
357
|
}
|
|
358
|
+
|
|
359
|
+
function extractWritePaths(args = {}) {
|
|
360
|
+
const paths = [];
|
|
361
|
+
for (const key of WRITE_PATH_KEYS) {
|
|
362
|
+
if (typeof args[key] === 'string') paths.push(args[key]);
|
|
363
|
+
}
|
|
364
|
+
for (const key of WRITE_PATH_ARRAY_KEYS) {
|
|
365
|
+
const value = args[key];
|
|
366
|
+
if (!Array.isArray(value)) continue;
|
|
367
|
+
for (const item of value) {
|
|
368
|
+
if (typeof item === 'string') {
|
|
369
|
+
paths.push(item);
|
|
370
|
+
} else if (item && typeof item === 'object') {
|
|
371
|
+
for (const nestedKey of WRITE_PATH_KEYS) {
|
|
372
|
+
if (typeof item[nestedKey] === 'string') paths.push(item[nestedKey]);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (Array.isArray(args.files)) {
|
|
378
|
+
for (const item of args.files) {
|
|
379
|
+
if (typeof item === 'string') paths.push(item);
|
|
380
|
+
else if (item && typeof item === 'object') {
|
|
381
|
+
for (const nestedKey of WRITE_PATH_KEYS) {
|
|
382
|
+
if (typeof item[nestedKey] === 'string') paths.push(item[nestedKey]);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return paths;
|
|
388
|
+
}
|
package/src/core/safety.mjs
CHANGED
|
@@ -28,6 +28,18 @@ const PROTECTED_NAMES = new Set([
|
|
|
28
28
|
'go.sum',
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
+
const APPROVAL_REQUIRED_WRITE_NAMES = new Set([
|
|
32
|
+
'.env',
|
|
33
|
+
'.env.local',
|
|
34
|
+
'.env.production',
|
|
35
|
+
'package.json',
|
|
36
|
+
'package-lock.json',
|
|
37
|
+
'yarn.lock',
|
|
38
|
+
'pnpm-lock.yaml',
|
|
39
|
+
'Cargo.lock',
|
|
40
|
+
'go.sum',
|
|
41
|
+
]);
|
|
42
|
+
|
|
31
43
|
/** Directory names that indicate a source root — never delete the dir itself. */
|
|
32
44
|
const SOURCE_DIRS = new Set([
|
|
33
45
|
'src',
|
|
@@ -80,13 +92,35 @@ const HIGH_RISK_COMMANDS = [
|
|
|
80
92
|
|
|
81
93
|
// ── Validation Functions ─────────────────────────────────────
|
|
82
94
|
|
|
95
|
+
function basenameFor(filePath = '') {
|
|
96
|
+
return path.basename(String(filePath || '').replace(/\\/g, '/'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isEnvLikeName(name = '') {
|
|
100
|
+
return /^\.env(?:\.|$)/.test(String(name || ''));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isProtectedName(name = '') {
|
|
104
|
+
return PROTECTED_NAMES.has(name) || isEnvLikeName(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function isSensitiveConfigPath(filePath = '') {
|
|
108
|
+
return isEnvLikeName(basenameFor(filePath));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isApprovalRequiredWritePath(filePath = '') {
|
|
112
|
+
const basename = basenameFor(filePath);
|
|
113
|
+
return isSensitiveConfigPath(filePath) || APPROVAL_REQUIRED_WRITE_NAMES.has(basename);
|
|
114
|
+
}
|
|
115
|
+
|
|
83
116
|
/**
|
|
84
117
|
* Check if a path is protected (should not be deleted/overwritten entirely).
|
|
85
118
|
* @param {string} filePath - Absolute path
|
|
86
119
|
* @param {string} cwd - Current working directory
|
|
87
|
-
* @
|
|
120
|
+
* @param {{ allowApprovalRequiredWrite?: boolean }} options
|
|
121
|
+
* @returns {{ safe: boolean, reason?: string, requiresApproval?: boolean, sensitive?: boolean, protected?: boolean }}
|
|
88
122
|
*/
|
|
89
|
-
export function validatePath(filePath, cwd = process.cwd()) {
|
|
123
|
+
export function validatePath(filePath, cwd = process.cwd(), options = {}) {
|
|
90
124
|
if (!filePath) return { safe: false, reason: 'Empty path' };
|
|
91
125
|
|
|
92
126
|
const resolved = path.resolve(cwd, filePath);
|
|
@@ -100,7 +134,16 @@ export function validatePath(filePath, cwd = process.cwd()) {
|
|
|
100
134
|
}
|
|
101
135
|
|
|
102
136
|
// Block protected file names at any level
|
|
103
|
-
if (
|
|
137
|
+
if (isProtectedName(basename) && !resolved.includes('node_modules/')) {
|
|
138
|
+
if (options.allowApprovalRequiredWrite && isApprovalRequiredWritePath(resolved)) {
|
|
139
|
+
return {
|
|
140
|
+
safe: true,
|
|
141
|
+
reason: `Approval required for protected path: ${basename}`,
|
|
142
|
+
requiresApproval: true,
|
|
143
|
+
protected: true,
|
|
144
|
+
sensitive: isSensitiveConfigPath(resolved),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
104
147
|
return { safe: false, reason: `Protected path: ${basename}` };
|
|
105
148
|
}
|
|
106
149
|
|
|
@@ -172,16 +215,29 @@ export function validateShellCommand(command) {
|
|
|
172
215
|
* @returns {{ safe: boolean, reason?: string }}
|
|
173
216
|
*/
|
|
174
217
|
export function validateWrite(filePath, content, cwd = process.cwd()) {
|
|
175
|
-
const pathCheck = validatePath(filePath, cwd);
|
|
218
|
+
const pathCheck = validatePath(filePath, cwd, { allowApprovalRequiredWrite: true });
|
|
176
219
|
if (!pathCheck.safe) return pathCheck;
|
|
177
220
|
|
|
178
221
|
const resolved = path.resolve(cwd, filePath);
|
|
222
|
+
const basename = path.basename(resolved);
|
|
179
223
|
|
|
180
224
|
// Don't allow overwriting .git internals
|
|
181
225
|
if (resolved.includes('/.git/')) {
|
|
182
226
|
return { safe: false, reason: 'Cannot write to .git directory' };
|
|
183
227
|
}
|
|
184
228
|
|
|
229
|
+
if (pathCheck.requiresApproval) {
|
|
230
|
+
return {
|
|
231
|
+
safe: true,
|
|
232
|
+
reason: pathCheck.sensitive
|
|
233
|
+
? `Sensitive config write requires approval: ${basename}`
|
|
234
|
+
: `Protected file write requires approval: ${basename}`,
|
|
235
|
+
requiresApproval: true,
|
|
236
|
+
protected: true,
|
|
237
|
+
sensitive: pathCheck.sensitive,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
185
241
|
// Warn if writing a very large file (> 1MB)
|
|
186
242
|
if (content && content.length > 1_000_000) {
|
|
187
243
|
return { safe: true, reason: `Large file write: ${(content.length / 1024).toFixed(0)}KB` };
|
|
@@ -196,6 +252,7 @@ export function validateWrite(filePath, content, cwd = process.cwd()) {
|
|
|
196
252
|
export function getSafetyRules() {
|
|
197
253
|
return {
|
|
198
254
|
protectedNames: [...PROTECTED_NAMES],
|
|
255
|
+
approvalRequiredWriteNames: [...APPROVAL_REQUIRED_WRITE_NAMES],
|
|
199
256
|
sourceDirs: [...SOURCE_DIRS],
|
|
200
257
|
blockedPatterns: DANGEROUS_SHELL_PATTERNS.length,
|
|
201
258
|
highRiskPatterns: HIGH_RISK_COMMANDS.length,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { createToolRegistry } from '../tools/registry.mjs';
|
|
12
12
|
import { detectCommandType, filterOutput } from './output-filter.mjs';
|
|
13
|
-
import { validatePath, validateDelete, validateShellCommand, validateWrite } from './safety.mjs';
|
|
13
|
+
import { isSensitiveConfigPath, validatePath, validateDelete, validateShellCommand, validateWrite } from './safety.mjs';
|
|
14
14
|
import { classifyCommand, isExitCodeError } from '../permissions/command-classifier.mjs';
|
|
15
15
|
import { analyzeCode } from '../context/ast-parser.mjs';
|
|
16
16
|
import { ProjectRegistry } from '../tools/project-overview.mjs';
|
|
@@ -74,6 +74,14 @@ export function createToolExecutor({
|
|
|
74
74
|
return path.resolve(cwd, value);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function blockedShellOutput(reason) {
|
|
78
|
+
const text = String(reason || 'Blocked by shell safety policy').trim();
|
|
79
|
+
const hint = /command substitution|backticks|\$\(\)/i.test(text)
|
|
80
|
+
? 'Retry with separate simple shell commands instead of backticks or $().'
|
|
81
|
+
: 'Work only inside a registered project root.';
|
|
82
|
+
return `BLOCKED: ${text}. ${hint}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
function longRunningObservationTimeoutMs() {
|
|
78
86
|
const configured = Number(process.env.KEPLER_LONG_RUNNING_TIMEOUT_MS);
|
|
79
87
|
return Number.isFinite(configured) && configured > 0 ? configured : 15_000;
|
|
@@ -358,18 +366,35 @@ export function createToolExecutor({
|
|
|
358
366
|
return result;
|
|
359
367
|
}
|
|
360
368
|
|
|
369
|
+
function buildResultFileDiff(filePath, before, after) {
|
|
370
|
+
const diff = buildFileDiff({
|
|
371
|
+
filePath,
|
|
372
|
+
before,
|
|
373
|
+
after,
|
|
374
|
+
cwd: projectRootFor(filePath),
|
|
375
|
+
});
|
|
376
|
+
if (!isSensitiveConfigPath(filePath)) return diff;
|
|
377
|
+
return {
|
|
378
|
+
...diff,
|
|
379
|
+
hunks: [],
|
|
380
|
+
unified: '',
|
|
381
|
+
redacted: true,
|
|
382
|
+
sensitive: true,
|
|
383
|
+
redaction_reason: 'Sensitive config diff redacted',
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
361
387
|
function attachFileDiff(result, filePath, before, after) {
|
|
362
388
|
try {
|
|
363
|
-
const diff =
|
|
364
|
-
filePath,
|
|
365
|
-
before,
|
|
366
|
-
after,
|
|
367
|
-
cwd: projectRootFor(filePath),
|
|
368
|
-
});
|
|
389
|
+
const diff = buildResultFileDiff(filePath, before, after);
|
|
369
390
|
result.file_diff = diff;
|
|
370
391
|
result.diff = diff.unified;
|
|
371
392
|
result.lines_added = diff.lines_added;
|
|
372
393
|
result.lines_removed = diff.lines_removed;
|
|
394
|
+
if (diff.redacted) {
|
|
395
|
+
result.output = `File updated: ${diff.relative_path || filePath}\nDiff redacted for sensitive config file.`;
|
|
396
|
+
result.redacted = true;
|
|
397
|
+
}
|
|
373
398
|
} catch { /* best effort */ }
|
|
374
399
|
return result;
|
|
375
400
|
}
|
|
@@ -578,7 +603,7 @@ export function createToolExecutor({
|
|
|
578
603
|
if (!shellCheck.safe) {
|
|
579
604
|
return {
|
|
580
605
|
success: false,
|
|
581
|
-
output:
|
|
606
|
+
output: blockedShellOutput(shellCheck.reason),
|
|
582
607
|
_tool: 'shell', _blocked: true,
|
|
583
608
|
};
|
|
584
609
|
}
|
|
@@ -588,7 +613,7 @@ export function createToolExecutor({
|
|
|
588
613
|
if (classification.classification === 'blocked') {
|
|
589
614
|
return {
|
|
590
615
|
success: false,
|
|
591
|
-
output:
|
|
616
|
+
output: blockedShellOutput(classification.reason),
|
|
592
617
|
_tool: 'shell', _blocked: true,
|
|
593
618
|
};
|
|
594
619
|
}
|
|
@@ -813,12 +838,7 @@ export function createToolExecutor({
|
|
|
813
838
|
|
|
814
839
|
await occRegistry.call('Write', { file_path: filePath, content });
|
|
815
840
|
const after = readTextIfExists(filePath);
|
|
816
|
-
diffs.push(
|
|
817
|
-
filePath,
|
|
818
|
-
before,
|
|
819
|
-
after,
|
|
820
|
-
cwd: projectRootFor(filePath),
|
|
821
|
-
}));
|
|
841
|
+
diffs.push(buildResultFileDiff(filePath, before, after));
|
|
822
842
|
updateProjectIndex(filePath);
|
|
823
843
|
results.push(rawPath);
|
|
824
844
|
} catch (err) {
|
|
@@ -1513,7 +1533,9 @@ print('OK: replaced')
|
|
|
1513
1533
|
const created = listLocalAgents(process.cwd()).find(agent => agent.slug === result.slug);
|
|
1514
1534
|
const payload = {
|
|
1515
1535
|
...result,
|
|
1516
|
-
agent: created
|
|
1536
|
+
agent: created
|
|
1537
|
+
? { ...compactAgentMetadata(created), spec: created.spec }
|
|
1538
|
+
: null,
|
|
1517
1539
|
next_actions: [
|
|
1518
1540
|
`Edit ${result.filePath}`,
|
|
1519
1541
|
`Run /agents sync ${result.slug} when ready`,
|
package/src/core/trust.mjs
CHANGED
|
@@ -96,11 +96,13 @@ export class TrustStore {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
if ((reask.on_risk_increase ?? this.policy.hitl?.reaskOnRiskIncrease) && rule.tier && tier) {
|
|
99
|
-
const order = [TIERS.READ, TIERS.SHELL_SAFE, TIERS.LOCAL_EDIT, TIERS.SHELL_MEDIUM, TIERS.NETWORK, TIERS.SHELL_DANGEROUS, TIERS.DESTRUCTIVE];
|
|
99
|
+
const order = [TIERS.READ, TIERS.SHELL_SAFE, TIERS.LOCAL_EDIT, TIERS.PROTECTED_EDIT, TIERS.SHELL_MEDIUM, TIERS.NETWORK, TIERS.SHELL_DANGEROUS, TIERS.DESTRUCTIVE];
|
|
100
100
|
if (order.indexOf(tier) > order.indexOf(rule.tier)) return 'risk tier increased';
|
|
101
101
|
}
|
|
102
|
-
if ((this.policy.hitl?.alwaysAskForDangerous ?? true) && (tier === TIERS.SHELL_DANGEROUS || tier === TIERS.DESTRUCTIVE)) {
|
|
103
|
-
return
|
|
102
|
+
if ((this.policy.hitl?.alwaysAskForDangerous ?? true) && (tier === TIERS.SHELL_DANGEROUS || tier === TIERS.DESTRUCTIVE || tier === TIERS.PROTECTED_EDIT)) {
|
|
103
|
+
return tier === TIERS.PROTECTED_EDIT
|
|
104
|
+
? 'protected edit requires fresh approval'
|
|
105
|
+
: 'dangerous tier requires fresh approval';
|
|
104
106
|
}
|
|
105
107
|
if ((reask.on_command_shape_change ?? this.policy.hitl?.reaskOnCommandShapeChange) && rule.command_shape && args.command) {
|
|
106
108
|
if (rule.command_shape !== commandShape(args.command)) return 'command shape changed';
|
package/src/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ import { printBanner, printProjectInfo, printHints, printAuthStatus, printStyled
|
|
|
35
35
|
import { ContextRetriever } from './context/retriever.mjs';
|
|
36
36
|
import { loadSettings } from './config/settings.mjs';
|
|
37
37
|
|
|
38
|
-
const VERSION = '2.6.
|
|
38
|
+
const VERSION = '2.6.15';
|
|
39
39
|
|
|
40
40
|
// ── Arg Parsing (consolidated from index.mjs + cli-args.mjs) ──
|
|
41
41
|
|