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,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Capability-Based Tool Permissions for CodeBot v1.8.0
|
|
4
|
+
*
|
|
5
|
+
* Fine-grained, per-tool resource restrictions.
|
|
6
|
+
* Configured via .codebot/policy.json → tools.capabilities.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* {
|
|
10
|
+
* "execute": {
|
|
11
|
+
* "shell_commands": ["npm", "node", "git", "tsc"],
|
|
12
|
+
* "max_output_kb": 500
|
|
13
|
+
* },
|
|
14
|
+
* "write_file": {
|
|
15
|
+
* "fs_write": ["./src/**", "./tests/**"]
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.CapabilityChecker = void 0;
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
// ── Capability Checker ──
|
|
56
|
+
class CapabilityChecker {
|
|
57
|
+
capabilities;
|
|
58
|
+
projectRoot;
|
|
59
|
+
constructor(capabilities, projectRoot) {
|
|
60
|
+
this.capabilities = capabilities;
|
|
61
|
+
this.projectRoot = projectRoot;
|
|
62
|
+
}
|
|
63
|
+
/** Get capabilities for a tool. undefined = no restrictions. */
|
|
64
|
+
getToolCapabilities(toolName) {
|
|
65
|
+
return this.capabilities[toolName];
|
|
66
|
+
}
|
|
67
|
+
/** Check if a specific capability is allowed. */
|
|
68
|
+
checkCapability(toolName, capabilityType, value) {
|
|
69
|
+
const caps = this.capabilities[toolName];
|
|
70
|
+
if (!caps)
|
|
71
|
+
return { allowed: true }; // No caps defined = unrestricted
|
|
72
|
+
switch (capabilityType) {
|
|
73
|
+
case 'fs_read':
|
|
74
|
+
return this.checkGlobs(caps.fs_read, value, 'fs_read', toolName);
|
|
75
|
+
case 'fs_write':
|
|
76
|
+
return this.checkGlobs(caps.fs_write, value, 'fs_write', toolName);
|
|
77
|
+
case 'net_access':
|
|
78
|
+
return this.checkDomain(caps.net_access, value, toolName);
|
|
79
|
+
case 'shell_commands':
|
|
80
|
+
return this.checkCommandPrefix(caps.shell_commands, value, toolName);
|
|
81
|
+
case 'max_output_kb':
|
|
82
|
+
return this.checkOutputSize(caps.max_output_kb, value, toolName);
|
|
83
|
+
default:
|
|
84
|
+
return { allowed: true };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Check if a path matches any of the allowed glob patterns. */
|
|
88
|
+
checkGlobs(patterns, filePath, capName, toolName) {
|
|
89
|
+
if (!patterns || patterns.length === 0)
|
|
90
|
+
return { allowed: true };
|
|
91
|
+
const resolved = path.resolve(filePath);
|
|
92
|
+
const relative = path.relative(this.projectRoot, resolved);
|
|
93
|
+
// Don't restrict paths outside the project (those are handled by security.ts)
|
|
94
|
+
if (relative.startsWith('..'))
|
|
95
|
+
return { allowed: true };
|
|
96
|
+
for (const pattern of patterns) {
|
|
97
|
+
if (this.matchGlob(relative, pattern)) {
|
|
98
|
+
return { allowed: true };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
allowed: false,
|
|
103
|
+
reason: `Tool "${toolName}" ${capName} capability blocks "${relative}" (allowed: ${patterns.join(', ')})`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Check if a domain is in the allowed list. */
|
|
107
|
+
checkDomain(allowed, domain, toolName) {
|
|
108
|
+
if (allowed === undefined)
|
|
109
|
+
return { allowed: true }; // undefined = unrestricted
|
|
110
|
+
if (allowed.length === 0) {
|
|
111
|
+
// Empty array = no network access allowed
|
|
112
|
+
return {
|
|
113
|
+
allowed: false,
|
|
114
|
+
reason: `Tool "${toolName}" has no allowed network domains`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (allowed.includes('*'))
|
|
118
|
+
return { allowed: true };
|
|
119
|
+
const normalizedDomain = domain.toLowerCase();
|
|
120
|
+
for (const d of allowed) {
|
|
121
|
+
const nd = d.toLowerCase();
|
|
122
|
+
if (normalizedDomain === nd)
|
|
123
|
+
return { allowed: true };
|
|
124
|
+
if (normalizedDomain.endsWith('.' + nd))
|
|
125
|
+
return { allowed: true };
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
allowed: false,
|
|
129
|
+
reason: `Tool "${toolName}" cannot access domain "${domain}" (allowed: ${allowed.join(', ')})`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Check if a command starts with an allowed prefix. */
|
|
133
|
+
checkCommandPrefix(allowed, command, toolName) {
|
|
134
|
+
if (!allowed || allowed.length === 0)
|
|
135
|
+
return { allowed: true };
|
|
136
|
+
const cmd = command.trim();
|
|
137
|
+
for (const prefix of allowed) {
|
|
138
|
+
if (cmd === prefix || cmd.startsWith(prefix + ' ')) {
|
|
139
|
+
return { allowed: true };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Extract first word for the error message
|
|
143
|
+
const firstWord = cmd.split(/\s+/)[0] || cmd.substring(0, 30);
|
|
144
|
+
return {
|
|
145
|
+
allowed: false,
|
|
146
|
+
reason: `Tool "${toolName}" cannot run "${firstWord}" (allowed commands: ${allowed.join(', ')})`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Check if output size is within the cap. */
|
|
150
|
+
checkOutputSize(maxKb, actualKb, toolName) {
|
|
151
|
+
if (maxKb === undefined || maxKb <= 0)
|
|
152
|
+
return { allowed: true };
|
|
153
|
+
if (actualKb <= maxKb)
|
|
154
|
+
return { allowed: true };
|
|
155
|
+
return {
|
|
156
|
+
allowed: false,
|
|
157
|
+
reason: `Tool "${toolName}" output ${actualKb}KB exceeds cap of ${maxKb}KB`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Simple glob matching (** = any depth, * = one segment). */
|
|
161
|
+
matchGlob(relativePath, pattern) {
|
|
162
|
+
const cleanPattern = pattern.replace(/^\.\//, '');
|
|
163
|
+
const cleanPath = relativePath.replace(/^\.\//, '');
|
|
164
|
+
// Exact match
|
|
165
|
+
if (cleanPath === cleanPattern)
|
|
166
|
+
return true;
|
|
167
|
+
// Prefix match (directory)
|
|
168
|
+
if (cleanPath.startsWith(cleanPattern + '/'))
|
|
169
|
+
return true;
|
|
170
|
+
if (cleanPath.startsWith(cleanPattern + path.sep))
|
|
171
|
+
return true;
|
|
172
|
+
// Glob expansion
|
|
173
|
+
if (pattern.includes('*')) {
|
|
174
|
+
const regex = new RegExp('^' +
|
|
175
|
+
cleanPattern
|
|
176
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
177
|
+
.replace(/\*\*/g, '<<GLOBSTAR>>')
|
|
178
|
+
.replace(/\*/g, '[^/]*')
|
|
179
|
+
.replace(/<<GLOBSTAR>>/g, '.*') +
|
|
180
|
+
'$');
|
|
181
|
+
return regex.test(cleanPath);
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.CapabilityChecker = CapabilityChecker;
|
|
187
|
+
//# sourceMappingURL=capabilities.js.map
|
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.main = main;
|
|
37
37
|
const readline = __importStar(require("readline"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
38
40
|
const agent_1 = require("./agent");
|
|
39
41
|
const openai_1 = require("./providers/openai");
|
|
40
42
|
const anthropic_1 = require("./providers/anthropic");
|
|
@@ -44,9 +46,11 @@ const setup_1 = require("./setup");
|
|
|
44
46
|
const banner_1 = require("./banner");
|
|
45
47
|
const tools_1 = require("./tools");
|
|
46
48
|
const scheduler_1 = require("./scheduler");
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const audit_1 = require("./audit");
|
|
50
|
+
const policy_1 = require("./policy");
|
|
51
|
+
const sandbox_1 = require("./sandbox");
|
|
52
|
+
const replay_1 = require("./replay");
|
|
53
|
+
const VERSION = '1.8.0';
|
|
50
54
|
const C = {
|
|
51
55
|
reset: '\x1b[0m',
|
|
52
56
|
bold: '\x1b[1m',
|
|
@@ -86,17 +90,158 @@ async function main() {
|
|
|
86
90
|
await (0, setup_1.runSetup)();
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
93
|
+
// ── v1.7.0: New standalone commands ──
|
|
94
|
+
// --init-policy: Generate default policy file
|
|
95
|
+
if (args['init-policy']) {
|
|
96
|
+
const policyPath = path.join(process.cwd(), '.codebot', 'policy.json');
|
|
97
|
+
const policyDir = path.dirname(policyPath);
|
|
98
|
+
if (!fs.existsSync(policyDir))
|
|
99
|
+
fs.mkdirSync(policyDir, { recursive: true });
|
|
100
|
+
if (fs.existsSync(policyPath)) {
|
|
101
|
+
console.log(c(`Policy file already exists at ${policyPath}`, 'yellow'));
|
|
102
|
+
console.log(c('Delete it first if you want to regenerate.', 'dim'));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
fs.writeFileSync(policyPath, (0, policy_1.generateDefaultPolicyFile)(), 'utf-8');
|
|
106
|
+
console.log(c(`Created default policy at ${policyPath}`, 'green'));
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// --verify-audit: Verify audit chain integrity
|
|
111
|
+
if (args['verify-audit']) {
|
|
112
|
+
const logger = new audit_1.AuditLogger();
|
|
113
|
+
const sessionId = typeof args['verify-audit'] === 'string' ? args['verify-audit'] : undefined;
|
|
114
|
+
if (sessionId) {
|
|
115
|
+
const entries = logger.query({ sessionId });
|
|
116
|
+
if (entries.length === 0) {
|
|
117
|
+
console.log(c(`No audit entries found for session ${sessionId}`, 'yellow'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const result = audit_1.AuditLogger.verify(entries);
|
|
121
|
+
if (result.valid) {
|
|
122
|
+
console.log(c(`Audit chain valid (${result.entriesChecked} entries checked)`, 'green'));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(c(`Audit chain INVALID at sequence ${result.firstInvalidAt}`, 'red'));
|
|
126
|
+
console.log(c(`Reason: ${result.reason}`, 'red'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Verify all entries from today's log
|
|
131
|
+
const entries = logger.query();
|
|
132
|
+
if (entries.length === 0) {
|
|
133
|
+
console.log(c('No audit entries found.', 'yellow'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Group by session and verify each
|
|
137
|
+
const sessions = new Map();
|
|
138
|
+
for (const e of entries) {
|
|
139
|
+
if (!sessions.has(e.sessionId))
|
|
140
|
+
sessions.set(e.sessionId, []);
|
|
141
|
+
sessions.get(e.sessionId).push(e);
|
|
142
|
+
}
|
|
143
|
+
let allValid = true;
|
|
144
|
+
for (const [sid, sessionEntries] of sessions) {
|
|
145
|
+
const result = audit_1.AuditLogger.verify(sessionEntries);
|
|
146
|
+
const shortId = sid.substring(0, 12);
|
|
147
|
+
if (result.valid) {
|
|
148
|
+
console.log(c(` ${shortId} ${result.entriesChecked} entries valid`, 'green'));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(c(` ${shortId} INVALID at seq ${result.firstInvalidAt}: ${result.reason}`, 'red'));
|
|
152
|
+
allValid = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
console.log(allValid
|
|
156
|
+
? c(`\nAll ${sessions.size} session chains verified.`, 'green')
|
|
157
|
+
: c(`\nSome chains are invalid — possible tampering detected.`, 'red'));
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// --sandbox-info: Show sandbox status
|
|
162
|
+
if (args['sandbox-info']) {
|
|
163
|
+
const info = (0, sandbox_1.getSandboxInfo)();
|
|
164
|
+
console.log(c('Sandbox Status:', 'bold'));
|
|
165
|
+
console.log(` Docker: ${info.available ? c('available', 'green') : c('not available', 'yellow')}`);
|
|
166
|
+
console.log(` Image: ${info.image}`);
|
|
167
|
+
console.log(` CPU: ${info.defaults.cpus} cores max`);
|
|
168
|
+
console.log(` Memory: ${info.defaults.memoryMb}MB max`);
|
|
169
|
+
console.log(` Network: ${info.defaults.network ? 'enabled' : 'disabled'} by default`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// --replay: Replay a saved session
|
|
173
|
+
if (args.replay) {
|
|
174
|
+
const replayId = typeof args.replay === 'string'
|
|
175
|
+
? args.replay
|
|
176
|
+
: history_1.SessionManager.latest();
|
|
177
|
+
if (!replayId) {
|
|
178
|
+
console.log(c('No session to replay. Specify an ID or ensure a previous session exists.', 'yellow'));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const data = (0, replay_1.loadSessionForReplay)(replayId);
|
|
182
|
+
if (!data) {
|
|
183
|
+
console.log(c(`Session ${replayId} not found or empty.`, 'red'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log(c(`\nReplaying session ${replayId.substring(0, 12)}...`, 'cyan'));
|
|
187
|
+
console.log(c(` ${data.messages.length} messages (${data.userMessages.length} user, ${data.assistantMessages.length} assistant)`, 'dim'));
|
|
188
|
+
const replayProvider = new replay_1.ReplayProvider(data.assistantMessages);
|
|
189
|
+
const config = await resolveConfig(args);
|
|
190
|
+
const agent = new agent_1.Agent({
|
|
191
|
+
provider: replayProvider,
|
|
192
|
+
model: config.model,
|
|
193
|
+
providerName: 'replay',
|
|
194
|
+
autoApprove: true,
|
|
195
|
+
});
|
|
196
|
+
// Collect recorded tool results in order for sequential comparison
|
|
197
|
+
const recordedResults = Array.from(data.toolResults.values());
|
|
198
|
+
let resultIndex = 0;
|
|
199
|
+
let divergences = 0;
|
|
200
|
+
for (const userMsg of data.userMessages) {
|
|
201
|
+
console.log(c(`\n> ${truncate(userMsg.content, 100)}`, 'cyan'));
|
|
202
|
+
for await (const event of agent.run(userMsg.content)) {
|
|
203
|
+
if (event.type === 'tool_result' && event.toolResult && !event.toolResult.is_error) {
|
|
204
|
+
const recorded = recordedResults[resultIndex++];
|
|
205
|
+
if (recorded !== undefined) {
|
|
206
|
+
const diff = (0, replay_1.compareOutputs)(recorded, event.toolResult.result);
|
|
207
|
+
if (diff) {
|
|
208
|
+
divergences++;
|
|
209
|
+
console.log(c(` ⚠ Divergence in ${event.toolResult.name || 'tool'}:`, 'yellow'));
|
|
210
|
+
console.log(c(` ${diff.split('\n').join('\n ')}`, 'dim'));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.log(c(` ✓ ${event.toolResult.name || 'tool'} — output matches`, 'green'));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (event.type === 'text') {
|
|
218
|
+
process.stdout.write(c('.', 'dim'));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
console.log(c(`\n\nReplay complete.`, 'bold'));
|
|
223
|
+
if (divergences === 0) {
|
|
224
|
+
console.log(c(' All tool outputs match — session is reproducible.', 'green'));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log(c(` ${divergences} divergence(s) detected — environment may have changed.`, 'yellow'));
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
89
231
|
// First run: auto-launch setup if nothing is configured
|
|
90
232
|
if ((0, setup_1.isFirstRun)() && process.stdin.isTTY && !args.message) {
|
|
91
233
|
console.log(c('Welcome! No configuration found — launching setup...', 'cyan'));
|
|
92
234
|
await (0, setup_1.runSetup)();
|
|
93
|
-
// If setup saved a config, continue to main flow
|
|
94
|
-
// Otherwise exit
|
|
95
235
|
if ((0, setup_1.isFirstRun)())
|
|
96
236
|
return;
|
|
97
237
|
}
|
|
98
238
|
const config = await resolveConfig(args);
|
|
99
239
|
const provider = createProvider(config);
|
|
240
|
+
// Deterministic mode: set temperature=0
|
|
241
|
+
if (args.deterministic) {
|
|
242
|
+
provider.temperature = 0;
|
|
243
|
+
console.log(c(' Deterministic mode: temperature=0', 'dim'));
|
|
244
|
+
}
|
|
100
245
|
// Session management
|
|
101
246
|
let resumeId;
|
|
102
247
|
if (args.continue) {
|
|
@@ -118,6 +263,7 @@ async function main() {
|
|
|
118
263
|
const agent = new agent_1.Agent({
|
|
119
264
|
provider,
|
|
120
265
|
model: config.model,
|
|
266
|
+
providerName: config.provider,
|
|
121
267
|
maxIterations: config.maxIterations,
|
|
122
268
|
autoApprove: config.autoApprove,
|
|
123
269
|
onMessage: (msg) => session.save(msg),
|
|
@@ -133,6 +279,7 @@ async function main() {
|
|
|
133
279
|
// Non-interactive: single message from CLI args
|
|
134
280
|
if (typeof args.message === 'string') {
|
|
135
281
|
await runOnce(agent, args.message);
|
|
282
|
+
printSessionSummary(agent);
|
|
136
283
|
return;
|
|
137
284
|
}
|
|
138
285
|
// Non-interactive: piped stdin
|
|
@@ -140,6 +287,7 @@ async function main() {
|
|
|
140
287
|
const input = await readStdin();
|
|
141
288
|
if (input.trim()) {
|
|
142
289
|
await runOnce(agent, input.trim());
|
|
290
|
+
printSessionSummary(agent);
|
|
143
291
|
}
|
|
144
292
|
return;
|
|
145
293
|
}
|
|
@@ -151,6 +299,22 @@ async function main() {
|
|
|
151
299
|
// Cleanup scheduler on exit
|
|
152
300
|
scheduler.stop();
|
|
153
301
|
}
|
|
302
|
+
/** Print session summary with tokens, cost, tool calls, files modified */
|
|
303
|
+
function printSessionSummary(agent) {
|
|
304
|
+
const tracker = agent.getTokenTracker();
|
|
305
|
+
tracker.saveUsage();
|
|
306
|
+
const summary = tracker.getSummary();
|
|
307
|
+
const duration = (new Date(summary.endTime).getTime() - new Date(summary.startTime).getTime()) / 1000;
|
|
308
|
+
const mins = Math.floor(duration / 60);
|
|
309
|
+
const secs = Math.floor(duration % 60);
|
|
310
|
+
console.log(c('\n── Session Summary ──', 'dim'));
|
|
311
|
+
console.log(` Duration: ${mins}m ${secs}s`);
|
|
312
|
+
console.log(` Model: ${summary.model} via ${summary.provider}`);
|
|
313
|
+
console.log(` Tokens: ${summary.totalInputTokens.toLocaleString()} in / ${summary.totalOutputTokens.toLocaleString()} out (${tracker.formatCost()})`);
|
|
314
|
+
console.log(` Requests: ${summary.requestCount}`);
|
|
315
|
+
console.log(` Tools: ${summary.toolCalls} calls`);
|
|
316
|
+
console.log(` Files: ${summary.filesModified} modified`);
|
|
317
|
+
}
|
|
154
318
|
function createProvider(config) {
|
|
155
319
|
if (config.provider === 'anthropic') {
|
|
156
320
|
return new anthropic_1.AnthropicProvider({
|
|
@@ -159,7 +323,6 @@ function createProvider(config) {
|
|
|
159
323
|
model: config.model,
|
|
160
324
|
});
|
|
161
325
|
}
|
|
162
|
-
// All other providers use OpenAI-compatible format
|
|
163
326
|
return new openai_1.OpenAIProvider({
|
|
164
327
|
baseUrl: config.baseUrl,
|
|
165
328
|
apiKey: config.apiKey,
|
|
@@ -186,7 +349,7 @@ async function repl(agent, config, session) {
|
|
|
186
349
|
}
|
|
187
350
|
try {
|
|
188
351
|
for await (const event of agent.run(input)) {
|
|
189
|
-
renderEvent(event);
|
|
352
|
+
renderEvent(event, agent);
|
|
190
353
|
}
|
|
191
354
|
}
|
|
192
355
|
catch (err) {
|
|
@@ -197,18 +360,19 @@ async function repl(agent, config, session) {
|
|
|
197
360
|
rl.prompt();
|
|
198
361
|
});
|
|
199
362
|
rl.on('close', () => {
|
|
363
|
+
printSessionSummary(agent);
|
|
200
364
|
console.log(c('\nBye!', 'dim'));
|
|
201
365
|
process.exit(0);
|
|
202
366
|
});
|
|
203
367
|
}
|
|
204
368
|
async function runOnce(agent, message) {
|
|
205
369
|
for await (const event of agent.run(message)) {
|
|
206
|
-
renderEvent(event);
|
|
370
|
+
renderEvent(event, agent);
|
|
207
371
|
}
|
|
208
372
|
console.log();
|
|
209
373
|
}
|
|
210
374
|
let isThinking = false;
|
|
211
|
-
function renderEvent(event) {
|
|
375
|
+
function renderEvent(event, agent) {
|
|
212
376
|
switch (event.type) {
|
|
213
377
|
case 'thinking':
|
|
214
378
|
if (!isThinking) {
|
|
@@ -248,20 +412,15 @@ function renderEvent(event) {
|
|
|
248
412
|
}
|
|
249
413
|
break;
|
|
250
414
|
case 'usage':
|
|
251
|
-
if (event.usage) {
|
|
252
|
-
|
|
253
|
-
sessionTokens.input += event.usage.inputTokens;
|
|
254
|
-
if (event.usage.outputTokens)
|
|
255
|
-
sessionTokens.output += event.usage.outputTokens;
|
|
256
|
-
if (event.usage.totalTokens)
|
|
257
|
-
sessionTokens.total += event.usage.totalTokens;
|
|
415
|
+
if (event.usage && agent) {
|
|
416
|
+
const tracker = agent.getTokenTracker();
|
|
258
417
|
const parts = [];
|
|
259
418
|
if (event.usage.inputTokens)
|
|
260
419
|
parts.push(`in: ${event.usage.inputTokens}`);
|
|
261
420
|
if (event.usage.outputTokens)
|
|
262
421
|
parts.push(`out: ${event.usage.outputTokens}`);
|
|
263
422
|
if (parts.length > 0) {
|
|
264
|
-
console.log(c(` [${parts.join(', ')} tokens]`, 'dim'));
|
|
423
|
+
console.log(c(` [${parts.join(', ')} tokens | ${tracker.formatCost()}]`, 'dim'));
|
|
265
424
|
}
|
|
266
425
|
}
|
|
267
426
|
break;
|
|
@@ -306,7 +465,10 @@ function handleSlashCommand(input, agent, config) {
|
|
|
306
465
|
/auto Toggle autonomous mode
|
|
307
466
|
/routines List scheduled routines
|
|
308
467
|
/undo Undo last file edit (/undo [path])
|
|
309
|
-
/usage Show token usage for this session
|
|
468
|
+
/usage Show token usage & cost for this session
|
|
469
|
+
/cost Show running cost
|
|
470
|
+
/policy Show current security policy
|
|
471
|
+
/audit Verify audit chain for this session
|
|
310
472
|
/config Show current config
|
|
311
473
|
/quit Exit`);
|
|
312
474
|
break;
|
|
@@ -365,10 +527,41 @@ function handleSlashCommand(input, agent, config) {
|
|
|
365
527
|
break;
|
|
366
528
|
}
|
|
367
529
|
case '/usage': {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
console.log(
|
|
371
|
-
console.log(`
|
|
530
|
+
const tracker = agent.getTokenTracker();
|
|
531
|
+
const summary = tracker.getSummary();
|
|
532
|
+
console.log(c('\nSession Usage:', 'bold'));
|
|
533
|
+
console.log(` Input: ${summary.totalInputTokens.toLocaleString()} tokens`);
|
|
534
|
+
console.log(` Output: ${summary.totalOutputTokens.toLocaleString()} tokens`);
|
|
535
|
+
console.log(` Cost: ${tracker.formatCost()}`);
|
|
536
|
+
console.log(` Requests: ${summary.requestCount}`);
|
|
537
|
+
console.log(` Tools: ${summary.toolCalls} calls`);
|
|
538
|
+
console.log(` Files: ${summary.filesModified} modified`);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case '/cost': {
|
|
542
|
+
const tracker = agent.getTokenTracker();
|
|
543
|
+
console.log(c(` ${tracker.formatStatusLine()}`, 'dim'));
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case '/policy': {
|
|
547
|
+
const policy = agent.getPolicyEnforcer().getPolicy();
|
|
548
|
+
console.log(c('\nCurrent Policy:', 'bold'));
|
|
549
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
case '/audit': {
|
|
553
|
+
const auditLogger = agent.getAuditLogger();
|
|
554
|
+
const result = auditLogger.verifySession();
|
|
555
|
+
if (result.entriesChecked === 0) {
|
|
556
|
+
console.log(c('No audit entries yet.', 'dim'));
|
|
557
|
+
}
|
|
558
|
+
else if (result.valid) {
|
|
559
|
+
console.log(c(`Audit chain valid (${result.entriesChecked} entries)`, 'green'));
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
console.log(c(`Audit chain INVALID at sequence ${result.firstInvalidAt}`, 'red'));
|
|
563
|
+
console.log(c(` ${result.reason}`, 'red'));
|
|
564
|
+
}
|
|
372
565
|
break;
|
|
373
566
|
}
|
|
374
567
|
case '/routines': {
|
|
@@ -409,7 +602,6 @@ function showModels() {
|
|
|
409
602
|
}
|
|
410
603
|
}
|
|
411
604
|
async function resolveConfig(args) {
|
|
412
|
-
// Load saved config (CLI args override saved config)
|
|
413
605
|
const saved = (0, setup_1.loadConfig)();
|
|
414
606
|
const model = args.model || process.env.CODEBOT_MODEL || saved.model || 'qwen2.5-coder:32b';
|
|
415
607
|
const detected = (0, registry_1.detectProvider)(model);
|
|
@@ -421,7 +613,6 @@ async function resolveConfig(args) {
|
|
|
421
613
|
maxIterations: parseInt(args['max-iterations'] || String(saved.maxIterations || 50), 10),
|
|
422
614
|
autoApprove: !!args['auto-approve'] || !!args.autonomous || !!args.auto || !!saved.autoApprove,
|
|
423
615
|
};
|
|
424
|
-
// Auto-resolve base URL and API key from provider
|
|
425
616
|
if (!config.baseUrl || !config.apiKey) {
|
|
426
617
|
const defaults = registry_1.PROVIDER_DEFAULTS[config.provider];
|
|
427
618
|
if (defaults) {
|
|
@@ -431,14 +622,12 @@ async function resolveConfig(args) {
|
|
|
431
622
|
config.apiKey = process.env[defaults.envKey] || process.env.CODEBOT_API_KEY || '';
|
|
432
623
|
}
|
|
433
624
|
}
|
|
434
|
-
// Fallback: try saved config API key, then generic env vars
|
|
435
625
|
if (!config.apiKey && saved.apiKey) {
|
|
436
626
|
config.apiKey = saved.apiKey;
|
|
437
627
|
}
|
|
438
628
|
if (!config.apiKey) {
|
|
439
629
|
config.apiKey = process.env.CODEBOT_API_KEY || process.env.OPENAI_API_KEY || '';
|
|
440
630
|
}
|
|
441
|
-
// If still no base URL, auto-detect local provider
|
|
442
631
|
if (!config.baseUrl) {
|
|
443
632
|
config.baseUrl = await autoDetectProvider();
|
|
444
633
|
}
|
|
@@ -494,6 +683,40 @@ function parseArgs(argv) {
|
|
|
494
683
|
result.setup = true;
|
|
495
684
|
continue;
|
|
496
685
|
}
|
|
686
|
+
if (arg === '--init-policy') {
|
|
687
|
+
result['init-policy'] = true;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (arg === '--sandbox-info') {
|
|
691
|
+
result['sandbox-info'] = true;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (arg === '--verify-audit') {
|
|
695
|
+
const next = argv[i + 1];
|
|
696
|
+
if (next && !next.startsWith('--')) {
|
|
697
|
+
result['verify-audit'] = next;
|
|
698
|
+
i++;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
result['verify-audit'] = true;
|
|
702
|
+
}
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (arg === '--replay') {
|
|
706
|
+
const next = argv[i + 1];
|
|
707
|
+
if (next && !next.startsWith('--')) {
|
|
708
|
+
result['replay'] = next;
|
|
709
|
+
i++;
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
result['replay'] = true; // replay latest
|
|
713
|
+
}
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (arg === '--deterministic') {
|
|
717
|
+
result['deterministic'] = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
497
720
|
if (arg.startsWith('--')) {
|
|
498
721
|
const key = arg.slice(2);
|
|
499
722
|
const next = argv[i + 1];
|
|
@@ -540,9 +763,19 @@ ${c('Options:', 'bold')}
|
|
|
540
763
|
--resume <id> Resume a previous session by ID
|
|
541
764
|
--continue, -c Resume the most recent session
|
|
542
765
|
--max-iterations <n> Max agent loop iterations (default: 50)
|
|
766
|
+
--sandbox <mode> Execution sandbox: docker, host, auto (default: auto)
|
|
543
767
|
-h, --help Show this help
|
|
544
768
|
-v, --version Show version
|
|
545
769
|
|
|
770
|
+
${c('Security & Policy:', 'bold')}
|
|
771
|
+
--init-policy Generate default .codebot/policy.json
|
|
772
|
+
--verify-audit [id] Verify audit log hash chain integrity
|
|
773
|
+
--sandbox-info Show Docker sandbox status
|
|
774
|
+
|
|
775
|
+
${c('Debugging & Replay:', 'bold')}
|
|
776
|
+
--replay [id] Replay a session, re-execute tools, compare outputs
|
|
777
|
+
--deterministic Set temperature=0 for reproducible outputs
|
|
778
|
+
|
|
546
779
|
${c('Supported Providers:', 'bold')}
|
|
547
780
|
Local: Ollama, LM Studio, vLLM (auto-detected)
|
|
548
781
|
Anthropic: Claude Opus/Sonnet/Haiku (ANTHROPIC_API_KEY)
|
|
@@ -560,6 +793,8 @@ ${c('Examples:', 'bold')}
|
|
|
560
793
|
codebot --model deepseek-chat Uses DeepSeek API
|
|
561
794
|
codebot --model qwen2.5-coder:32b Uses local Ollama
|
|
562
795
|
codebot --autonomous "refactor src/" Full auto, no prompts
|
|
796
|
+
codebot --init-policy Create security policy
|
|
797
|
+
codebot --verify-audit Check audit integrity
|
|
563
798
|
|
|
564
799
|
${c('Interactive Commands:', 'bold')}
|
|
565
800
|
/help Show commands
|
|
@@ -569,6 +804,10 @@ ${c('Interactive Commands:', 'bold')}
|
|
|
569
804
|
/auto Toggle autonomous mode
|
|
570
805
|
/clear Clear conversation
|
|
571
806
|
/compact Force context compaction
|
|
807
|
+
/usage Show token usage & cost
|
|
808
|
+
/cost Show running cost
|
|
809
|
+
/policy Show security policy
|
|
810
|
+
/audit Verify session audit chain
|
|
572
811
|
/config Show configuration
|
|
573
812
|
/quit Exit`);
|
|
574
813
|
}
|
package/dist/history.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Message } from './types';
|
|
2
|
+
import { IntegrityResult } from './integrity';
|
|
2
3
|
export interface SessionMeta {
|
|
3
4
|
id: string;
|
|
4
5
|
model: string;
|
|
@@ -11,14 +12,17 @@ export declare class SessionManager {
|
|
|
11
12
|
private sessionId;
|
|
12
13
|
private filePath;
|
|
13
14
|
private model;
|
|
15
|
+
private integrityKey;
|
|
14
16
|
constructor(model: string, sessionId?: string);
|
|
15
17
|
getId(): string;
|
|
16
|
-
/** Append a message to the session file */
|
|
18
|
+
/** Append a message to the session file (HMAC signed) */
|
|
17
19
|
save(message: Message): void;
|
|
18
|
-
/** Save all messages (atomic overwrite
|
|
20
|
+
/** Save all messages (atomic overwrite, HMAC signed) */
|
|
19
21
|
saveAll(messages: Message[]): void;
|
|
20
|
-
/** Load messages from a session file (
|
|
22
|
+
/** Load messages from a session file (verifies HMAC, drops tampered) */
|
|
21
23
|
load(): Message[];
|
|
24
|
+
/** Verify integrity of all messages in this session. */
|
|
25
|
+
verifyIntegrity(): IntegrityResult;
|
|
22
26
|
/** List recent sessions */
|
|
23
27
|
static list(limit?: number): SessionMeta[];
|
|
24
28
|
/** Get the most recent session ID */
|