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.
@@ -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: {
@@ -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);
@@ -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: {
@@ -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 {
@@ -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
- // Security: filter sensitive env vars
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: args.timeout || 30000,
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 `Exit code ${e.status || 1}\nSTDOUT:\n${e.stdout || ''}\nSTDERR:\n${e.stderr || ''}`;
155
+ return `${tag} Exit code ${e.status || 1}\nSTDOUT:\n${e.stdout || ''}\nSTDERR:\n${e.stderr || ''}`;
131
156
  }
132
157
  }
133
158
  }
@@ -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
@@ -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[];
@@ -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());
@@ -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: {
@@ -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
@@ -33,6 +33,7 @@ export interface ProviderConfig {
33
33
  }
34
34
  export interface LLMProvider {
35
35
  name: string;
36
+ temperature?: number;
36
37
  chat(messages: Message[], tools?: ToolSchema[]): AsyncGenerator<StreamEvent>;
37
38
  listModels?(): Promise<string[]>;
38
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebot-ai",
3
- "version": "1.6.0",
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",