codebot-ai 1.6.0 → 1.8.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/dist/agent.d.ts +22 -0
- package/dist/agent.js +138 -5
- package/dist/audit.d.ts +31 -9
- package/dist/audit.js +85 -11
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +187 -0
- package/dist/cli.js +265 -26
- package/dist/history.d.ts +7 -3
- package/dist/history.js +55 -8
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -1
- package/dist/integrity.d.ts +35 -0
- package/dist/integrity.js +135 -0
- package/dist/policy.d.ts +132 -0
- package/dist/policy.js +444 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +4 -0
- package/dist/providers/openai.d.ts +1 -0
- package/dist/providers/openai.js +4 -0
- package/dist/replay.d.ts +55 -0
- package/dist/replay.js +196 -0
- package/dist/sandbox.d.ts +65 -0
- package/dist/sandbox.js +214 -0
- package/dist/telemetry.d.ts +73 -0
- package/dist/telemetry.js +286 -0
- package/dist/tools/batch-edit.d.ts +3 -0
- package/dist/tools/batch-edit.js +12 -0
- package/dist/tools/edit.d.ts +3 -0
- package/dist/tools/edit.js +11 -0
- package/dist/tools/execute.js +29 -4
- package/dist/tools/git.d.ts +5 -0
- package/dist/tools/git.js +31 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +6 -6
- package/dist/tools/write.d.ts +3 -0
- package/dist/tools/write.js +11 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Token & Cost Tracking for CodeBot v1.7.0
|
|
4
|
+
*
|
|
5
|
+
* Tracks per-request and per-session token usage and estimated costs.
|
|
6
|
+
* Supports cost limits and historical usage queries.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.TokenTracker = void 0;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const PRICING = {
|
|
47
|
+
// Anthropic
|
|
48
|
+
'claude-opus-4-6': { input: 15.0, output: 75.0 },
|
|
49
|
+
'claude-sonnet-4-20250514': { input: 3.0, output: 15.0 },
|
|
50
|
+
'claude-haiku-3-5': { input: 0.80, output: 4.0 },
|
|
51
|
+
// OpenAI
|
|
52
|
+
'gpt-4o': { input: 2.50, output: 10.0 },
|
|
53
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60 },
|
|
54
|
+
'gpt-4.1': { input: 2.0, output: 8.0 },
|
|
55
|
+
'gpt-4.1-mini': { input: 0.40, output: 1.60 },
|
|
56
|
+
'gpt-4.1-nano': { input: 0.10, output: 0.40 },
|
|
57
|
+
'o1': { input: 15.0, output: 60.0 },
|
|
58
|
+
'o3': { input: 10.0, output: 40.0 },
|
|
59
|
+
'o3-mini': { input: 1.10, output: 4.40 },
|
|
60
|
+
'o4-mini': { input: 1.10, output: 4.40 },
|
|
61
|
+
// Google
|
|
62
|
+
'gemini-2.5-pro': { input: 1.25, output: 10.0 },
|
|
63
|
+
'gemini-2.5-flash': { input: 0.15, output: 0.60 },
|
|
64
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40 },
|
|
65
|
+
// DeepSeek
|
|
66
|
+
'deepseek-chat': { input: 0.14, output: 0.28 },
|
|
67
|
+
'deepseek-reasoner': { input: 0.55, output: 2.19 },
|
|
68
|
+
// Groq (free tier pricing)
|
|
69
|
+
'llama-3.3-70b-versatile': { input: 0.59, output: 0.79 },
|
|
70
|
+
// Mistral
|
|
71
|
+
'mistral-large-latest': { input: 2.0, output: 6.0 },
|
|
72
|
+
'codestral-latest': { input: 0.30, output: 0.90 },
|
|
73
|
+
// xAI
|
|
74
|
+
'grok-3': { input: 3.0, output: 15.0 },
|
|
75
|
+
'grok-3-mini': { input: 0.30, output: 0.50 },
|
|
76
|
+
};
|
|
77
|
+
// Default pricing for unknown models (conservative estimate)
|
|
78
|
+
const DEFAULT_PRICING = { input: 2.0, output: 8.0 };
|
|
79
|
+
// Free pricing for local models
|
|
80
|
+
const LOCAL_PRICING = { input: 0, output: 0 };
|
|
81
|
+
class TokenTracker {
|
|
82
|
+
model;
|
|
83
|
+
provider;
|
|
84
|
+
sessionId;
|
|
85
|
+
records = [];
|
|
86
|
+
toolCallCount = 0;
|
|
87
|
+
filesModifiedSet = new Set();
|
|
88
|
+
startTime;
|
|
89
|
+
costLimitUsd = 0;
|
|
90
|
+
constructor(model, provider, sessionId) {
|
|
91
|
+
this.model = model;
|
|
92
|
+
this.provider = provider;
|
|
93
|
+
this.sessionId = sessionId || `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
94
|
+
this.startTime = new Date().toISOString();
|
|
95
|
+
}
|
|
96
|
+
/** Set cost limit in USD. 0 = no limit. */
|
|
97
|
+
setCostLimit(usd) {
|
|
98
|
+
this.costLimitUsd = usd;
|
|
99
|
+
}
|
|
100
|
+
/** Record token usage from an LLM request */
|
|
101
|
+
recordUsage(inputTokens, outputTokens) {
|
|
102
|
+
const pricing = this.getPricing();
|
|
103
|
+
const costUsd = (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
|
|
104
|
+
const record = {
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
model: this.model,
|
|
107
|
+
provider: this.provider,
|
|
108
|
+
inputTokens,
|
|
109
|
+
outputTokens,
|
|
110
|
+
costUsd,
|
|
111
|
+
};
|
|
112
|
+
this.records.push(record);
|
|
113
|
+
return record;
|
|
114
|
+
}
|
|
115
|
+
/** Record a tool call (for summary) */
|
|
116
|
+
recordToolCall() {
|
|
117
|
+
this.toolCallCount++;
|
|
118
|
+
}
|
|
119
|
+
/** Record a file modification (for summary) */
|
|
120
|
+
recordFileModified(filePath) {
|
|
121
|
+
this.filesModifiedSet.add(filePath);
|
|
122
|
+
}
|
|
123
|
+
/** Check if cost limit has been exceeded */
|
|
124
|
+
isOverBudget() {
|
|
125
|
+
if (this.costLimitUsd <= 0)
|
|
126
|
+
return false;
|
|
127
|
+
return this.getTotalCost() >= this.costLimitUsd;
|
|
128
|
+
}
|
|
129
|
+
/** Get remaining budget in USD (Infinity if no limit) */
|
|
130
|
+
getRemainingBudget() {
|
|
131
|
+
if (this.costLimitUsd <= 0)
|
|
132
|
+
return Infinity;
|
|
133
|
+
return Math.max(0, this.costLimitUsd - this.getTotalCost());
|
|
134
|
+
}
|
|
135
|
+
// ── Aggregates ──
|
|
136
|
+
getTotalInputTokens() {
|
|
137
|
+
return this.records.reduce((sum, r) => sum + r.inputTokens, 0);
|
|
138
|
+
}
|
|
139
|
+
getTotalOutputTokens() {
|
|
140
|
+
return this.records.reduce((sum, r) => sum + r.outputTokens, 0);
|
|
141
|
+
}
|
|
142
|
+
getTotalCost() {
|
|
143
|
+
return this.records.reduce((sum, r) => sum + r.costUsd, 0);
|
|
144
|
+
}
|
|
145
|
+
getRequestCount() {
|
|
146
|
+
return this.records.length;
|
|
147
|
+
}
|
|
148
|
+
/** Generate a session summary */
|
|
149
|
+
getSummary() {
|
|
150
|
+
return {
|
|
151
|
+
sessionId: this.sessionId,
|
|
152
|
+
model: this.model,
|
|
153
|
+
provider: this.provider,
|
|
154
|
+
startTime: this.startTime,
|
|
155
|
+
endTime: new Date().toISOString(),
|
|
156
|
+
totalInputTokens: this.getTotalInputTokens(),
|
|
157
|
+
totalOutputTokens: this.getTotalOutputTokens(),
|
|
158
|
+
totalCostUsd: this.getTotalCost(),
|
|
159
|
+
requestCount: this.getRequestCount(),
|
|
160
|
+
toolCalls: this.toolCallCount,
|
|
161
|
+
filesModified: this.filesModifiedSet.size,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** Format cost for display */
|
|
165
|
+
formatCost() {
|
|
166
|
+
const cost = this.getTotalCost();
|
|
167
|
+
if (cost === 0)
|
|
168
|
+
return 'free (local model)';
|
|
169
|
+
if (cost < 0.01)
|
|
170
|
+
return `< $0.01`;
|
|
171
|
+
return `$${cost.toFixed(4)}`;
|
|
172
|
+
}
|
|
173
|
+
/** Format a compact status line for CLI */
|
|
174
|
+
formatStatusLine() {
|
|
175
|
+
const inTk = this.getTotalInputTokens();
|
|
176
|
+
const outTk = this.getTotalOutputTokens();
|
|
177
|
+
const cost = this.formatCost();
|
|
178
|
+
return `${inTk.toLocaleString()} in / ${outTk.toLocaleString()} out | ${cost}`;
|
|
179
|
+
}
|
|
180
|
+
/** Save session usage to ~/.codebot/usage/ for historical tracking */
|
|
181
|
+
saveUsage() {
|
|
182
|
+
try {
|
|
183
|
+
const usageDir = path.join(os.homedir(), '.codebot', 'usage');
|
|
184
|
+
fs.mkdirSync(usageDir, { recursive: true });
|
|
185
|
+
const summary = this.getSummary();
|
|
186
|
+
const fileName = `usage-${summary.startTime.split('T')[0]}.jsonl`;
|
|
187
|
+
const filePath = path.join(usageDir, fileName);
|
|
188
|
+
fs.appendFileSync(filePath, JSON.stringify(summary) + '\n', 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Usage tracking failures are non-fatal
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Load historical usage from ~/.codebot/usage/
|
|
196
|
+
*/
|
|
197
|
+
static loadHistory(days) {
|
|
198
|
+
const summaries = [];
|
|
199
|
+
try {
|
|
200
|
+
const usageDir = path.join(os.homedir(), '.codebot', 'usage');
|
|
201
|
+
if (!fs.existsSync(usageDir))
|
|
202
|
+
return [];
|
|
203
|
+
const files = fs.readdirSync(usageDir)
|
|
204
|
+
.filter(f => f.startsWith('usage-') && f.endsWith('.jsonl'))
|
|
205
|
+
.sort();
|
|
206
|
+
// Filter by date range if specified
|
|
207
|
+
const cutoff = days
|
|
208
|
+
? new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString()
|
|
209
|
+
: undefined;
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
const content = fs.readFileSync(path.join(usageDir, file), 'utf-8');
|
|
212
|
+
for (const line of content.split('\n')) {
|
|
213
|
+
if (!line.trim())
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
const summary = JSON.parse(line);
|
|
217
|
+
if (cutoff && summary.startTime < cutoff)
|
|
218
|
+
continue;
|
|
219
|
+
summaries.push(summary);
|
|
220
|
+
}
|
|
221
|
+
catch { /* skip malformed */ }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Can't read usage
|
|
227
|
+
}
|
|
228
|
+
return summaries;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Format a historical usage report.
|
|
232
|
+
*/
|
|
233
|
+
static formatUsageReport(days = 30) {
|
|
234
|
+
const history = TokenTracker.loadHistory(days);
|
|
235
|
+
if (history.length === 0)
|
|
236
|
+
return 'No usage data found.';
|
|
237
|
+
let totalInput = 0;
|
|
238
|
+
let totalOutput = 0;
|
|
239
|
+
let totalCost = 0;
|
|
240
|
+
let totalRequests = 0;
|
|
241
|
+
let totalTools = 0;
|
|
242
|
+
for (const s of history) {
|
|
243
|
+
totalInput += s.totalInputTokens;
|
|
244
|
+
totalOutput += s.totalOutputTokens;
|
|
245
|
+
totalCost += s.totalCostUsd;
|
|
246
|
+
totalRequests += s.requestCount;
|
|
247
|
+
totalTools += s.toolCalls;
|
|
248
|
+
}
|
|
249
|
+
const lines = [
|
|
250
|
+
`Usage Report (last ${days} days)`,
|
|
251
|
+
'─'.repeat(40),
|
|
252
|
+
`Sessions: ${history.length}`,
|
|
253
|
+
`LLM Requests: ${totalRequests.toLocaleString()}`,
|
|
254
|
+
`Tool Calls: ${totalTools.toLocaleString()}`,
|
|
255
|
+
`Input Tokens: ${totalInput.toLocaleString()}`,
|
|
256
|
+
`Output Tokens: ${totalOutput.toLocaleString()}`,
|
|
257
|
+
`Total Cost: $${totalCost.toFixed(4)}`,
|
|
258
|
+
];
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
}
|
|
261
|
+
// ── Helpers ──
|
|
262
|
+
getPricing() {
|
|
263
|
+
// Local models are free
|
|
264
|
+
if (this.isLocalModel())
|
|
265
|
+
return LOCAL_PRICING;
|
|
266
|
+
// Exact match
|
|
267
|
+
if (PRICING[this.model])
|
|
268
|
+
return PRICING[this.model];
|
|
269
|
+
// Prefix match (for versioned models like claude-sonnet-4-20250514)
|
|
270
|
+
for (const [key, pricing] of Object.entries(PRICING)) {
|
|
271
|
+
if (this.model.startsWith(key))
|
|
272
|
+
return pricing;
|
|
273
|
+
}
|
|
274
|
+
return DEFAULT_PRICING;
|
|
275
|
+
}
|
|
276
|
+
isLocalModel() {
|
|
277
|
+
// Models running on Ollama, LM Studio, vLLM are free
|
|
278
|
+
return !this.provider ||
|
|
279
|
+
this.provider === 'ollama' ||
|
|
280
|
+
this.provider === 'lmstudio' ||
|
|
281
|
+
this.provider === 'vllm' ||
|
|
282
|
+
this.provider === 'local';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.TokenTracker = TokenTracker;
|
|
286
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class BatchEditTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
8
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
6
9
|
parameters: {
|
|
7
10
|
type: string;
|
|
8
11
|
properties: {
|
package/dist/tools/batch-edit.js
CHANGED
|
@@ -42,6 +42,10 @@ class BatchEditTool {
|
|
|
42
42
|
name = 'batch_edit';
|
|
43
43
|
description = 'Apply multiple find-and-replace edits across one or more files atomically. All edits are validated before any are applied. Useful for renaming, refactoring, and coordinated multi-file changes.';
|
|
44
44
|
permission = 'prompt';
|
|
45
|
+
policyEnforcer;
|
|
46
|
+
constructor(policyEnforcer) {
|
|
47
|
+
this.policyEnforcer = policyEnforcer;
|
|
48
|
+
}
|
|
45
49
|
parameters = {
|
|
46
50
|
type: 'object',
|
|
47
51
|
properties: {
|
|
@@ -85,6 +89,14 @@ class BatchEditTool {
|
|
|
85
89
|
errors.push(`${safety.reason}`);
|
|
86
90
|
continue;
|
|
87
91
|
}
|
|
92
|
+
// Policy: filesystem restrictions
|
|
93
|
+
if (this.policyEnforcer) {
|
|
94
|
+
const policyCheck = this.policyEnforcer.isPathWritable(filePath);
|
|
95
|
+
if (!policyCheck.allowed) {
|
|
96
|
+
errors.push(`Blocked by policy — ${policyCheck.reason}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
88
100
|
if (!byFile.has(filePath))
|
|
89
101
|
byFile.set(filePath, []);
|
|
90
102
|
byFile.get(filePath).push(edit);
|
package/dist/tools/edit.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class EditFileTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
8
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
6
9
|
parameters: {
|
|
7
10
|
type: string;
|
|
8
11
|
properties: {
|
package/dist/tools/edit.js
CHANGED
|
@@ -46,6 +46,10 @@ class EditFileTool {
|
|
|
46
46
|
name = 'edit_file';
|
|
47
47
|
description = 'Edit a file by replacing an exact string match with new content. The old_string must appear exactly once in the file. Shows a diff preview and creates an undo snapshot.';
|
|
48
48
|
permission = 'prompt';
|
|
49
|
+
policyEnforcer;
|
|
50
|
+
constructor(policyEnforcer) {
|
|
51
|
+
this.policyEnforcer = policyEnforcer;
|
|
52
|
+
}
|
|
49
53
|
parameters = {
|
|
50
54
|
type: 'object',
|
|
51
55
|
properties: {
|
|
@@ -74,6 +78,13 @@ class EditFileTool {
|
|
|
74
78
|
if (!safety.safe) {
|
|
75
79
|
return `Error: ${safety.reason}`;
|
|
76
80
|
}
|
|
81
|
+
// Policy: filesystem restrictions (denied paths, read-only, writable scope)
|
|
82
|
+
if (this.policyEnforcer) {
|
|
83
|
+
const policyCheck = this.policyEnforcer.isPathWritable(filePath);
|
|
84
|
+
if (!policyCheck.allowed) {
|
|
85
|
+
return `Error: Blocked by policy — ${policyCheck.reason}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
77
88
|
// Security: resolve symlinks before reading
|
|
78
89
|
let realPath;
|
|
79
90
|
try {
|
package/dist/tools/execute.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ExecuteTool = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const security_1 = require("../security");
|
|
6
|
+
const sandbox_1 = require("../sandbox");
|
|
7
|
+
const policy_1 = require("../policy");
|
|
6
8
|
const BLOCKED_PATTERNS = [
|
|
7
9
|
// Destructive filesystem operations
|
|
8
10
|
/rm\s+-rf\s+\//,
|
|
@@ -109,25 +111,48 @@ class ExecuteTool {
|
|
|
109
111
|
if (!cwdSafety.safe) {
|
|
110
112
|
return `Error: ${cwdSafety.reason}`;
|
|
111
113
|
}
|
|
112
|
-
|
|
114
|
+
const timeout = args.timeout || 30000;
|
|
115
|
+
// ── v1.7.0: Sandbox routing ──
|
|
116
|
+
const policy = (0, policy_1.loadPolicy)(projectRoot);
|
|
117
|
+
const sandboxMode = policy.execution?.sandbox || 'auto';
|
|
118
|
+
const useSandbox = sandboxMode === 'docker' ||
|
|
119
|
+
(sandboxMode === 'auto' && (0, sandbox_1.isDockerAvailable)());
|
|
120
|
+
if (useSandbox) {
|
|
121
|
+
const result = (0, sandbox_1.sandboxExec)(cmd, projectRoot, {
|
|
122
|
+
network: policy.execution?.network !== false,
|
|
123
|
+
memoryMb: policy.execution?.max_memory_mb || 512,
|
|
124
|
+
timeoutMs: timeout,
|
|
125
|
+
});
|
|
126
|
+
if (result.sandboxed) {
|
|
127
|
+
const output = result.stdout || result.stderr || '(no output)';
|
|
128
|
+
const tag = '[sandboxed]';
|
|
129
|
+
if (result.exitCode !== 0) {
|
|
130
|
+
return `${tag} Exit code ${result.exitCode}\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`;
|
|
131
|
+
}
|
|
132
|
+
return `${tag} ${output}`;
|
|
133
|
+
}
|
|
134
|
+
// Fallthrough: sandboxExec returned sandboxed=false (Docker wasn't available after all)
|
|
135
|
+
}
|
|
136
|
+
// ── Host execution (existing path) ──
|
|
113
137
|
const safeEnv = { ...process.env };
|
|
114
138
|
for (const key of FILTERED_ENV_VARS) {
|
|
115
139
|
delete safeEnv[key];
|
|
116
140
|
}
|
|
141
|
+
const tag = useSandbox ? '[host-fallback]' : '[host]';
|
|
117
142
|
try {
|
|
118
143
|
const output = (0, child_process_1.execSync)(cmd, {
|
|
119
144
|
cwd,
|
|
120
|
-
timeout
|
|
145
|
+
timeout,
|
|
121
146
|
maxBuffer: 1024 * 1024,
|
|
122
147
|
encoding: 'utf-8',
|
|
123
148
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
124
149
|
env: safeEnv,
|
|
125
150
|
});
|
|
126
|
-
return output || '(no output)'
|
|
151
|
+
return `${tag} ${output || '(no output)'}`;
|
|
127
152
|
}
|
|
128
153
|
catch (err) {
|
|
129
154
|
const e = err;
|
|
130
|
-
return
|
|
155
|
+
return `${tag} Exit code ${e.status || 1}\nSTDOUT:\n${e.stdout || ''}\nSTDERR:\n${e.stderr || ''}`;
|
|
131
156
|
}
|
|
132
157
|
}
|
|
133
158
|
}
|
package/dist/tools/git.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class GitTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
6
8
|
parameters: {
|
|
7
9
|
type: string;
|
|
8
10
|
properties: {
|
|
@@ -21,6 +23,9 @@ export declare class GitTool implements Tool {
|
|
|
21
23
|
};
|
|
22
24
|
required: string[];
|
|
23
25
|
};
|
|
26
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
24
27
|
execute(args: Record<string, unknown>): Promise<string>;
|
|
28
|
+
/** Get current git branch name. */
|
|
29
|
+
private getCurrentBranch;
|
|
25
30
|
}
|
|
26
31
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/tools/git.js
CHANGED
|
@@ -10,6 +10,7 @@ class GitTool {
|
|
|
10
10
|
name = 'git';
|
|
11
11
|
description = 'Run git operations. Actions: status, diff, log, commit, branch, checkout, stash, push, pull, merge, blame, tag, add, reset.';
|
|
12
12
|
permission = 'prompt';
|
|
13
|
+
policyEnforcer;
|
|
13
14
|
parameters = {
|
|
14
15
|
type: 'object',
|
|
15
16
|
properties: {
|
|
@@ -19,6 +20,9 @@ class GitTool {
|
|
|
19
20
|
},
|
|
20
21
|
required: ['action'],
|
|
21
22
|
};
|
|
23
|
+
constructor(policyEnforcer) {
|
|
24
|
+
this.policyEnforcer = policyEnforcer;
|
|
25
|
+
}
|
|
22
26
|
async execute(args) {
|
|
23
27
|
const action = args.action;
|
|
24
28
|
if (!action)
|
|
@@ -36,6 +40,20 @@ class GitTool {
|
|
|
36
40
|
if (/clean\s+-[a-z]*f/i.test(fullCmd)) {
|
|
37
41
|
return 'Error: git clean -f is blocked for safety.';
|
|
38
42
|
}
|
|
43
|
+
// Policy: block push to main/master when never_push_main=true
|
|
44
|
+
if (action === 'push' && this.policyEnforcer?.isMainPushBlocked()) {
|
|
45
|
+
const currentBranch = this.getCurrentBranch(cwd);
|
|
46
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
47
|
+
return 'Error: Pushing to main/master is blocked by policy (git.never_push_main=true). Create a feature branch first.';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Policy: block commit on main/master when always_branch=true
|
|
51
|
+
if (action === 'commit' && this.policyEnforcer?.shouldAlwaysBranch()) {
|
|
52
|
+
const currentBranch = this.getCurrentBranch(cwd);
|
|
53
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
54
|
+
return 'Error: Committing to main/master is blocked by policy (git.always_branch=true). Create a feature branch first.';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
try {
|
|
40
58
|
const output = (0, child_process_1.execSync)(fullCmd, {
|
|
41
59
|
cwd,
|
|
@@ -53,6 +71,19 @@ class GitTool {
|
|
|
53
71
|
return `Exit ${e.status || 1}${stdout ? `\n${stdout}` : ''}${stderr ? `\nError: ${stderr}` : ''}`;
|
|
54
72
|
}
|
|
55
73
|
}
|
|
74
|
+
/** Get current git branch name. */
|
|
75
|
+
getCurrentBranch(cwd) {
|
|
76
|
+
try {
|
|
77
|
+
return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
78
|
+
cwd,
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
timeout: 5000,
|
|
81
|
+
}).trim();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
}
|
|
57
88
|
exports.GitTool = GitTool;
|
|
58
89
|
//# sourceMappingURL=git.js.map
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Tool, ToolSchema } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export { EditFileTool } from './edit';
|
|
3
4
|
export declare class ToolRegistry {
|
|
4
5
|
private tools;
|
|
5
|
-
constructor(projectRoot?: string);
|
|
6
|
+
constructor(projectRoot?: string, policyEnforcer?: PolicyEnforcer);
|
|
6
7
|
register(tool: Tool): void;
|
|
7
8
|
get(name: string): Tool | undefined;
|
|
8
9
|
getSchemas(): ToolSchema[];
|
package/dist/tools/index.js
CHANGED
|
@@ -34,12 +34,12 @@ var edit_2 = require("./edit");
|
|
|
34
34
|
Object.defineProperty(exports, "EditFileTool", { enumerable: true, get: function () { return edit_2.EditFileTool; } });
|
|
35
35
|
class ToolRegistry {
|
|
36
36
|
tools = new Map();
|
|
37
|
-
constructor(projectRoot) {
|
|
38
|
-
// Core file tools
|
|
37
|
+
constructor(projectRoot, policyEnforcer) {
|
|
38
|
+
// Core file tools — policy-enforced tools receive the enforcer
|
|
39
39
|
this.register(new read_1.ReadFileTool());
|
|
40
|
-
this.register(new write_1.WriteFileTool());
|
|
41
|
-
this.register(new edit_1.EditFileTool());
|
|
42
|
-
this.register(new batch_edit_1.BatchEditTool());
|
|
40
|
+
this.register(new write_1.WriteFileTool(policyEnforcer));
|
|
41
|
+
this.register(new edit_1.EditFileTool(policyEnforcer));
|
|
42
|
+
this.register(new batch_edit_1.BatchEditTool(policyEnforcer));
|
|
43
43
|
this.register(new execute_1.ExecuteTool());
|
|
44
44
|
this.register(new glob_1.GlobTool());
|
|
45
45
|
this.register(new grep_1.GrepTool());
|
|
@@ -51,7 +51,7 @@ class ToolRegistry {
|
|
|
51
51
|
this.register(new browser_1.BrowserTool());
|
|
52
52
|
this.register(new routine_1.RoutineTool());
|
|
53
53
|
// v1.4.0 — intelligence & dev tools
|
|
54
|
-
this.register(new git_1.GitTool());
|
|
54
|
+
this.register(new git_1.GitTool(policyEnforcer));
|
|
55
55
|
this.register(new code_analysis_1.CodeAnalysisTool());
|
|
56
56
|
this.register(new multi_search_1.MultiSearchTool());
|
|
57
57
|
this.register(new task_planner_1.TaskPlannerTool());
|
package/dist/tools/write.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class WriteFileTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
8
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
6
9
|
parameters: {
|
|
7
10
|
type: string;
|
|
8
11
|
properties: {
|
package/dist/tools/write.js
CHANGED
|
@@ -44,6 +44,10 @@ class WriteFileTool {
|
|
|
44
44
|
name = 'write_file';
|
|
45
45
|
description = 'Create a new file or overwrite an existing file with the given content. Automatically saves an undo snapshot for existing files.';
|
|
46
46
|
permission = 'prompt';
|
|
47
|
+
policyEnforcer;
|
|
48
|
+
constructor(policyEnforcer) {
|
|
49
|
+
this.policyEnforcer = policyEnforcer;
|
|
50
|
+
}
|
|
47
51
|
parameters = {
|
|
48
52
|
type: 'object',
|
|
49
53
|
properties: {
|
|
@@ -68,6 +72,13 @@ class WriteFileTool {
|
|
|
68
72
|
if (!safety.safe) {
|
|
69
73
|
return `Error: ${safety.reason}`;
|
|
70
74
|
}
|
|
75
|
+
// Policy: filesystem restrictions (denied paths, read-only, writable scope)
|
|
76
|
+
if (this.policyEnforcer) {
|
|
77
|
+
const policyCheck = this.policyEnforcer.isPathWritable(filePath);
|
|
78
|
+
if (!policyCheck.allowed) {
|
|
79
|
+
return `Error: Blocked by policy — ${policyCheck.reason}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
71
82
|
// Security: secret detection (warn but don't block)
|
|
72
83
|
const secrets = (0, secrets_1.scanForSecrets)(content);
|
|
73
84
|
let warning = '';
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebot-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Zero-dependency autonomous AI agent. Code, browse, search, automate. Works with any LLM — Ollama, Claude, GPT, Gemini, DeepSeek, Groq, Mistral, Grok.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|