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/replay.js ADDED
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * Session Replay Engine for CodeBot v1.8.0
4
+ *
5
+ * Replays saved sessions by feeding recorded assistant responses
6
+ * instead of calling the LLM. Tool calls are re-executed and outputs
7
+ * compared against recorded results to detect environment divergences.
8
+ *
9
+ * Usage:
10
+ * codebot --replay <session-id>
11
+ * codebot --replay (replays latest session)
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.ReplayProvider = void 0;
48
+ exports.loadSessionForReplay = loadSessionForReplay;
49
+ exports.compareOutputs = compareOutputs;
50
+ exports.listReplayableSessions = listReplayableSessions;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const os = __importStar(require("os"));
54
+ // ── Replay Provider ──
55
+ /**
56
+ * Mock LLM provider that feeds recorded assistant messages.
57
+ * Used during replay to bypass actual LLM calls.
58
+ */
59
+ class ReplayProvider {
60
+ name = 'replay';
61
+ assistantMessages;
62
+ callIndex = 0;
63
+ constructor(assistantMessages) {
64
+ this.assistantMessages = assistantMessages;
65
+ }
66
+ async *chat(_messages, _tools) {
67
+ const msg = this.assistantMessages[this.callIndex++];
68
+ if (!msg) {
69
+ yield { type: 'text', text: '[replay: no more recorded responses]' };
70
+ yield { type: 'done' };
71
+ return;
72
+ }
73
+ // Emit text content
74
+ if (msg.content) {
75
+ yield { type: 'text', text: msg.content };
76
+ }
77
+ // Emit tool calls
78
+ if (msg.tool_calls) {
79
+ for (const tc of msg.tool_calls) {
80
+ yield {
81
+ type: 'tool_call_end',
82
+ toolCall: tc,
83
+ };
84
+ }
85
+ }
86
+ yield { type: 'done' };
87
+ }
88
+ }
89
+ exports.ReplayProvider = ReplayProvider;
90
+ /**
91
+ * Load a session from disk and prepare it for replay.
92
+ * Returns null if session doesn't exist or is empty.
93
+ */
94
+ function loadSessionForReplay(sessionId) {
95
+ const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
96
+ const filePath = path.join(sessionsDir, `${sessionId}.jsonl`);
97
+ if (!fs.existsSync(filePath))
98
+ return null;
99
+ let content;
100
+ try {
101
+ content = fs.readFileSync(filePath, 'utf-8').trim();
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ if (!content)
107
+ return null;
108
+ const messages = [];
109
+ for (const line of content.split('\n')) {
110
+ try {
111
+ const obj = JSON.parse(line);
112
+ delete obj._ts;
113
+ delete obj._model;
114
+ delete obj._sig;
115
+ messages.push(obj);
116
+ }
117
+ catch {
118
+ continue;
119
+ }
120
+ }
121
+ if (messages.length === 0)
122
+ return null;
123
+ const assistantMessages = messages.filter(m => m.role === 'assistant');
124
+ const userMessages = messages.filter(m => m.role === 'user');
125
+ const toolResults = new Map();
126
+ for (const msg of messages) {
127
+ if (msg.role === 'tool' && msg.tool_call_id) {
128
+ toolResults.set(msg.tool_call_id, msg.content);
129
+ }
130
+ }
131
+ return { messages, assistantMessages, userMessages, toolResults };
132
+ }
133
+ /**
134
+ * Compare recorded vs actual tool output.
135
+ * Returns null if identical, or a diff description.
136
+ */
137
+ function compareOutputs(recorded, actual) {
138
+ if (recorded === actual)
139
+ return null;
140
+ // Normalize whitespace for soft comparison
141
+ const normRecorded = recorded.trim().replace(/\s+/g, ' ');
142
+ const normActual = actual.trim().replace(/\s+/g, ' ');
143
+ if (normRecorded === normActual)
144
+ return null;
145
+ const maxShow = 200;
146
+ const expectedSnippet = normRecorded.length > maxShow
147
+ ? normRecorded.substring(0, maxShow) + '...'
148
+ : normRecorded;
149
+ const actualSnippet = normActual.length > maxShow
150
+ ? normActual.substring(0, maxShow) + '...'
151
+ : normActual;
152
+ return `Expected: ${expectedSnippet}\nActual: ${actualSnippet}`;
153
+ }
154
+ // ── Session Listing ──
155
+ /**
156
+ * List sessions available for replay.
157
+ */
158
+ function listReplayableSessions(limit = 10) {
159
+ const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
160
+ if (!fs.existsSync(sessionsDir))
161
+ return [];
162
+ const files = fs.readdirSync(sessionsDir)
163
+ .filter(f => f.endsWith('.jsonl'))
164
+ .map(f => {
165
+ const stat = fs.statSync(path.join(sessionsDir, f));
166
+ return { name: f, mtime: stat.mtime };
167
+ })
168
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
169
+ .slice(0, limit);
170
+ return files.map(f => {
171
+ const id = f.name.replace('.jsonl', '');
172
+ const fullPath = path.join(sessionsDir, f.name);
173
+ try {
174
+ const content = fs.readFileSync(fullPath, 'utf-8').trim();
175
+ const lines = content ? content.split('\n') : [];
176
+ let preview = '';
177
+ for (const line of lines) {
178
+ try {
179
+ const msg = JSON.parse(line);
180
+ if (msg.role === 'user') {
181
+ preview = msg.content.substring(0, 80);
182
+ break;
183
+ }
184
+ }
185
+ catch {
186
+ continue;
187
+ }
188
+ }
189
+ return { id, preview, messageCount: lines.length, date: f.mtime.toISOString() };
190
+ }
191
+ catch {
192
+ return { id, preview: '', messageCount: 0, date: f.mtime.toISOString() };
193
+ }
194
+ });
195
+ }
196
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Docker Sandbox Execution for CodeBot v1.7.0
3
+ *
4
+ * Runs shell commands inside disposable Docker containers for isolation.
5
+ * Features:
6
+ * - Read-only root filesystem
7
+ * - Project directory mounted read-write
8
+ * - No network by default (configurable)
9
+ * - CPU, memory, PID limits
10
+ * - Automatic container cleanup
11
+ * - Graceful fallback to host execution when Docker unavailable
12
+ */
13
+ export interface SandboxConfig {
14
+ /** Max CPU cores (default: 2) */
15
+ cpus?: number;
16
+ /** Max memory in MB (default: 512) */
17
+ memoryMb?: number;
18
+ /** Max PIDs in container (default: 100) */
19
+ pidsLimit?: number;
20
+ /** Allow network access (default: false) */
21
+ network?: boolean;
22
+ /** Timeout in ms (default: 120000) */
23
+ timeoutMs?: number;
24
+ /** Working directory inside container (default: /workspace) */
25
+ workDir?: string;
26
+ /** Docker image to use (default: node:20-slim) */
27
+ image?: string;
28
+ }
29
+ export interface SandboxResult {
30
+ stdout: string;
31
+ stderr: string;
32
+ exitCode: number;
33
+ sandboxed: boolean;
34
+ }
35
+ /** Check if Docker is installed and the daemon is running */
36
+ export declare function isDockerAvailable(): boolean;
37
+ /** Reset the cached Docker availability check (for testing) */
38
+ export declare function resetDockerCheck(): void;
39
+ /**
40
+ * Execute a command inside a Docker sandbox.
41
+ *
42
+ * The project directory is mounted at /workspace (read-write).
43
+ * Root filesystem is read-only. /tmp is tmpfs (100MB).
44
+ * Network is disabled by default.
45
+ *
46
+ * Falls back to host execution if Docker is unavailable.
47
+ */
48
+ export declare function sandboxExec(command: string, projectDir: string, config?: SandboxConfig): SandboxResult;
49
+ /**
50
+ * Build or pull the sandbox Docker image.
51
+ * Call this during `codebot --setup` or first sandbox use.
52
+ */
53
+ export declare function ensureSandboxImage(image?: string): {
54
+ ready: boolean;
55
+ error?: string;
56
+ };
57
+ /**
58
+ * Get a summary of sandbox configuration for display.
59
+ */
60
+ export declare function getSandboxInfo(): {
61
+ available: boolean;
62
+ image: string;
63
+ defaults: SandboxConfig;
64
+ };
65
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ /**
3
+ * Docker Sandbox Execution for CodeBot v1.7.0
4
+ *
5
+ * Runs shell commands inside disposable Docker containers for isolation.
6
+ * Features:
7
+ * - Read-only root filesystem
8
+ * - Project directory mounted read-write
9
+ * - No network by default (configurable)
10
+ * - CPU, memory, PID limits
11
+ * - Automatic container cleanup
12
+ * - Graceful fallback to host execution when Docker unavailable
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.isDockerAvailable = isDockerAvailable;
49
+ exports.resetDockerCheck = resetDockerCheck;
50
+ exports.sandboxExec = sandboxExec;
51
+ exports.ensureSandboxImage = ensureSandboxImage;
52
+ exports.getSandboxInfo = getSandboxInfo;
53
+ const child_process_1 = require("child_process");
54
+ const path = __importStar(require("path"));
55
+ // ── Docker Detection ──
56
+ let _dockerAvailable = null;
57
+ /** Check if Docker is installed and the daemon is running */
58
+ function isDockerAvailable() {
59
+ if (_dockerAvailable !== null)
60
+ return _dockerAvailable;
61
+ try {
62
+ (0, child_process_1.execSync)('docker info', {
63
+ timeout: 5000,
64
+ stdio: ['pipe', 'pipe', 'pipe'],
65
+ encoding: 'utf-8',
66
+ });
67
+ _dockerAvailable = true;
68
+ }
69
+ catch {
70
+ _dockerAvailable = false;
71
+ }
72
+ return _dockerAvailable;
73
+ }
74
+ /** Reset the cached Docker availability check (for testing) */
75
+ function resetDockerCheck() {
76
+ _dockerAvailable = null;
77
+ }
78
+ // ── Sandbox Execution ──
79
+ const DEFAULT_CONFIG = {
80
+ cpus: 2,
81
+ memoryMb: 512,
82
+ pidsLimit: 100,
83
+ network: false,
84
+ timeoutMs: 120_000,
85
+ workDir: '/workspace',
86
+ image: 'node:20-slim',
87
+ };
88
+ /**
89
+ * Execute a command inside a Docker sandbox.
90
+ *
91
+ * The project directory is mounted at /workspace (read-write).
92
+ * Root filesystem is read-only. /tmp is tmpfs (100MB).
93
+ * Network is disabled by default.
94
+ *
95
+ * Falls back to host execution if Docker is unavailable.
96
+ */
97
+ function sandboxExec(command, projectDir, config) {
98
+ const cfg = { ...DEFAULT_CONFIG, ...config };
99
+ const resolvedProjectDir = path.resolve(projectDir);
100
+ if (!isDockerAvailable()) {
101
+ return hostFallback(command, resolvedProjectDir, cfg.timeoutMs);
102
+ }
103
+ // Build docker run command
104
+ const dockerArgs = [
105
+ 'docker', 'run',
106
+ '--rm', // Cleanup on exit
107
+ '--read-only', // Read-only root filesystem
108
+ '--tmpfs', '/tmp:size=100m', // Writable /tmp
109
+ `--cpus="${cfg.cpus}"`, // CPU limit
110
+ `--memory="${cfg.memoryMb}m"`, // Memory limit
111
+ `--pids-limit`, String(cfg.pidsLimit), // PID limit
112
+ '--security-opt', 'no-new-privileges', // No privilege escalation
113
+ '--cap-drop=ALL', // Drop all capabilities
114
+ ];
115
+ // Network
116
+ if (!cfg.network) {
117
+ dockerArgs.push('--network', 'none');
118
+ }
119
+ // Mount project directory
120
+ dockerArgs.push('-v', `${resolvedProjectDir}:${cfg.workDir}:rw`);
121
+ // Working directory
122
+ dockerArgs.push('-w', cfg.workDir);
123
+ // Image
124
+ dockerArgs.push(cfg.image);
125
+ // Command (via sh -c for shell features)
126
+ dockerArgs.push('sh', '-c', command);
127
+ const dockerCmd = dockerArgs.join(' ');
128
+ try {
129
+ const stdout = (0, child_process_1.execSync)(dockerCmd, {
130
+ timeout: cfg.timeoutMs,
131
+ maxBuffer: 1024 * 1024, // 1MB
132
+ encoding: 'utf-8',
133
+ stdio: ['pipe', 'pipe', 'pipe'],
134
+ });
135
+ return { stdout: stdout || '', stderr: '', exitCode: 0, sandboxed: true };
136
+ }
137
+ catch (err) {
138
+ const e = err;
139
+ return {
140
+ stdout: e.stdout || '',
141
+ stderr: e.stderr || '',
142
+ exitCode: e.status || 1,
143
+ sandboxed: true,
144
+ };
145
+ }
146
+ }
147
+ /**
148
+ * Fallback: execute on host with existing security measures.
149
+ * Used when Docker is not available.
150
+ */
151
+ function hostFallback(command, cwd, timeoutMs) {
152
+ try {
153
+ const stdout = (0, child_process_1.execSync)(command, {
154
+ cwd,
155
+ timeout: timeoutMs,
156
+ maxBuffer: 1024 * 1024,
157
+ encoding: 'utf-8',
158
+ stdio: ['pipe', 'pipe', 'pipe'],
159
+ });
160
+ return { stdout: stdout || '', stderr: '', exitCode: 0, sandboxed: false };
161
+ }
162
+ catch (err) {
163
+ const e = err;
164
+ return {
165
+ stdout: e.stdout || '',
166
+ stderr: e.stderr || '',
167
+ exitCode: e.status || 1,
168
+ sandboxed: false,
169
+ };
170
+ }
171
+ }
172
+ /**
173
+ * Build or pull the sandbox Docker image.
174
+ * Call this during `codebot --setup` or first sandbox use.
175
+ */
176
+ function ensureSandboxImage(image) {
177
+ const img = image || DEFAULT_CONFIG.image;
178
+ if (!isDockerAvailable()) {
179
+ return { ready: false, error: 'Docker is not available' };
180
+ }
181
+ try {
182
+ // Check if image exists locally
183
+ (0, child_process_1.execSync)(`docker image inspect ${img}`, {
184
+ timeout: 10_000,
185
+ stdio: ['pipe', 'pipe', 'pipe'],
186
+ });
187
+ return { ready: true };
188
+ }
189
+ catch {
190
+ // Image not found, try to pull
191
+ try {
192
+ (0, child_process_1.execSync)(`docker pull ${img}`, {
193
+ timeout: 120_000,
194
+ stdio: ['pipe', 'pipe', 'pipe'],
195
+ });
196
+ return { ready: true };
197
+ }
198
+ catch (pullErr) {
199
+ const msg = pullErr instanceof Error ? pullErr.message : String(pullErr);
200
+ return { ready: false, error: `Failed to pull ${img}: ${msg}` };
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Get a summary of sandbox configuration for display.
206
+ */
207
+ function getSandboxInfo() {
208
+ return {
209
+ available: isDockerAvailable(),
210
+ image: DEFAULT_CONFIG.image,
211
+ defaults: { ...DEFAULT_CONFIG },
212
+ };
213
+ }
214
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Token & Cost Tracking for CodeBot v1.7.0
3
+ *
4
+ * Tracks per-request and per-session token usage and estimated costs.
5
+ * Supports cost limits and historical usage queries.
6
+ */
7
+ export interface UsageRecord {
8
+ timestamp: string;
9
+ model: string;
10
+ provider: string;
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ costUsd: number;
14
+ }
15
+ export interface SessionSummary {
16
+ sessionId: string;
17
+ model: string;
18
+ provider: string;
19
+ startTime: string;
20
+ endTime: string;
21
+ totalInputTokens: number;
22
+ totalOutputTokens: number;
23
+ totalCostUsd: number;
24
+ requestCount: number;
25
+ toolCalls: number;
26
+ filesModified: number;
27
+ }
28
+ export declare class TokenTracker {
29
+ private model;
30
+ private provider;
31
+ private sessionId;
32
+ private records;
33
+ private toolCallCount;
34
+ private filesModifiedSet;
35
+ private startTime;
36
+ private costLimitUsd;
37
+ constructor(model: string, provider: string, sessionId?: string);
38
+ /** Set cost limit in USD. 0 = no limit. */
39
+ setCostLimit(usd: number): void;
40
+ /** Record token usage from an LLM request */
41
+ recordUsage(inputTokens: number, outputTokens: number): UsageRecord;
42
+ /** Record a tool call (for summary) */
43
+ recordToolCall(): void;
44
+ /** Record a file modification (for summary) */
45
+ recordFileModified(filePath: string): void;
46
+ /** Check if cost limit has been exceeded */
47
+ isOverBudget(): boolean;
48
+ /** Get remaining budget in USD (Infinity if no limit) */
49
+ getRemainingBudget(): number;
50
+ getTotalInputTokens(): number;
51
+ getTotalOutputTokens(): number;
52
+ getTotalCost(): number;
53
+ getRequestCount(): number;
54
+ /** Generate a session summary */
55
+ getSummary(): SessionSummary;
56
+ /** Format cost for display */
57
+ formatCost(): string;
58
+ /** Format a compact status line for CLI */
59
+ formatStatusLine(): string;
60
+ /** Save session usage to ~/.codebot/usage/ for historical tracking */
61
+ saveUsage(): void;
62
+ /**
63
+ * Load historical usage from ~/.codebot/usage/
64
+ */
65
+ static loadHistory(days?: number): SessionSummary[];
66
+ /**
67
+ * Format a historical usage report.
68
+ */
69
+ static formatUsageReport(days?: number): string;
70
+ private getPricing;
71
+ private isLocalModel;
72
+ }
73
+ //# sourceMappingURL=telemetry.d.ts.map